itmo_conspects

Лекция 4. Шаблоны GRASP

Шаблоны GRASP (General Responsibility Assignment Software Principles, Общие принципы распределения ответственности в ПО) - шаблоны, которые решают задачи назначения ответственности программных компонентов

Шаблоны были описаны Крэгом Ларманом в книге “Применение UML и шаблонов проектирования” в 1997 году. Каждый шаблон помогает решить конкретную задачу, возникающую при проектировании ПО, всего их девять:

  1. Информационный эксперт (Information Expert)
  2. Создатель (Creator)
  3. Контроллер (Controller)
  4. Слабое зацепление (Low Coupling)
  5. Сильная связность (High Cohesion)
  6. Перенаправление (Indirection)
  7. Полиморфизм (Polymorphism)
  8. Устойчивость к изменениям (Protected Variations)
  9. Чистая выдумка (Pure Fabrication)

Информационный эксперт

Информационный эксперт (Information Expert) - это принцип, согласно которому обязанность (то есть действие с данными) назначается классу, который обладает всей необходимой информацией для её выполнения

Приведем пример заказа и создателя чека:

public record OrderItem(int Id, decimal Price, int Quantity);

public class Order
{
    private readonly List<OrderItem> _items;

    public Order()
    {
        _items = new List<OrderItem>();
    }

    public IReadOnlyCollection<OrderItem> Items => _items;
}

public record Receipt(decimal TotalCost, DateTime Timestamp);

public class ReceiptService
{
    public Receipt CalculateReceipt(Order customer)
    {
        var totalCost = customer.Items
            .Sum(order => order.Price * order.Quantity);
        var timestamp = DateTime.Now;
        return new Receipt(totalCost, timestamp);
    }
}

В этом примере при составлении чека мы подсчитываем стоимость позиции заказа order => order.Price * order.Quantity внутри сервиса ReceiptService

Здесь нет информационного эксперта (обязанность расчета суммы переложена на ReceiptService, а не на тех, кто обладает информацией), и из-за этого появляются проблемы:

Исправим пример:

public record OrderItem(int Id, decimal Price, int Quantity)
{
    public decimal Cost => Price * Quantity;
}

public class Order
{
    private readonly List<OrderItem> _items;
    public Order()
    {
        _items = new List<OrderItem>();
    }
    public IReadOnlyCollection<OrderItem> Items => _items;
    public decimal TotalCost => _items.Sum(x => x.Cost);
}

public record Receipt(decimal TotalCost, DateTime Timestamp);

public class ReceiptService
{
    public Receipt CalculateReceipt(Order customer)
    {
        var totalCost = customer.TotalCost;
        var timestamp = DateTime.Now;
        return new Receipt(totalCost, timestamp);
    }
}

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

Создатель

Создатель (Creator) - шаблон, который решает проблему ответственности за создание используемых объектов. Если объект класса B содержит, активно использует объекты класса A, а также обладает всеми данными для их создания, то объект класса B должен быть создателем объектов класса A

Приведем пример: здесь сервис заказа OrderService создает позицию заказа OrderItem, которая передается в объект заказа Order

public class Order
{
    private readonly List<OrderItem> _items;

    public Order AddItem(OrderItem item)
    {
        _items.Add(item);
        return this;
    }
}

public class OrderService
{
    public Order CreateDefaultOrder()
    {
        var order = new Order()
            .AddItem(new OrderItem(1, 100, 1))
            .AddItem(new OrderItem(2, 1000, 3));
        return order;
    }
}

Можно это исправить так: передадим аргументы к позиции заказа, чтобы в объекте заказа он создавался

public class Order
{
    private readonly List<OrderItem> _items;

    public Order AddItem(int id, decimal price, int quantity)
    {
        _items.Add(new OrderItem(id, price, quantity));
        return this;
    }
}

public class OrderService
{
    public Order CreateDefaultOrder()
    {
    var order = new Order()
        .AddItem(1, 100, 1)
        .AddItem(2, 1000, 3);
    return order;
    }
}

Недостатки создателя:

Контроллер

Контроллер (Controller) - переходник между моделями бизнес-логики и моделями представления. Контроллер помогают пользователю через представление (то есть пользовательский интерфейс) совершать некоторые действия - их называют сценариями использования (Use Case) - над объектами данных

Сам контроллер не выполняет действия, он их делегирует конкретным исполнителям (их чаще всего называются сервисами)

Различают 3 вида контроллеров:

Слабое зацепление

Зацеплением (Coupling) считается мера зависимости разных модулей друг между другом

Слабое зацепление (Low Coupling) - принцип, согласно которому класс должен иметь как можно меньше зависимостей от других классов. Чем меньше класс знает о внутреннем устройстве других, тем проще его переиспользовать, тестировать и модифицировать

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

Например: есть класс DataProvider, методы которого выводят температуру воздуха в комнате и используемую сборщиком мусора память в чипе микроконтроллера. Логически эти данные никак не связаны, поэтому лучше всего ослабить их зацепление - создать 2 отдельных класса для вывода температуры и для вывода памяти

Сильная связность

Связность (Cohesion) - это мера логической соотнесенности логики в рамках модуля

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

Низкая связность (Low cohesion) возникает, когда класс содержит несвязанные друг с другом обязанности

Например: сделаем класс DataMonitor, который отображает нужную метрику от DataProvider в зависимости от переданного enum MetricType; так как мы работаем с перечислением, то не избежать использования switch, а значит код будет трудно расширять - нарушается принцип открытости/закрытости

В этом случае лучше будет создать интерфейс для DataProvider, реализации которого будут использоваться в DataMonitor

Перенаправление

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

Перенаправление тесно связано с принципами разделения интерфейса и инверсии зависимостей. Принцип перенаправления используется в архитектуре Модель-Представление-Контроллер (Model-View-Controller, MVC): бизнес-логика из модели общается с сущностями представления через посредник-контроллер

Из-за этого повышается гибкость и тестируемость системы, но дополнительный уровень усложняет понимание кода

Полиморфизм

Полиморфизм (Polymorphism) - принцип, согласно которому поведение системы может варьироваться в зависимости от типа объекта, при этом сам код обращается к единому интерфейсу. Полиморфизм позволяет избежать громоздких условных конструкций if/switch и делегирует выбор конкретного поведения самим объектам. Вместо того чтобы проверять тип объекта и вручную выбирать нужное действие, объявляется абстрактный метод или метод интерфейса, в реализации которого в конкретных классах описано желаемое поведение

кря

Устойчивость к изменениям

Устойчивость к изменениям (Protected Variations) - это способность системы оставаться работоспособной и легко модифицируемой при появлении новых требований или изменении существующих. Система с высокой устойчивостью к изменениям требует минимальных правок в уже написанном коде при добавлении новой функциональности

Эта характеристика чаще всего не достигается сама по себе, а является следствием применения других принципов: слабого зацепления, сильной связности, перенаправления и информационного эксперта

Чистая выдумка

Чистая выдумка (Pure fabrication) подразумевает создание выдуманной сущности, которая не входит в моделирование бизнес-логики. Чаще всего это инфраструктурные модули, например, сервис для доступа к базе данных (их называют объектами доступа в данным, Data Access Object)

Такие типы уменьшают зацепление и повышают связность