Рубрики
Без рубрики

Устаревшие шаблоны Проектирования – Командный шаблон

Я уверен, что большинство из нас знакомы по крайней мере с некоторыми моделями, изложенными “Бандой четырех” в… Помеченный как java, архитектура, ооп.

Я уверен, что большинство из нас знакомы по крайней мере с некоторыми шаблонами, изложенными “Бандой четырех” в книге Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения . Эти шаблоны были невероятно полезны в языках OO, потому что они давали ответы на проблемы, в которых эти языки отчаянно нуждались.

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

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

В этой серии я буду рассматривать шаблоны, которые либо уже устарели, либо находятся в процессе становления таковыми.

Командный шаблон

Целью этого шаблона было инкапсулировать информацию о том, как выполнить метод в команде . Команда представляет собой интерфейс с одним абстрактным методом, обычно называемым выполнить типа () -> () . Этот метод фактически запускает метод, который команда “обертывает”.

Преимущества команд заключались в следующем:

  1. Возможность передачи в качестве значения первого класса, например, сохранение его в списках, передача другим функциям для выполнения на основе определенных условий и т.д.
  2. Разделение передачи параметров (в частности, в конструктор) и выполнения функции.
  3. Возможность группировать команды на основе общего интерфейса.
  4. Инкапсуляция информации о выполнении функции для уменьшения повторения на сайтах вызовов и отделения бизнес-логики от других задач, таких как представление или хранение данных.

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

Сначала мы рассмотрим пример реализации шаблона команд в Java 7. Этот пример в основном взят из GeeksforGeeks: Шаблон команды . Аналогичный пример также существует в Википедия: Шаблон команды . После этого мы проведем рефакторинг шаблона команд с использованием функций Java 8.

Код Java 7

Вот наши Сущности :

package Entities;

import Boundary.IO;

public class Light {
    private final IO io;

    public Light(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Light is on");
    }

    public void off() {
        io.show("Light is off");
    }
}
package Entities;

import Commands.Command;

public class RemoteControl {
    private Command button;

    public RemoteControl() { }

    public void setCommand(Command command) {
        button = command;
    }

    public void pressButton() {
        button.execute();
    }
}
package Entities;

import Boundary.IO;

public class Stereo {
    private final IO io;

    public Stereo(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Stereo is on");
    }

    public void off() {
        io.show("Stereo is off");
    }

    public void setCD() {
        io.show("Stereo is set for CD input");
    }

    public void setVolume(int volume) {
        io.show("Stereo volume set to " + volume);
    }
}

Глядя на вышесказанное, вам может быть любопытно, что такое IO . Ну, это просто простая зависимость, которую я определил в Граница чтобы не связывать мой код с PrintStream или более в частности System.out .

Код на самом деле относительно прост:

package Boundary;

public interface IO {
    void show(String str);
}

и единственная реализация, которая существует в этом приложении, – это эта:

package Presentation;

import Boundary.IO;

class ConsoleIO implements IO {
    @Override
    public void show(String str) {
        System.out.println(str);
    }
}

И, наконец, наши Команды :

package Commands;

public interface Command {
    void execute();
}
package Commands;

import Entities.Light;

public class LightOnCommand implements Command {
    private final Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }
}
package Commands;

import Entities.Light;

public class LightOffCommand implements Command {
    private final Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.off();
    }
}
package Commands;

import Entities.Stereo;

public class StereoOnWithCDCommand implements Command {
    private final Stereo stereo;

    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(10);
    }
}
package Commands;

import Entities.Stereo;

public class StereoOffCommand implements Command {
    private final Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.off();
    }
}

Это код, который мы можем использовать, чтобы увидеть вышеуказанную функциональность:

import Boundary.IO;
import Commands.*;
import Entities.*;
import Presentation.ConsoleIO;

class Main {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        IO io = new ConsoleIO();

        Light light = new Light(io);
        Stereo stereo = new Stereo(io);

        remote.setCommand(new LightOnCommand(light));
        remote.pressButton();
        remote.setCommand(new StereoOnWithCDCommand(stereo));
        remote.pressButton();
        remote.setCommand(new StereoOffCommand(stereo));
        remote.pressButton();
    }
}

Объяснение Java 7

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

Основная форма повторения заключается в таком коде (во всех командах ):

public class SomeClass implements Command {
  private final SomeType someType;

  public SomeClass(SomeType someType) {
    this.someType = someType;
  }

  public void execute() {
    someType.doStuff();
    // sometimes more calls on `someType` are here
  }
}

Мне нравится называть этот вид занятий прославленной функцией. Функция будет набрана как Некоторый тип -> () , т.е. она принимает некоторый тип и ничего не возвращает.

Он “прославлен” в контексте Java, потому что обладает некоторыми возможностями, которых нет у обычных функций. У вас есть возможность передать параметр в другое время после выполнения и передать эту частично применяемую функцию в качестве значения первого класса.

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

Однако, прежде чем мы углубимся в детали перезаписи, давайте на самом деле посмотрим на код Java 8.

Код Java 8

Сущности:

// Identical to Java 7 version
package Entities;

import Boundary.IO;

public class Light {
    private final IO io;

    public Light(IO io) {
        this.io = io;
    }

    public void on() {
        io.show("Light is on");
    }

    public void off() {
        io.show("Light is off");
    }
}
// This uses Runnable instead of Command now
package Entities;

public class RemoteControl {
    private Runnable button;

    public RemoteControl() { }

    public void setCommand(Runnable command) {
        button = command;
    }

    public void pressButton() {
        button.execute();
    }
}
/* 
  We added a new method here (onWithCD) to replace one of the commands 
  that used multiple methods on this entity.

  Then we made `off` public because that was just a 1:1 call 
  from a command previously.

  And lastly we made everything else private. So although we 
  added code to this class, we actually reduced the public API.
*/
package Entities

import Boundary.IO;

public class Stereo {
    private final IO io;

    public Stereo(IO io) {
        this.io = io;
    }

    public void onWithCD() {
        on();
        setCD();
        setVolume(10);
    }


    public void off() {
        io.show("Stereo is off");
    }

    private void on() {
        io.show("Stereo is on");
    }

    private void setCD() {
        io.show("Stereo is set for CD input");
    }

    private void setVolume(int volume) {
        io.show("Stereo volume set to " + volume);
    }

    private void setDVD() {
        io.show("Stereo is set for DVD input");
    }

    private void setRadio() {
        io.show("Stereo is set for Radio");
    }
}

ио:

// Identical to Java 7 version
package Boundary;

public interface IO {
    void show(String str);
}
// Identical to Java 7 version
package Presentation;

import Boundary.IO;

class ConsoleIO implements IO {
    @Override
    public void show(String str) {
        System.out.println(str);
    }
}

Главный:

// The method references used here instead of commands are talked about
// in the explanation below
import Boundary.IO;
import Entities.*;
import Presentation.ConsoleIO;

public class Main {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        IO io = new ConsoleIO();

        Light light = new Light(io);
        Stereo stereo = new Stereo(io);

        remote.setCommand(light::on);
        remote.pressButton();
        remote.setCommand(stereo::onWithCD);
        remote.pressButton();
        remote.setCommand(stereo::off);
        remote.pressButton();
    }
}

Объяснение Java 8

Так в то время как Стерео немного длиннее, вы, вероятно, заметили, что отсутствует целый, относительно большой раздел! Команды полностью исчезли, но у нас все еще есть код, который использует все исходные преимущества, перечисленные в верхней части этого поста. Я рассмотрю их один за другим и объясню, как мы все еще достигаем преимуществ шаблона команд без использования команд вообще.

1 . Возможность передачи в качестве значения первого класса, например, сохранение его в списках, передача другим функциям для выполнения на основе определенных условий и т.д.

Таким образом, хотя в Java отсутствуют функции первого класса, у нее есть класс Функция , а также другие аналогичные функциональные интерфейсы, которые могут быть реализованы с помощью лямбд или ссылок на методы. И этот функциональный интерфейс является первоклассным. Когда мы строим Запускаемый с помощью light::on , мы передаем ссылку на этот метод команде set , точно так же, как мы передали ссылку на различные команды в Java 7.

2 . Разделение передачи параметров (в частности, в конструктор) и выполнения функции.

На самом деле это то, на что я ссылался ранее, и мы не делали этого специально, потому что в этом не было необходимости. Единственное, что мы передавали в конструкторы Command , – это объекты, к которым они обращались (только один для любой заданной команды ).

Это легко возможно с помощью. Если бы нам нужно было передать параметры во время команды set , мы могли бы сделать это, выполнив функции, для которых требуются параметры (т.е. вернув, возможно, вложенный функциональный интерфейс из такого метода, как onWithCD или выключен ).

В качестве супер простого примера каррирования приведем добавление карри, обратите внимание, что тип сложный, но реализация проста, так что в будущем это может упростить вывод о большем типе:

Function> add = x -> y -> x + y;

3 . Возможность группировать команды на основе общего интерфейса, обычно буквально называемого Command .

Этот общий интерфейс в Java 8 является тем Функциональным интерфейсом s (Выполняемый, Функция, бифункция, Предикат и т.д.). Интерфейс с тем же типом является Запускаемый , поэтому мы использовали его в версии Java 8.

В языках с первоклассными функциями это просто функции.

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

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

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

Вывод

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

Важно отметить, что этот подход, основанный на функциях, проще в самой первой версии, это было возможно в Java (Java 8). Java – это прежде всего язык OO. Он был разработан с использованием классов в качестве основы для всех взаимодействий. Этот язык и другие подобные ему являются причиной того, что такого рода шаблоны были необходимы в первую очередь, и поскольку эти языки перенимают определенные аспекты функциональных языков, эти шаблоны OO устареют из-за функций.

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

Следите за обновлениями, чтобы узнать больше деталей из этой серии! Спасибо за чтение, и дайте мне знать, если у вас есть какие-либо шаблоны, которые вы хотите, чтобы я обсудил!

Оригинал: “https://dev.to/tylerwbrown/obsolete-design-patterns-command-pattern-4ijf”