Статьи

Друзья не позволяют друзьям использовать строковые состояния

Многие из моих коллег рассказали о новых захватывающих технологиях и инфраструктурах, которые помогут вам в вашем программировании. Но в настоящее время я думаю, что важно вернуться назад и охватить важные основы программирования. На протяжении всей моей карьеры программиста одним из самых распространенных анти-паттернов, которые я видел, является «Строковое состояние». Он появился в той или иной форме в каждом проекте, над которым я работал. Проще говоря, анти-шаблон String State — это использование строки Java для представления состояния объекта. Конечно, строковые представления состояний важны. Вы хотите, чтобы код был читабельным, вы хотите, чтобы в отчетах было доступно читаемое значение для отчетов, и вы хотите, чтобы читаемое представление состояния отображалось на вашем интерфейсе. Все это хорошие вещи, и у вас должно быть некоторое строковое представление вашего состояния, но не допускайте, чтобы string была полной реализацией вашего состояния. Возьмем, к примеру, эту упрощенную версию корзины для покупок:

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
package stringstate;
 
public class Item {
    private String name;
    private int price;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
}
 
package stringstate;
 
public interface OrderState {
    public static String OPEN = "Open";
    public static String CLOSED = "Closed";
}
 
package stringstate;
 
import java.util.ArrayList;
import java.util.List;
 
public class Order extends Item implements OrderState {
    private List items = new ArrayList();
    private String orderState;
 
    public List getItems() {
        return items;
    }
 
    protected  void setItems(List items) {
        this.items = items;
    }
 
    public String getOrderState() {
        return orderState;
    }
 
    public void setOrderState(String orderState) {
        this.orderState = orderState;
    }
 
    public void addItem(Item anItem){
        getItems().add(anItem);
    }
 
}

В этом примере состояние используется без бизнес-логики. В этом случае состояние строки не будет вредным. Однако ни один проект не является таким простым. Даже если состояние начинается только как отображение / сохранение, вы в конечном итоге будете использовать его для бизнес-логики. Поверьте мне — если состояние объекта достаточно важно для хранения, то в конечном итоге вам придется принимать решения с ним. Так что теперь ваша упрощенная корзина должна запретить людям добавлять товары, если она закрыта. Легко: добавить новый метод и изменить addItem .

01
02
03
04
05
06
07
08
09
10
11
public void addItem(Item anItem){
        if (canModify()){
            getItems().add(anItem);
        } else {
            throw new IllegalStateException("Cannot add items to a closed order");
        }
    }
 
    public boolean canModify(){
        return getOrderState().equals(OPEN);
    }

Пока что не все так плохо. Вы спрашиваете, почему я так расстроен? Потому что теперь вы хотите добавить 2 новых состояния New и Finalized, а также дать пользователю возможность повторно открыть не завершенный заказ. Конечно, новый метод, модификация canModify , проверка setOrderState, и мы уже в пути. Но подождите, теперь пользователь хочет 2 завершенных состояния: отправлено и отменено . Еще несколько настроек, верно? Теперь ваш код заказа изобилует словами «if», касающимися состояний:

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
package stringstate;
 
import java.util.ArrayList;
import java.util.List;
 
public class Order extends Item implements OrderState {
    private List items = new ArrayList();
    private String orderState;
 
    public List getItems() {
        return items;
    }
 
    protected  void setItems(List items) {
        this.items = items;
    }
 
    public String getOrderState() {
        return orderState;
    }
 
    public void setOrderState(String orderState) {
        if (!isFinalized()){
            this.orderState = orderState;
        }
    }
 
    public void addItem(Item anItem){
        if (canModify()){
            getItems().add(anItem);
        } else {
            throw new IllegalStateException("Cannot add items to a closed order");
        }
    }
 
    public boolean canModify(){
        return getOrderState().equals(OPEN);
    }
 
    public void reOpen(){
        if (!isFinalized()){
            setOrderState(OPEN);
        }
    }
 
    public boolean isFinalized(){
        return getOrderState().equals(SHIPPED) || getOrderState().equals(CANCELED);
    }
}

К сожалению, мы забыли добавить новое состояние в canModify () . Теперь возьмем пример состояния стратегии, в котором вся логика состояния делегирована объекту, умнее строки. Первое преимущество — сделать всю логику в заказе о заказе, а не о состоянии заказа. Следующее преимущество заключается в том, что каждый раз, когда вы добавляете новое решение, основанное на состоянии, вы можете применять его абстрактно для принудительной реализации во всех состояниях или для обеспечения реализации по умолчанию. Порядок:

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
67
68
69
package strategystate;
 
import java.util.ArrayList;
import java.util.List;
 
public class Order {
    private List items = new ArrayList();
    private OrderState orderState;
 
    public List getItems() {
        return items;
    }
 
    protected  void setItems(List items) {
        this.items = items;
    }
 
    public OrderState getOrderState() {
        return orderState;
    }
 
    public void setOrderState(OrderState orderState) {
        if (!getOrderState().isFinalized()){
            this.orderState = orderState;
        }
    }
 
    public void addItem(Item anItem){
        if (getOrderState().canModify()){
            getItems().add(anItem);
        } else {
            throw new IllegalStateException("Cannot add items to a closed order");
        }
    }
 
    public void reOpen(){
        if (!getOrderState().isFinalized()){
            setOrderState(OrderState.OPEN);
        }
    }
 
}
 
public enum OrderState {
    NEW("New",true,false),
    OPEN("Open",true,false),
    CLOSED("Closed",false,false),
    SHIPPED("Shipped",false,true),
    CANCELED("Canceled",false,true);
 
    protected String name;
    protected boolean finalized;
    protected boolean modify;
 
    private OrderState(String name, boolean canModify, boolean isFinalized){
        this.name = name;
        this.finalized= isFinalized;
        this.modify = canModify;
    }
    public boolean canModify(){
        return modify;
    }
    public boolean isFinalized(){
        return finalized;
    }
    public String getName(){
        return name;
    }
}

Чем больше состояний и больше логики у вас основано на состоянии, тем больше вы получаете выгоды от состояния стратегии. Стратегия State сохраняет ваши основные бизнес-объекты свободными от растущих «операторов if» на основе списков состояний. Кроме того, вы можете сохранить все удобства строки. Различие может быть небольшим в этом примере, но с ростом сложности домена увеличивается и преимущество состояния стратегии. Кроме того, над каким проектом вы когда-либо работали, где сложность не росла? Я рекомендую всегда начинать с состояния стратегии. Накладные расходы невелики, и вы будете благодарны, когда сложность проекта возрастет.