Конкретные примеры того, почему "модель анемического домена" считается анти-шаблоном [закрыто]

Я прошу прощения, если это дубликат, но я не смог найти ни одного конкретного примера по данной теме вопросы.

после прочтения статья Мартина Фаулера о "модели анемического домена", я остался блуждать, почему это считается анти-шаблоном. Даже большинство корпоративных разработчиков считают это анти-шаблоном, поскольку AFAIK, вероятно, 90% приложений j2ee разработаны "анемичным" способом ?

может кто-нибудь порекомендовать дальше чтение по этой теме (кроме книги "доменный дизайн") или даже лучше, приведите конкретные примеры того, как этот анти-шаблон плохо влияет на дизайн приложений.

спасибо,

6 ответов


учитывая следующие два класса:

class CalculatorBean  
{  
    //getters and setters  
}  

class CalculatorBeanService  
{  
   Number calculate(Number first, Number second);  
    {  
       //do calculation  
    }  
} 

если я правильно понимаю, Фаулер утверждает, что потому что ваш CalculatorBean - это просто куча геттеров / сеттеров, от которых вы не получаете никакого реального значения, и если вы перенесете этот объект в другую систему, он ничего не сделает. Проблема, кажется, что ваш CalculatorBeanService содержит все, что CalculatorBean должен нести ответственность за. Который не самый лучший, как сейчас CalculatorBean делегирует всю свою ответственность CalculatorBeanService


для полного ответа взгляните на мой блог, который также содержит примеры исходного кода [блог]: https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

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

объектно-ориентированный означает, что: объект управляет своим состоянием и гарантирует, что он имеет в правовом государстве в любое время. (скрытие данных, инкапсуляция)

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

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

анемичная модель

 public class Order {
    private BigDecimal total = BigDecimal.ZERO;
    private List<OrderItem> items = new ArrayList<OrderItem>();

    public BigDecimal getTotal() {
        return total;
    }

    public void setTotal(BigDecimal total) {
        this.total = total;
    }

    public List<OrderItem> getItems() {
        return items;
    }

    public void setItems(List<OrderItem> items) {
        this.items = items;
    }
}

public class OrderItem {

    private BigDecimal price = BigDecimal.ZERO;
    private int quantity;
    private String name;

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
}

Итак, где находится логика, которая интерпретирует порядок и элементы порядка для вычисления общей суммы заказа? Эта логика часто помещается в классы с именем * Helper, *Util, *Manager или просто *Service. Доставка заказа в анемичной модели будет выглядеть следующим образом:

public class OrderService {
    public void calculateTotal(Order order) {
        if (order == null) {
             throw new IllegalArgumentException("order must not be null");
        }

        BigDecimal total = BigDecimal.ZERO;
        List<OrderItem> items = order.getItems();

        for (OrderItem orderItem : items) {
            int quantity = orderItem.getQuantity();
            BigDecimal price = orderItem.getPrice();
            BigDecimal itemTotal = price.multiply(new BigDecimal(quantity));
            total = total.add(itemTotal);
        }
        order.setTotal(total);
    }
}

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

иногда вы увидите немного другую реализацию службы, которая не изменяет анемичную модель. Вместо этого он возвращает вычисляемое значение. Е. Г.

public BigDecimal calculateTotal(Order order); 

в этом случае Order не имеет свойства total. Если вы сейчас сделаете Order неизменяемые вы находитесь на пути к функциональной программирование. Но это еще одна тема, которую я не могу найти здесь.

проблемы с анемичной моделью порядка выше:

  • если кто-то добавляет OrderItem к порядку Order.getTotal() значение неверно, если оно не было пересчитано OrderService. В реальном мире приложение может быть громоздким, чтобы узнать, кто добавил элемент заказа и почему OrderService не был вызван. Как вы, возможно, уже поняли, порядок также ломается заключение список позиций заказа. Кто-то может позвонить order.getItems().add(orderItem) для добавления элемента заказа. Это может затруднить поиск кода, который действительно добавляет элемент (order.getItems() ссылка может быть передана через все приложение).
  • на OrderService ' s calculateTotalметод отвечает за вычисление итога для всех объектов заказа. Поэтому она должна быть безгосударственной. Но без состояния также означает, что он не может кэшировать общее значение и только пересчитать его, если объект Order изменился. Так если метод calculateTotal занимает много времени, у вас также есть проблема с производительностью. Тем не менее, у вас будут проблемы с производительностью, потому что клиенты могут не знать, находится ли заказ в законном состоянии или нет, и поэтому предупредительно звоните calculateTotal(..) даже когда это не нужно.

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

public class OrderService {
    public BigDecimal calculateTotal(Order order) {
        if (order == null) {
             throw new IllegalArgumentException("order must not be null");
        }

        BigDecimal total = BigDecimal.ZERO;
        List<OrderItem> items = order.getItems();

        for (OrderItem orderItem : items) {
            int quantity = orderItem.getQuantity();
            BigDecimal price = orderItem.getPrice();
            BigDecimal itemTotal = price.multiply(new BigDecimal(quantity));
            total = total.add(itemTotal);
        }
       return total;
    }
}

в этом случае службы интерпретируют состояние анемии моделируйте в некоторое время и не обновляйте анемичную модель с результатом. Единственным преимуществом этого подхода является то, что анемичная модель не может содержать недопустимый total состояние, потому что у него не будет total собственность. Но это также означает, что total должен быть рассчитан каждый раз, когда это необходимо. Путем удаления total свойство вы заставляете разработчиков использовать сервис и не полагаться на total'государственной собственности. Но это не гарантирует, что разработчики кэшируют total значение в некотором роде и, следовательно, они могут также использовать устаревшие значения. Этот способ реализации службы может быть выполнен всякий раз, когда свойство является производным от другого свойства. Или другими словами... при интерпретации основных данных. Е. Г. int getAge(Date birthday).

теперь взгляните на богатую модель домена, чтобы увидеть разницу.

богатый домен подход

public class Order {

    private BigDecimal total;
    private List<OrderItem> items = new ArrayList<OrderItem>();

    /**
      * The total is defined as the sum of all {@link OrderItem#getTotal()}.
      *
      * @return the total of this {@link Order}.
      */
    public BigDecimal getTotal() {
        if (total == null) {
           /*
            * we have to calculate the total and remember the result
            */
           BigDecimal orderItemTotal = BigDecimal.ZERO;
           List<OrderItem> items = getItems();

           for (OrderItem orderItem : items) {
               BigDecimal itemTotal = orderItem.getTotal();
               /*
                * add the total of an OrderItem to our total.
                */
               orderItemTotal = orderItemTotal.add(itemTotal);
           }

           this.total = orderItemTotal;
           }
        return total;
        }

   /**
    * Adds the {@link OrderItem} to this {@link Order}.
    *
    * @param orderItem
    *            the {@link OrderItem} to add. Must not be null.
    */
    public void addItem(OrderItem orderItem) {
        if (orderItem == null) {
            throw new IllegalArgumentException("orderItem must not be null");
        }
        if (this.items.add(orderItem)) {
           /*
            * the list of order items changed so we reset the total field to
            * let getTotal re-calculate the total.
            */ 
            this.total = null;
        }
    }

    /**
      *
      * @return the {@link OrderItem} that belong to this {@link Order}. Clients
      *         may not modify the returned {@link List}. Use
      *         {@link #addItem(OrderItem)} instead.
      */
    public List<OrderItem> getItems() {
       /*
        * we wrap our items to prevent clients from manipulating our internal
        * state.
        */
        return Collections.unmodifiableList(items);
    }

}

public class OrderItem {

    private BigDecimal price;

    private int quantity;

    private String name = "no name";

    public OrderItem(BigDecimal price, int quantity, String name) {
     if (price == null) {
      throw new IllegalArgumentException("price must not be null");
     }
     if (name == null) {
      throw new IllegalArgumentException("name must not be null");
     }
     if (price.compareTo(BigDecimal.ZERO) < 0) {
      throw new IllegalArgumentException(
        "price must be a positive big decimal");
     }
     if (quantity < 1) {
      throw new IllegalArgumentException("quantity must be 1 or greater");
     }
     this.price = price;
     this.quantity = quantity;
     this.name = name;
    }

    public BigDecimal getPrice() {
     return price;
    }

    public int getQuantity() {
     return quantity;
    }

    public String getName() {
     return name;
    }

    /**
      * The total is defined as the {@link #getPrice()} multiplied with the
      * {@link #getQuantity()}.
      *
      * @return
      */
    public BigDecimal getTotal() {
     int quantity = getQuantity();
      BigDecimal price = getPrice();
      BigDecimal total = price.multiply(new BigDecimal(quantity));
     return total;
    }
}

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

ссылки


Мартин Фаулер приносит этой отрасли много слов и меньше понимания.

большинству приложений сегодня (web/db) нужно много объектов, которые раскрывают свои свойства.

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

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


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

struct SomeStruct {
    int x;
    float y;
};

void some_op_i(SomeStruct* s, int x) {
    // do something
}
void some_op_f(SomeStruct* s, float y) {
    // something else
}

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

когда появился c++, структура стала классом, и она позволяет вам помещать функции в структуру (класс). Затем структура неявно передается как указатель this, поэтому вместо создания структуры и передачи ее функциям вы создаете класс и вызываете методы против него. Код более ясен и понятен таким образом.

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


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

но есть много случаев, когда разработчики пытаются построить модель домена, ака делать DDD, и в конечном итоге с анемичным режимом домена вместо этого. Я думаю, что в этом случае модель анемического домена считается анти-patern.

просто убедитесь, что вы использовать лучший инструмент для работы и если он работает для вас, не портили он.


Это просто нарушает "скажи, Не спрашивай" принцип, который гласит, что объекты должны сообщить клиенту, что они могут или не могут сделать, а не подвергать свойства и оставлять его клиенту, чтобы определить, находится ли объект в определенном состоянии для данного действия.