Статьи

Java 8 лямбда-выражение для шаблонов проектирования — шаблон проектирования команд

В этом блоге я иллюстрирую реализацию шаблона команд в стиле функционального программирования с использованием лямбда-выражений Java 8 . Назначение шаблона команды состоит в том, чтобы инкапсулировать запрос в виде объекта, тем самым параметризируя клиентов различными запросами, запросами очереди или журнала и поддерживая соответствующие операции. Шаблон команды — это способ написания универсального кода, который упорядочивает и выполняет методы на основе решений во время выполнения. Участники этого шаблона следующие:

  • Команда — объявляет интерфейс для выполнения операции.
  • ConcreteCommand — определяет привязку между объектом Receiver и действием.
  • Клиент — создает экземпляр ConcreteCommand и устанавливает его получателя.
  • Invoker — Управляет командой (-ами) для выполнения запроса (-ов).
  • Приемник — выполняет фактическую работу.

Отношения между этими участниками изображены ниже:

команда

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

1
2
3
4
5
public interface FileSystemReceiver {
    void openFile();
    void writeFile();
        void closeFile();
}

Каждая из операций, таких как openFile и writefile , является командами. Мы можем создать общий командный интерфейс, чтобы соответствовать этим различным операциям. Давайте назовем этот интерфейс Action, так как он представляет выполнение одного действия в нашем домене. Это интерфейс, который реализуют все наши объекты команд.

1
2
3
public interface Action {
    public void perform();
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
public class OpenFile implements Action {
 
    private final FileReceiver fileReceiver;
 
    public OpenFile(FileReceiver fileReceiver) {
        this.fileReceiver = fileReceiver;
    }
 
    public void perform() {
        fileReceiver.openFile();
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Macro {
    private final List actions;
 
    public Macro() {
        actions = new ArrayList<>();
    }
 
    public void record(Action action) {
        actions.add(action);
    }
 
    public void run() {
        actions.forEach(Action::perform);
    }
}

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

1
2
3
4
5
Macro macro = new Macro();
macro.record(new OpenFile(fileReceiver));
macro.record(new WriteFile(fileReceiver));
macro.record(new CloseFile(fileReceiver));
macro.run();

Если бы вы были со мной до этого момента, вам было бы интересно, где лямбда-выражения вписываются во все это. На самом деле все наши командные классы, такие как OpenFile, WriteFile и CloseFile, на самом деле являются просто лямбда-выражениями, которые хотят вырваться из своих оболочек. Это просто поведение, которое передается как классы. Весь этот шаблон становится намного проще с лямбда-выражениями, потому что мы можем полностью покончить с этими классами. Давайте посмотрим, как класс Macro (клиент) может использовать лямбда-выражения вместо командных классов.

1
2
3
4
5
Macro macro = new Macro();
macro.record(() -> fileReceiver.openFile());
macro.record(() -> fileReceiver.writeFile());
macro.record(() -> fileReceiver.closeFile());
macro.run();

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

1
2
3
4
5
Macro macro = new Macro();
macro.record(fileReceiver::openFile);
macro.record(fileReceiver::writeFile);
macro.record(fileReceiver::closeFile);
macro.run();

Шаблон команд легко расширяется, и в приемники можно добавлять новые методы действий для создания новых реализаций команд без изменения клиентского кода. Runnable интерфейс (java.lang.Runnable) в JDK — это популярный интерфейс, в котором используется шаблон Command. В этом блоге я попытался выразить шаблон команды в лямбда-выражении Java 8. Вы бы увидели, что с помощью лямбда-выражений требуется гораздо меньше шаблонов, что приводит к более чистому коду.

Этот пост был вдохновлен статьей « Использование шаблона команды с лямбда-выражениями » Ричарда Уорбертона.