Государственный шаблон проектирования в Java

Государственный шаблон проектирования в Java

1. обзор

В этом уроке мы познакомимся с одним из поведенческих шаблонов проектирования GoF - шаблоном State.

Сначала мы дадим обзор его цели и объясним проблему, которую он пытается решить. Затем мы рассмотрим диаграмму UML штата и реализацию практического примера.

2. Государственный дизайн шаблона

Основная идея шаблона состояния заключается вallow the object for changing its behavior without changing its class. . Кроме того, при его реализации код должен оставаться более чистым без множества операторов if / else.

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

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

Кроме того, вся логика для каждого из состояний будет распространяться на все методы. Теперь это то место, где можно использовать шаблон State. Благодаря шаблону проектирования State мы можем инкапсулировать логику в выделенные классы, применять более чистый и удобный в сопровождении кодSingle Responsibility Principle иOpen/Closed Principle, .

3. Диаграмма UML

UML diagram of state design pattern

На диаграмме UML мы видим, что с классомContext связанState, который будет изменяться во время выполнения программы.

Наш контекст будетdelegate the behavior to the state implementation.. Другими словами, все входящие запросы будут обрабатываться конкретной реализацией состояния.

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

4. Реализация

Давайте спроектируем наше приложение. Как уже упоминалось, пакет можно заказать, доставить и получить, поэтому у нас будет три состояния и класс контекста.

Во-первых, давайте определим наш контекст, это будет классPackage:

public class Package {

    private PackageState state = new OrderedState();

    // getter, setter

    public void previousState() {
        state.prev(this);
    }

    public void nextState() {
        state.next(this);
    }

    public void printStatus() {
        state.printStatus();
    }
}

Как мы видим, он содержит ссылку для управления состоянием, обратите внимание на методыpreviousState(), nextState() and printStatus(), в которых мы делегируем задание объекту состояния. Состояния будут связаны друг с другом, иevery state will set another one based on this reference будет передан обоим методам.

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

Затем у нас будетPackageState, который имеет три метода со следующими сигнатурами:

public interface PackageState {

    void next(Package pkg);
    void prev(Package pkg);
    void printStatus();
}

Этот интерфейс будет реализован каждым конкретным классом состояний.

Первое конкретное состояние будетOrderedState:

public class OrderedState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new DeliveredState());
    }

    @Override
    public void prev(Package pkg) {
        System.out.println("The package is in its root state.");
    }

    @Override
    public void printStatus() {
        System.out.println("Package ordered, not delivered to the office yet.");
    }
}

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

Давайте посмотрим на классDeliveredState:

public class DeliveredState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new ReceivedState());
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new OrderedState());
    }

    @Override
    public void printStatus() {
        System.out.println("Package delivered to post office, not received yet.");
    }
}

Опять же, мы видим связь между государствами. Пакет меняет свое состояние с заказанного на доставленное, также изменяется сообщение вprintStatus().

Последний статусReceivedState:

public class ReceivedState implements PackageState {

    @Override
    public void next(Package pkg) {
        System.out.println("This package is already received by a client.");
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new DeliveredState());
    }
}

Здесь мы достигаем последнего состояния, мы можем только вернуться к предыдущему состоянию.

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

5. тестирование

Посмотрим, как себя ведет реализация. Во-первых, давайте проверим, правильно ли работают переходы настройки:

@Test
public void givenNewPackage_whenPackageReceived_thenStateReceived() {
    Package pkg = new Package();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(DeliveredState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(ReceivedState.class));
}

Затем быстро проверьте, может ли ваш пакет вернуться назад с его состоянием:

@Test
public void givenDeliveredPackage_whenPrevState_thenStateOrdered() {
    Package pkg = new Package();
    pkg.setState(new DeliveredState());
    pkg.previousState();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
}

После этого давайте проверим изменение состояния и посмотрим, как реализация методаprintStatus() меняет свою реализацию во время выполнения:

public class StateDemo {

    public static void main(String[] args) {

        Package pkg = new Package();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();
    }
}

Это даст нам следующий результат:

Package ordered, not delivered to the office yet.
Package delivered to post office, not received yet.
Package was received by client.
This package is already received by a client.
Package was received by client.

Поскольку мы меняли состояние нашего контекста, поведение менялось, но класс остался прежним. Как и API, которым мы пользуемся.

Кроме того, произошел переход между состояниями, наш класс изменил свое состояние и, как следствие, свое поведение.

6. Downsides

Недостатком структуры состояний является отдача при реализации перехода между состояниями. Это делает государство жестко закодированным, что является плохой практикой в ​​целом.

Но, в зависимости от наших потребностей и требований, это может быть или не быть проблемой.

7. Государство против Шаблон стратегии

Оба шаблона проектирования очень похожи, но их UML-диаграмма одинакова, а идея немного отличается.

Во-первых,strategy pattern defines a family of interchangeable algorithms. Как правило, они достигают той же цели, но с другой реализацией, например, алгоритмы сортировки или рендеринга.

In state pattern, the behavior might change completely, исходя из фактического состояния.

Затемin strategy, the client has to be aware of the possible strategies to use and change them explicitly. В то время как в шаблоне состояний каждое состояние связано с другим и создает поток, как в конечном автомате.

8. Заключение

Шаблон проектирования состояний хорош, когда мы хотимavoid primitive if/else statements. Вместо этого мыextract the logic to separate classes и передаем нашcontext object delegate the behavior методам, реализованным в классе состояния. Кроме того, мы можем использовать переходы между состояниями, где одно состояние может изменять состояние контекста.

В общем, этот шаблон проектирования отлично подходит для относительно простых приложений, но для более продвинутого подхода мы можем взглянуть наSpring’s State Machine tutorial.

Как обычно, полный код доступен наthe GitHub project.