Статьи

Пример шаблона проектирования команд

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !

1. Введение

Шаблон проектирования команд представляет собой шаблон проектирования поведения и помогает отделить инициатора от получателя запроса.

Чтобы понять шаблон проектирования команд, давайте создадим пример для выполнения различных типов заданий. Задание может быть чем угодно в системе, например, отправка электронной почты, SMS, ведение журнала и выполнение некоторых функций ввода-вывода.

Шаблон Command поможет отделить инициатор от получателя и поможет выполнить любой тип задания, не зная его реализации. Давайте сделаем этот пример более интересным, создав потоки, которые помогут выполнять эти задания одновременно. Поскольку эти задания не зависят друг от друга, последовательность их выполнения не очень важна. Мы создадим пул потоков, чтобы ограничить количество потоков для выполнения заданий. Объект команды инкапсулирует задания и передает его потоку из пула, который будет выполнять задание.

Перед реализацией примера давайте узнаем больше о шаблоне проектирования команд.

2. Что такое шаблон проектирования команд

Целью шаблона проектирования команд является инкапсуляция запроса в виде объекта, что позволяет разработчику параметризовать клиентов с помощью различных запросов, запросов очереди или журнала и поддерживать отмену операций.

В общем, объектно-ориентированное приложение состоит из набора взаимодействующих объектов, каждый из которых предлагает ограниченную, сфокусированную функциональность. В ответ на взаимодействие с пользователем приложение выполняет некоторую обработку. Для этой цели приложение использует службы различных объектов для обработки требований.

С точки зрения реализации, приложение может зависеть от назначенного объекта, который вызывает методы для этих объектов, передавая необходимые данные в качестве аргументов. Этот обозначенный объект может упоминаться как вызывающий, так как он вызывает операции над различными объектами. Invoker может рассматриваться как часть клиентского приложения. Набор объектов, которые фактически содержат реализацию для предоставления услуг, необходимых для обработки запроса, может называться объектами Receiver .

С помощью шаблона Command можно отделить инициатор, который выдает запрос от имени клиента, и набор объектов Receiver оказывающих услуги. Шаблон Command предлагает создать абстракцию для выполняемой обработки или действия, которое необходимо предпринять в ответ на запросы клиента. Эта абстракция может быть разработана для объявления общего интерфейса, который будет реализован различными конкретными реализациями, называемыми объектами Command . Каждый объект Command представляет отдельный тип клиентского запроса и соответствующую обработку.

Данный объект Command отвечает за предоставление функциональности, необходимой для обработки запроса, который он представляет, но он не содержит фактической реализации функциональности. Command объекты используют объекты Receiver предлагая эту функциональность.

Рисунок 1 - Шаблон команды Диаграмма классов

Рисунок 1 — Шаблон команды Диаграмма классов

команда

  • Объявляет интерфейс для выполнения операции.

ConcreteCommand

  • Определяет связь между объектом Receiver и действием.
  • Реализует Execute , вызывая соответствующие операции на Receiver .

клиент

  • Создает объект ConcreteCommand и устанавливает его получатель.

Чешуи

  • Просит команду выполнить запрос.

Приемник

  • Умеет выполнять операции, связанные с выполнением запроса. Любой класс может служить в качестве Receiver .

3. Реализация шаблона проектирования команд

Мы реализуем пример, используя объект команды. На объект команды будет ссылаться общий интерфейс, и он будет содержать метод, который будет использоваться для выполнения запросов. Классы конкретных команд переопределят этот метод и предоставят свою собственную конкретную реализацию для выполнения запроса.

1
2
3
4
5
6
package com.javacodegeeks.patterns.commandpattern;
 
public interface Job {
 
    public void run();
}

Интерфейс Job — это командный интерфейс, содержащий один run метода, который выполняется потоком. Метод execute нашей команды — это метод run, который будет использоваться потоком для выполнения работы.

Там будет другой тип заданий, которые могут быть выполнены. Ниже приведены различные конкретные классы, экземпляры которых будут выполняться различными объектами команд.

1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.commandpattern;
 
public class Email {
 
    public void sendEmail(){
        System.out.println("Sending email.......");
    }
}
1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.commandpattern;
 
public class FileIO {
 
    public void execute(){
        System.out.println("Executing File IO operations...");
    }
}
1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.commandpattern;
 
public class Logging {
 
    public void log(){
        System.out.println("Logging...");
    }
}
1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.commandpattern;
 
public class Sms {
 
    public void sendSms(){
        System.out.println("Sending SMS...");
    }
}

Ниже приведены различные классы команд, которые инкапсулируют вышеуказанные классы и реализуют интерфейс Job .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.javacodegeeks.patterns.commandpattern;
 
public class EmailJob implements Job{
 
    private Email email;
     
    public void setEmail(Email email){
        this.email = email;
    }
     
    @Override
    public void run() {
        System.out.println("Job ID: "+Thread.currentThread().getId()+" executing email jobs.");
        if(email!=null){
            email.sendEmail();
        }
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
         
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.javacodegeeks.patterns.commandpattern;
 
public class FileIOJob implements Job{
 
    private FileIO fileIO;
     
    public void setFileIO(FileIO fileIO){
        this.fileIO = fileIO;
    }
     
    @Override
    public void run() {
        System.out.println("Job ID: "+Thread.currentThread().getId()+" executing fileIO jobs.");
        if(fileIO!=null){
            fileIO.execute();
        }
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
         
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.javacodegeeks.patterns.commandpattern;
 
public class LoggingJob implements Job{
 
    private Logging logging;
     
    public void setLogging(Logging logging){
        this.logging = logging;
    }
     
    @Override
    public void run() {
        System.out.println("Job ID: "+Thread.currentThread().getId()+" executing logging jobs.");
        if(logging!=null){
            logging.log();
        }
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
         
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.javacodegeeks.patterns.commandpattern;
 
public class SmsJob implements Job{
 
    private Sms sms;
     
    public void setSms(Sms sms) {
        this.sms = sms;
    }
 
 
    @Override
    public void run() {
        System.out.println("Job ID: "+Thread.currentThread().getId()+" executing sms jobs.");
        if(sms!=null){
            sms.sendSms();
        }
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
         
    }
 
}

Приведенные выше классы содержат ссылку на их соответствующие классы, которые будут использоваться для выполнения работы. Классы переопределяют метод run и выполняют запрошенную работу. Например, класс SmsJob используется для отправки смс, его метод run вызывает метод sendSms объекта Sms для выполнения работы.

Вы можете установить разные объекты один за другим в один и тот же объект command .

Ниже приведен класс ThreadPool используемый для создания пула потоков и позволяет потоку извлекать и выполнять задание из очереди заданий.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.javacodegeeks.patterns.commandpattern;
 
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
 
public class ThreadPool {
     
    private final BlockingQueue<Job> jobQueue;
    private final Thread[] jobThreads;
    private volatile boolean shutdown;
 
    public ThreadPool(int n)
    {
        jobQueue = new LinkedBlockingQueue<>();
        jobThreads = new Thread[n];
 
        for (int i = 0; i < n; i++) {
            jobThreads[i] = new Worker("Pool Thread " + i);
            jobThreads[i].start();
        }
    }
 
    public void addJob(Job r)
    {
        try {
            jobQueue.put(r);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
 
    public void shutdownPool()
    {
        while (!jobQueue.isEmpty()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        shutdown = true;
        for (Thread workerThread : jobThreads) {
            workerThread.interrupt();
        }
    }
 
    private class Worker extends Thread
    {
        public Worker(String name)
        {
            super(name);
        }
 
        public void run()
        {
            while (!shutdown) {
                try {
                    Job r = jobQueue.take();
                    r.run();
                } catch (InterruptedException e) {
                }
            }
        }
    }
 
}

Приведенный выше класс используется для создания n потоков (рабочих потоков). Каждый рабочий поток будет ожидать задания в очереди, а затем выполнит задание и вернется в состояние ожидания. Класс содержит очередь заданий; когда новое задание будет добавлено в очередь, рабочий поток из пула выполнит задание.

Мы также включили метод shutdownPool который будет использоваться для закрытия пула путем прерывания всех рабочих потоков только тогда, когда очередь заданий пуста. Метод addJob используется для добавления заданий в очереди.

Теперь давайте проверим код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.javacodegeeks.patterns.commandpattern;
 
 
public class TestCommandPattern {
    public static void main(String[] args)
    {
        init();
    }
  
    private static void init()
    {
        ThreadPool pool = new ThreadPool(10);
         
        Email email = null;
        EmailJob  emailJob = new EmailJob();
         
        Sms sms = null;
        SmsJob smsJob = new SmsJob();
         
        FileIO fileIO = null;;
        FileIOJob fileIOJob = new FileIOJob();
         
        Logging logging = null;
        LoggingJob logJob = new LoggingJob();
         
        for (int i = 0; i < 5; i++) {
            email = new Email();
            emailJob.setEmail(email);
             
            sms = new Sms();
            smsJob.setSms(sms);
             
            fileIO = new FileIO();
            fileIOJob.setFileIO(fileIO);
             
            logging = new Logging();
            logJob.setLogging(logging);
             
            pool.addJob(emailJob);
            pool.addJob(smsJob);
            pool.addJob(fileIOJob);
            pool.addJob(logJob);
        }
        pool.shutdownPool();
    }
 
}

Приведенный выше код приведет к следующему выводу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Job ID: 9 executing email jobs.
Sending email.......
Job ID: 12 executing logging jobs.
Job ID: 17 executing email jobs.
Sending email.......
Job ID: 13 executing email jobs.
Sending email.......
Job ID: 10 executing sms jobs.
Sending SMS...
Job ID: 11 executing fileIO jobs.
Executing File IO operations...
Job ID: 18 executing sms jobs.
Sending SMS...
Logging...
Job ID: 16 executing logging jobs.
Logging...
Job ID: 15 executing fileIO jobs.
Executing File IO operations...
Job ID: 14 executing sms jobs.
Sending SMS...
Job ID: 12 executing fileIO jobs.
Executing File IO operations...
Job ID: 10 executing logging jobs.
Logging...
Job ID: 18 executing email jobs.
Sending email.......
Job ID: 16 executing sms jobs.
Sending SMS...
Job ID: 14 executing fileIO jobs.
Executing File IO operations...
Job ID: 9 executing logging jobs.
Logging...
Job ID: 17 executing email jobs.
Sending email.......
Job ID: 13 executing sms jobs.
Sending SMS...
Job ID: 15 executing fileIO jobs.
Executing File IO operations...
Job ID: 11 executing logging jobs.
Logging...

Обратите внимание, что выходные данные могут отличаться при последующих выполнениях.

В приведенном выше классе мы создали пул потоков из 10 потоков. Затем мы устанавливаем разные объекты команд с разными заданиями и добавляем эти задания в очередь, используя метод addJob класса ThreadPool . Как только задание вставляется в очередь, поток выполняет задание и удаляет его из очереди.

Мы установили другой тип заданий, но, используя шаблон проектирования команд, мы отделяем задание от потока вызывающего. Поток выполнит любой тип объекта, который реализует интерфейс Job . Различные объекты команд инкапсулируют различные объекты и выполняют запрошенные операции над этими объектами.

Выходные данные показывают разные потоки, выполняющие разные задания. Наблюдая за идентификатором задания в выводе, вы можете ясно увидеть, что один поток выполняет более одного задания. Это потому, что после выполнения задания поток отправляет обратно в пул.

Преимущество шаблона проектирования команд состоит в том, что вы можете добавлять более различные виды работ, не изменяя существующие классы. Это приводит к большей гибкости и удобству сопровождения, а также снижает вероятность ошибок в коде.

4. Когда использовать шаблон проектирования команд

Используйте шаблон Command, когда вы хотите:

  • Параметризация объектов по действию, которое нужно выполнить.
  • Укажите, поставьте в очередь и выполняйте запросы в разное время. Объект Command может иметь время жизни независимо от исходного запроса. Если получатель запроса может быть представлен независимым от адресного пространства способом, то вы можете передать объект команды для запроса другому процессу и выполнить запрос там.
  • Поддержка отмены. Операция « Execute может сохранять состояние для отмены ее эффектов в самой команде. Интерфейс Command должен иметь добавленную операцию Un-execute которая отменяет эффекты предыдущего вызова Execute. Выполненные команды сохраняются в списке истории. Неограниченный уровень отмены и повтора достигается путем обхода этого списка назад и вперед, вызывая Un-execute и Execute , соответственно.
  • Поддержка регистрации изменений, чтобы их можно было повторно применить в случае сбоя системы. Расширяя интерфейс Command операциями загрузки и хранения, вы можете вести постоянный журнал изменений. Восстановление после сбоя включает в себя перезагрузку зарегистрированных команд с диска и повторное их выполнение с помощью команды «Выполнить».
  • Структурируйте систему вокруг операций высокого уровня, построенных на операциях примитивов. Такая структура распространена в информационных системах, поддерживающих транзакции. Транзакция инкапсулирует набор изменений в данные. Шаблон Command предлагает способ моделирования транзакций. Команды имеют общий интерфейс, позволяющий вам вызывать все транзакции одинаково. Шаблон также позволяет легко расширять систему новыми транзакциями.

5. Шаблон проектирования команд в JDK

  • java.lang.Runnable
  • javax.swing.Action

6. Загрузите исходный код

Это был урок по шаблону проектирования команд. Вы можете скачать исходный код здесь: CommandPattern-Project