Шаблоны GRASP (General Responsibility Assignment Software Principles, Общие принципы распределения ответственности в ПО) - шаблоны, которые решают задачи назначения ответственности программных компонентов
Шаблоны были описаны Крэгом Ларманом в книге “Применение UML и шаблонов проектирования” в 1997 году. Каждый шаблон помогает решить конкретную задачу, возникающую при проектировании ПО, всего их девять:
Информационный эксперт (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, а не на тех, кто обладает информацией), и из-за этого появляются проблемы:
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;
}
}
Недостатки создателя:
OrderItem и методом создания AddItem: если мы захотим в OrderItem добавить новый аргумент, то будет трудно везде изменять методКонтроллер (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)
Такие типы уменьшают зацепление и повышают связность