Skip to content

9. [TD]: تنفيذ الطبقة [ui] باستخدام برنامج وحدة التحكم

الكلمات المفتاحية: بنية متعددة الطبقات، Spring، حقن التبعيات.

9.1. Support

في [1]، يحتوي المجلد [support / chap-09] على مشروع Eclipse للطبقة [UI] لتطبيق وحدة التحكم.

9.2. تكوين Maven

يتم تكوين مشروع Eclipse [elections-ui-metier-dao-jdbc] بواسطة ملف Maven [pom.xml] التالي:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-ui-metier-dao-jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-ui-metier-dao-jdbc</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- المهنة -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-jdbc</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- اختبار Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- المكونات الإضافية -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>config.AppConfig</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- لتثبيت أداة المشروع في مستودع Maven المحلي -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • الأسطر 22-26: يتم استيراد أرشيف الطبقة [métier] وبالتالي أرشيف الطبقة [DAO

9.3. تكوين Spring

  

تقوم الفئة [UiConfig] بتكوين تطبيق Spring:


package elections.ui.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

import elections.metier.config.MetierConfig;

@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.ui.service" })
public class UiConfig {
}
  • السطر 8: يتم استيراد الحبوب المحددة في الطبقة [métier]. كانت هذه الطبقة تستورد بالفعل الحبوب المحددة في الطبقة [DAO]. لذلك، يمكننا هنا الوصول إلى الحبوب في الطبقات الثلاث؛
  • السطر 9: تحتوي الحزمة [elections.ui.service] على مكونات أخرى؛

9.4. واجهة الطبقة [UI]

  

لفهم ماهية واجهة Java للطبقة [UI]، علينا معرفة من سيستخدم هذه الواجهة. لا يتعلق الأمر بمستخدم المخطط أعلاه، بل بالبرنامج الذي سيقوم بتشغيل التطبيق ككل.

يتم تقديم واجهة [IElectionsUI] إلى البرنامج الرئيسي [main] الذي سيقوم بتشغيل التطبيق. ما الذي يمكن أن تطلبه [main] من الطبقة [ui]؟ يمكنها أن تطلب منها بدء التبادل مع المستخدم الذي سيقوم بإدخال البيانات الناقصة للانتخابات. سنعتمد الواجهة الدنيا التالية:


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • السطر 7: تحتوي الواجهة على طريقة واحدة فقط: run. عند استدعاء هذه الطريقة، نطلب من الطبقة [ui] بدء التفاعل مع المستخدم.

9.5. فئة بدء تشغيل التطبيق

  

فئة بدء تشغيل التطبيق هي فئة Java تحتوي على طريقة ثابتة [main]. يجب أن تقوم هذه الطريقة بإنشاء مثيلات للطبقات [ui, metier, demo] وأن تطلب من الطبقة [ui] بدء الحوار مع المستخدم. قد تكون هذه الفئة هي الفئة [AbstractBootElections] التالية:


package elections.ui.boot;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import elections.dao.entities.ElectionsException;
import elections.ui.config.UiConfig;
import elections.ui.service.IElectionsUI;

public abstract class AbstractBootElections {

    // استرداد سياق Spring
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // إنشاء مثيل لطبقة [ui]
        IElectionsUI electionsUI = null;
        try {
            // استرداد سياق Spring
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // استرداد الطبقة [ui]
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // الإبلاغ عن الخطأ
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // إيقاف التطبيق
            System.exit(1);
        }
        // تنفيذ الطبقة [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // الإبلاغ عن الخطأ
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // يتم إيقاف التطبيق
            System.exit(3);
        } catch (RuntimeException ex1) {
            // الإبلاغ عن الخطأ
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // يتم إيقاف التطبيق
            System.exit(2);
        }
    }

    protected abstract IElectionsUI getUI();

    private void afficheExceptions(String message, ElectionsException ex) {
        // عرض الرسالة
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // عرض الأخطاء
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }

    }

    public void afficheExceptions(String message, Exception ex) {
        // عرض الرسالة
        System.out.println(String.format("%s -------------", message));
        // عرض مكدس الاستثناءات
        Throwable cause = ex;
        while (cause != null) {
            System.out.println(String.format("-- %s", cause.getMessage()));
            cause = cause.getCause();
        }
    }
}
  • السطر 19: يتم إنشاء مثيل لسياق Spring: سيتم إنشاء جميع الفاصوليا المحددة في ملفات التكوين المختلفة؛
  • السطر 21: يتم استرداد مرجع إلى bean الذي ينفذ واجهة [IElectionsUI]. سنقوم بتنفيذ واجهة [IElectionsUI] بواسطة beanين:
    • [ElectionsConsole] لتطبيق وحدة التحكم؛
    • [ElectionsSwing] لتطبيق Swing؛

الطريقة [getUI] هي طريقة مجردة (السطر 44). في الواقع، ستكون الفئة [AbstractBootElections] هي الفئة الأم لفئتين:

  • [BootElectionsConsole] التي ستوفر bean [ElectionsConsole] لفئتها الأم؛
  • [BootElectionsSwing] التي ستوفر bean [ElectionsSwing] لفئتها الأم؛
  • السطر 30: يتم تنفيذ الطريقة [run] للواجهة؛

يتم التعامل مع الاستثناءات في عدة أماكن:

  • الأسطر 22-27: قد تحدث استثناءات أثناء إنشاء مثيل لسياق Spring؛
  • الأسطر 31-41: قد تحدث استثناءات أثناء تنفيذ الطبقة [UI]. هناك نوعان من الاستثناءات:
    • الفئة [ElectionsException] التي تطلقها الطبقة [DAO
    • الفئة [RuntimeException] للاستثناءات الأخرى التي قد تحدث أثناء التنفيذ؛

الفئة [BootElectionsConsole] هي الفئة التي تطلق تنفيذ وحدة التحكم. ورمزها هو كما يلي:


package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsConsole extends AbstractBootElections{
    public static void main(String[] arguments) {
        new BootElectionsConsole().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConsole",IElectionsUI.class);
    }
}
  • السطر 5، الفئة [BootElectionsConsole] توسع الفئة [AbstractBootElections]. لذا يجب أن تنفذ الطريقة [getUI] التي أعلنتها فئتها الأم على أنها مجردة؛
  • الأسطر 10-13: تنفيذ الطريقة [getUI
  • السطر 12: نجعل الفول المسمى [electionsConsole] ينفذ واجهة [IElectionsUI]. [ctx] هو سياق Spring المحدد في الفئة الأم من خلال الإعلان:

    // استرداد سياق Spring
    protected AnnotationConfigApplicationContext ctx;

نظرًا لأن الحقل يحتوي على السمة [protected]، فإنه يكون مرئيًا في الفئات الفرعية.

سيتم إعلان bean في السطر 12 على النحو التالي:


@Component
public class ElectionsConsole implements IElectionsUI {

الاسم الافتراضي لهذا bean هو اسم الفئة مع الحرف الأول صغيرًا. يمكن تجاوز هذا الاسم الافتراضي بالكتابة صراحةً:

@Component(nom_du_bean_entre_guillemets)

قد تكون الفئة [BootElectionsSwing] التي تطلق تنفيذ Swing كما يلي:


package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsSwing", IElectionsUI.class);
    }
}

هذا النموذج التصميمي، الذي يتم فيه تجميع السلوك المشترك بين الفئات في فئة أم، وترك الفئات الفرعية لتنفيذ التفاصيل الخاصة بها، يُسمى نموذج التصميم Strategy. يفرض هذا النموذج التصميمي سلوكًا مشتركًا على جميع الفئات الفرعية.

9.6. فئة التنفيذ [ElectionsConsole]

  

ستكون فئة التنفيذ الأولى الخاصة بنا في الطبقة [ui] فئة تستخدم وحدة التحكم للتواصل مع المستخدم. فيما يلي مثال على حوار تم الحصول عليه على وحدة التحكم Eclipse:


Il y a 7 listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :
Nombre de voix de la liste [A] : 2500
Nombre de voix de la liste [B] : 4500
Nombre de voix de la liste [C] : x
Nombre de voix incorrect. Veuillez recommencer
Nombre de voix de la liste [C] : 8000
Nombre de voix de la liste [D] : 12000
Nombre de voix de la liste [E] : 16000
Nombre de voix de la liste [F] : 25000
Nombre de voix de la liste [G] : 32000

Résultats de l'élection

[G,32000,2,false]
[F,25000,2,false]
[E,16000,1,false]
[D,12000,1,false]
[C,8000,0,false]
[B,4500,0,true]
[A,2500,0,true]

قد يكون للطبقة [ElectionsConsole] الهيكل التالي:


package elections.ui.service;

import java.util.Comparator;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.dao.entities.ListeElectorale;
import elections.metier.service.IElectionsMetier;

@Component
public class ElectionsConsole implements IElectionsUI {

    @Autowired
    private IElectionsMetier electionsMetier;

    @Override
    public void run() {
        // إدخال البيانات
        try (Scanner clavier = new Scanner(System.in)) {
            // يتم طلب القوائم المتنافسة من الطبقة [metier]

            // يتم إدخال الأصوات

        }
        // يتم حساب المقاعد

        // يتم تسجيل النتائج

        // يتم فرز القوائم حسب ترتيب الأصوات التنازلي

        // يتم عرضها
    }

    // فئة مقارنة القوائم الانتخابية
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // مقارنة قائمتين مرشحتين حسب عدد الأصوات
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // مقارنة الأصوات بين هاتين القائمتين
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}
  • السطر 12: الفئة [ElectionsConsole] هي مكون Spring؛
  • السطر 13: الفئة تنفذ واجهة [IElectionsUI]
  • السطران 15-16: حقن مرجع بواسطة Spring في الطبقة [metier
  • السطر 18-34: الطريقة [run] للواجهة [IelectionsUI

يُسمى try في الأسطر 21-28 بـ try-with-resources وصيغته هي كما يلي:

1
2
3
try(ressource){
}

يجب أن تنفذ المورد في السطر 1 واجهة [java.lang.AutoCloseable]. يتم فتح المورد في السطر 1 ويتم إغلاقه تلقائيًا بعد السطر 3، سواء كان هناك استثناء أم لا في الكود المنفذ بين السطرين. تسمح هذه الصيغة بالتأكد من أن المورد المفتوح سيتم إغلاقه بالفعل، مهما حدث.


المهمة المطلوبة: اكتب كود الطريقة [run]. استعن بالتعليقات.