Статьи

Разборка «Скажи, не спрашивай» — внутреннее состояние объекта


В
моем последнем блоге я определил «
Скажите не спрашивай» (TDA), используя простой пример корзины. В нем корзина была ответственна за определение общей стоимости товаров в корзине, в отличие от того, что клиент запрашивал список товаров, а затем вычислял саму общую стоимость. Пример TDA показан ниже:

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;
  }
}

… и это тестовый пример:

  @Test
  public void calculateTotalCost() {

    ShoppingCart instance = new ShoppingCart();

    Item a = new Item("gloves", 23.43);
    instance.addItem(a);

    Item b = new Item("hat", 10.99);
    instance.addItem(b);

    Item c = new Item("scarf", 5.99);
    instance.addItem(c);

    double totalCost = instance.calcTotalCost();
    assertEquals(40.41, totalCost, 0.0001);
  }

Я должен спросить, хотя, TDA все это сводится к материальному языку и семантике. Рассмотрите пример выше, клиент говорит или спрашивает? Хотя этот код гораздо лучше, чем мой
пример запроса «
не говори» , я думаю, что можно сказать, что клиент запрашивает полную цену. Рассмотрим следующие вопросы:

  • Говоря корзине покупок, чтобы она возвращала общую цену, откуда вы знаете, что не запрашиваете внутреннее состояние объекта? Глядя на код ShoppingCart, вы видите, что общая стоимость не является частью прямого внутреннего состояния объекта 1 , но объект вычисляет его, и, следовательно, общая цена является частью производного внутреннего состояния объекта, и эта сумма возвращается вызывающей стороне. ,
  • В мире TDA, зачем клиенту нужна полная стоимость? Чтобы выяснить, нужно ли добавить стоимость доставки? Это можно сделать с помощью ShoppingCart. Подать счет в соответствующую платежную систему? Опять же, это можно сделать с помощью корзины.

Если вы согласны с тем, что возвращаемые значения отражают внутреннее состояние объекта, как прямого, так и логического, то, как сказал бы г-н Спок, логика подсказывает, что вам придется заключить, что все сигнатуры методов будут иметь возвращаемое значение void, никогда не выбрасывать исключения и обрабатывать все ошибки сами и выглядеть примерно так:

  public void processOrder(String arg1, int arg2);

И вот тут часть логики может начать разгадываться. Не нарушает ли ShoppingCart, связывающийся с платежной системой Visa или Mastercard, принцип единой ответственности (SRP)? WareHouse также должен быть проинформирован о том, что заказ может быть отправлен. Это ответственность ShoppingCart? А что, если мы хотим распечатать подробный счет в формате PDF? Вы можете указать ShoppingCart печатать себя, но мы нарушаем SRP, добавляя код печати в ShoppingCart, каким бы маленьким он ни был? Возможно, это требует дальнейших размышлений, поэтому взгляните на схему коммуникации ниже:

На этой диаграмме показан простой сценарий добавления товара в ShoppingCart. TDA прекрасно работает здесь, потому что нет веток и нет решений, весь процесс линейен: браузер СКАЖИТ OrderController, чтобы добавить элемент, и OrderController СКАЖЕТ ShoppingCart, чтобы добавить элемент к себе.

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

Если вы посмотрите на диаграмму, вы увидите, что браузер СКАЖИТ контролеру OrderController для подтверждения заказа. OrderController сообщает корзину покупок для подтверждения заказа. ShoppingCart РАССКАЗЫВАЕТ систему Visa, чтобы списать сумму с карты пользователя. Если оплата прошла успешно, карта Visa СКАЖЕТ WareHouse, чтобы отправить заказ.

Теперь это работает как дизайн, если вы принимаете, что ShoppingCart отвечает за ведение списка товаров, которые хочет получить пользователь, и оплату этих товаров. Вы также должны согласиться с тем, что карта Visa отвечает за доставку их клиенту: все это не имеет смысла для меня, поскольку это нарушает SRP, и, на мой взгляд, SRP является одной из фундаментальных особенностей хороший дизайн программного обеспечения. Кроме того, когда я делаю покупки в супермаркете, я не прошу свою корзину для покупок оплатить счет, я должен вытащить свой кошелек. Если вы продолжите эту аналогию дальше, то придете к выводу, что ответственность за маршалинг потока транзакции лежит на другом объекте; Возможно, OrderController?

На этой финальной диаграмме вы можете видеть, что браузер СКАЖИТ контролеру OrderController для подтверждения заказа. OrderController ЗАПРОСИТ ShoppingCart на общую стоимость, а затем СКАЖИТ объект Visa, чтобы оплатить счет, и, наконец, если Visa возвращает успех, он ОБРАЩАЕТСЯ в WareHouse, чтобы отправить ShoppingCart. Для меня этот дизайн имеет больше смысла, он говорит, не спрашивайте с оттенком прагматизма.

Теперь не поймите меня неправильно, мне нравится идея
сказать, не спрашивайтеэто имеет смысл, но не идеально и может споткнуться. Если вы ищете в Интернете примеры, вы часто обнаруживаете, что они линейны по своей природе, отражая первые две диаграммы выше, где A вызывает B, B вызывает C, C вызывает D и т. Д. Это не отражает большинство приложений, как в какой-то момент При выполнении ваших программ вы вынуждены запрашивать данные у объекта и принимать решение на основе этих данных. Вторая причина, по которой
вы не спрашиваете спотыканий, заключается в том, что попасть в ловушку запроса объекта для данных так легко, даже не задумываясь Например, взгляните на фрагмент ниже:

      AnnotationChecker checker = new AnnotationChecker(clazz);
      if (checker.isMyComponent()) {

        Object obj = clazz.newInstance();
        result.put(clazz, obj);
      }

Этот пример, взятый из моего примера проекта github dependency-инъекция-фабрика, показывает, как я спрашиваю объект о его состоянии и использую эту информацию для принятия решения. Процедурное программирование поражает снова …

__________________________________________


1 Прямое внутреннее состояние: значение, хранящееся в переменной экземпляра объекта. Производное или предполагаемое внутреннее состояние: значение, возвращаемое объектом, которое вычисляется или получается из переменных экземпляра этого объекта.