вторник, 3 ноября 2009 г.

CQS – как вам сущности без публичных свойств?

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

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

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

class Order : RootAggregate
{
    readonly IList _lines = new List();
    readonly DateTime _createdDate;
    
    decimal _totalAmount;

    public Order(DateTime createdDate)
    {
        _createdDate = createdDate;
    }

    public void AddProduct(string productName, int quantity, decimal price)
    {
        _lines.Add(new OrderLine(productName, quantity, price));
        UpdateTotal();
    }

    private void UpdateTotal()
    {
        _totalAmount = _lines.Sum(x => x.Price*x.Quantity);
    }
}

public class OrderLine : ValueObject
{
    public string ProductName { get; private set; }
    public int Quantity { get; private set; }
    public decimal Price { get; private set; }

    public OrderLine(string productName, int quantity, decimal price)
    {
        ProductName = productName;
        Quantity = quantity;
        Price = price;
    }
}

Главная особенность приведенного кода в том, что снаружи доступ к состоянию агрегата закрыт. Т.е. вообще никаких шансов узнать из чего состоит выбранный заказ. Да и не нужно этого — ведь для оценки состояния модели у нас есть отчеты. А в модели поведения возможно только управление поведением.

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

var orderIsApplicableForDiscountSpecification = 
    new OrderIsApplicableForDiscountSpecification();

if (orderIsApplicableForDiscountSpecification.Matches(order))
    order.ApplyDiscount(new StandardDiscount());

где сама спецификация реализована таким образом:

class OrderIsApplicableForDiscountSpecification
{
    public bool Matches(Order order)
    {
        return order.TotalAmount >= 10000;
    }
}

Таким образом появляется и свойство TotalAmount у заказа.

На самом деле, никакой мистики здесь нет — все вышепоказанное ни что иное, как обычная банальная инкапсуляция. Просто в свете применения принципа CQS 80% случаев использования информации о состоянии модели просто уходят в небытие и вместе с этим уходит вся необходимая инфраструктура в виде публичных свойств.

Опубликовать

Комментариев нет:

Отправить комментарий