Статьи

Шаблон стратегии

В недавнем блоге я получил комментарий от Wojciech Soczyjike о том, как шаблон «стратегии» может быть использован для обеспечения соблюдения принципа единой ответственности (SRP) при использовании принципа Tell Don’t Ask (TDA). В какой-то момент я планирую обсудить это дальше, но сначала подумал, что было бы неплохо определить шаблон стратегии, используя пример ShoppingCart, который я использовал пару недель назад в своем сообщении Tell Not Ask и его последующих действиях. Не спрашивайте блоги:

Сначала определение: в простейших терминах вы можете определить шаблон стратегии как указание объекту выполнять работу и делать это, используя ДРУГОЙ объект.

Чтобы пояснить это далее, я собираюсь немного изменить дизайн ShoppingCart, предоставив ему метод pay () *:

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
public class ShoppingCart {
 
  private final List<Item> items;
 
  public ShoppingCart() {
    items = new ArrayList<Item>();
  }
 
  public void addItem(Item item) {
 
    items.add(item);
  }
 
  public double calcTotalCost() {
 
    double total = 0.0;
    for (Item item : items) {
      total += item.getPrice();
    }
 
    return total;
  }
 
  public boolean pay(PaymentMethod method) {
 
    double totalCost = calcTotalCost();
    return method.pay(totalCost);
  }
}

Следует отметить, что метод pay () принимает один параметр типа PaymentMethod — это PaymentMethod, который является «ДРУГИМ» объектом в моем определении выше.

Следующее, что нужно сделать, это определить PaymentMethod как интерфейс. Почему интерфейс? Дело в том, что сила этого метода заключается в том, что вы можете во время выполнения решить, какой конкретный тип вы передадите в ShoppingCart для совершения платежа. Например, учитывая интерфейс оплаты:

1
2
3
4
5
public interface PaymentMethod {
 
  public boolean pay(double amount);
 
}

Затем вы можете определить любой конкретный платежный объект, такой как Visa или MasterCard, например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Visa implements PaymentMethod {
 
  private final String name;
  private final String cardNumber;
  private final Date expires;
 
  public Visa(String name, String cardNumber, Date expires) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
    this.expires = expires;
  }
 
  @Override
  public boolean pay(double amount) {
 
    // Open Comms to Visa
    // Verify connection
    // Paybill using these details
    return true; // if payment goes through
  }
 
}

…и

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MasterCard implements PaymentMethod {
 
  private final String name;
  private final String cardNumber;
  private final Date expires;
 
  public MasterCard(String name, String cardNumber, Date expires) {
    super();
    this.name = name;
    this.cardNumber = cardNumber;
    this.expires = expires;
  }
 
  @Override
  public boolean pay(double amount) {
 
    // Open Comms to Mastercard
    // Verify connection
    // Paybill using these details
    return true; // if payment goes through
  }
 
}

Последнее, что нужно сделать, это продемонстрировать это с помощью модульного теста: payBillUsingVisa

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
@Test
  public void payBillUsingVisa() {
 
    ShoppingCart instance = new ShoppingCart();
 
    Item a = new Item("gloves", 23.43);
    instance.addItem(a);
 
    Item b = new Item("hat", 10.99);
    instance.addItem(b);
 
    Date expiryDate = getCardExpireyDate();
    PaymentMethod visa = new Visa("CaptainDebug", "1234234534564567", expiryDate);
 
    boolean result = instance.pay(visa);
    assertTrue(result);
 
  }
 
  private Date getCardExpireyDate() {
    Calendar cal = Calendar.getInstance();
    cal.clear();
    cal.set(2015, Calendar.JANUARY, 21);
    return cal.getTime();
  }

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

И это определяет образец Стратегии, но это не конец блога. Если вы когда-либо использовали Spring, но никогда не слышали о шаблоне Strategy, все это должно показаться вам немного знакомым. Это потому, что оказывается, что ребята в Spring используют паттерн стратегии, чтобы поддержать всю свою технологию. Если я возьму свой пример выше и внесу несколько небольших изменений, я могу придумать:

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
@Component
public class SpringShoppingCart {
 
  private final List<Item> items;
 
  @Autowired
  @Qualifier("Visa")
  private PaymentMethod method;
 
  public SpringShoppingCart() {
    items = new ArrayList<Item>();
  }
 
  public void addItem(Item item) {
 
    items.add(item);
  }
 
  public double calcTotalCost() {
 
    double total = 0.0;
    for (Item item : items) {
      total += item.getPrice();
    }
 
    return total;
  }
 
  public boolean pay() {
 
    double totalCost = calcTotalCost();
    return method.pay(totalCost);
  }
}

Единственное различие между этим воплощением и первым заключается в том, что класс стратегии Visa внедряется Spring, когда класс загружается с использованием аннотации @Autowired. Подводя итог, я предполагаю, что это означает, что шаблон стратегии является самой популярной моделью в мире.

* Для целей этого обсуждения я предполагаю, что ShoppingCart может окупить себя, но это правильно или нет, это совершенно новый блог…

Ссылка: шаблон стратегии от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .