Skip to content

9. [TD]: پیاده‌سازی لایه [ui] با استفاده از یک برنامه کنسول

واژه‌های کلیدی: معماری چندلایه، اسپرینگ، تزریق وابستگی.

9.1. Support

در [1]، پوشه [support / chap-09] حاوی پروژه Eclipse برای لایه [UI] از برنامه کنسول است.

9.2. پیکربندی Maven

پروژه اکلیپس [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>
        <!-- اسپرینگ بوت -->
        <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>
  • خطوط ۲۲–۲۶: آرشیو لایه [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 {
}
  • خط ۸: بین‌های تعریف‌شده در لایه [métier] وارد می‌شوند. این لایه قبلاً بین‌های تعریف‌شده در لایه [DAO] را وارد کرده است. بنابراین ما در اینجا به بین‌های هر سه لایه دسترسی داریم؛
  • خط ۹: بسته [elections.ui.service] حاوی بیین‌های بیشتری است؛

9.4. رابط لایه [UI]

  

برای درک اینکه رابط جاوا لایه [UI] ممکن است چه باشد، باید بدانیم چه کسی از این رابط استفاده خواهد کرد. این رابط برای کاربر نمودار بالا نیست، بلکه برای برنامه‌ای است که کل برنامه را راه‌اندازی می‌کند.

رابط [IElectionsUI] به برنامه اصلی [main] که کل برنامه را راه‌اندازی می‌کند، ارائه می‌شود. [main] چه چیزی را می‌تواند از لایه [ui] درخواست کند؟ می‌تواند از آن بخواهد تعامل با کاربر را آغاز کند، کسی که داده‌های انتخاباتی ناقص را وارد خواهد کرد. ما رابط حداقلی زیر را اتخاذ خواهیم کرد:


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • خط ۷: این رابط تنها یک متد دارد: run. با فراخوانی این متد، به لایه [ui] دستور می‌دهیم تا تعامل با کاربر را آغاز کند.

9.5. کلاس راه‌انداز برنامه

  

کلاس شروع‌کنندهٔ برنامه یک کلاس جاوا است که شامل یک متد ایستا به نام [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 {
            //بازیابی کانکست اسپرینگ
            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();
        }
    }
}
  • خط ۱۹: کانکست Spring نمونه سازی می‌شود: تمام Beans تعریف شده در فایل‌های پیکربندی مختلف ایجاد خواهند شد؛
  • خط ۲۱: یک مرجع به بیانی که رابط [IElectionsUI] را پیاده‌سازی می‌کند، بازیابی می‌شود. ما رابط [IElectionsUI] را با استفاده از دو بیان پیاده‌سازی خواهیم کرد:
    • [ElectionsConsole] برای یک برنامه کنسول؛
    • [ElectionsSwing] برای یک برنامه Swing؛

متد [getUI] انتزاعی است (خط ۴۴). این به این دلیل است که کلاس [AbstractBootElections] والد دو کلاس خواهد بود:

  • [BootElectionsConsole]، که بیِن [ElectionsConsole] را به کلاس والد خود ارائه خواهد داد؛
  • [BootElectionsSwing]، که بیون [ElectionsSwing] را به کلاس والد خود ارائه خواهد داد؛
  • خط ۳۰: متد [run] اینفریس اجرا می‌شود؛

استثناها در نقاط مختلف رسیدگی می‌شوند:

  • خطوط 22–27: ممکن است هنگام نمونه‌سازی کنتکست Spring یک استثنا رخ دهد؛
  • خطوط ۳۱–۴۱: ممکن است هنگام اجرای لایه [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);
    }
}
  • خط ۵: کلاس [BootElectionsConsole] از کلاس [AbstractBootElections] ارث می‌برد. بنابراین باید متد [getUI] را که کلاس والدین آن را به صورت انتزاعی (abstract) اعلام کرده بود، پیاده‌سازی کند؛
  • خطوط ۱۰–۱۳: پیاده‌سازی متد [getUI];
  • خط ۱۲: بیانی به نام [electionsConsole] موظف به پیاده‌سازی رابط [IElectionsUI] می‌شود. [ctx] کانکست Spring است که در کلاس والد با اعلامیه‌ی زیر تعریف شده است:

    // بازیابی کن متن Spring
    protected AnnotationConfigApplicationContext ctx;

از آنجا که فیلد دارای ویژگی [protected] است، در کلاس‌های فرزند قابل مشاهده است.

بیان در خط ۱۲ به شرح زیر اعلام خواهد شد:


@Component
public class ElectionsConsole implements IElectionsUI {

نام پیش‌فرض این بین، نام کلاس با حرف اول کوچک است. شما می‌توانید این نام پیش‌فرض را با نوشتن صریح آن بازنویسی کنید:

@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] کلاسی خواهد بود که از کنسول برای ارتباط با کاربر استفاده می‌کند. در اینجا مثالی از یک دیالوگ که در کنسول اکلیپس نمایش داده می‌شود، آورده شده است:


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;
            }
        }
    }

}
  • خط ۱۲: کلاس [ElectionsConsole] یک کامپوننت Spring است؛
  • خط ۱۳: کلاس رابط [IElectionsUI] را پیاده‌سازی می‌کند
  • خطوط ۱۵–۱۶: اسپرینگ یک مرجع به لایه [metier] تزریق می‌کند؛
  • خطوط ۱۸–۳۴: متد [run] از رابط [IelectionsUI];

try در خطوط 21–28 با نام try-with-resources فراخوانی می‌شود و نحو آن به شرح زیر است:

1
2
3
try(ressource){
}

منبع در خط ۱ باید رابط [java.lang.AutoCloseable] را پیاده‌سازی کند. منبع در خط ۱ باز شده و پس از خط ۳ به طور خودکار بسته می‌شود، صرف‌نظر از اینکه آیا خطایی (exception) در کدی که بین این دو خط اجرا می‌شود رخ دهد یا خیر. این نحو تضمین می‌کند که یک منبع باز، تحت هر شرایطی، قطعاً بسته خواهد شد.


وظیفه: کد متد [run] را بنویسید. از نظرات برای راهنمایی استفاده کنید.