GRASP принципы
GRASP - general responsibility assignment software patterns — общие шаблоны распределения ответственностей. GRASP шаблоны, используемые в объектно-ориентированном проектировании для решения общих задач по назначению ответственностей классам и объектам.
Существует девять GRAPS шаблонов, изначально описанных в книге Крейга Лармана «Применение UML и шаблонов проектирования». В отличие от классических читателю паттернов из Банды Четырех, GRAPS паттерны не имеют выраженной структуры, четкой области применения и конкретной решаемой проблемы, а лишь представляют собой обобщенные подходы/рекомендации/принципы, используемые при проектировании дизайна системы.
GRASP состоит из 5 основных и 4 дополнительных шаблонов.
Основные шаблоны:
- Information Expert
- Creator
- Controller
- Low Coupling
- High Cohesion
Дополнительные шаблоны:
- Pure Fabrication
- Indirection
- Polymorphism
- Protected Variations
Информационный эксперт (Information Expert)
Шаблон определяет базовый принцип распределения ответственности:
Ответственность должна быть назначена тому, кто владеет максимумом необходимой информации для исполнения — информационному эксперту.
Этот шаблон — самый очевидный и важный из девяти. Если его не учесть — получится "спагетти-код", в котором трудно разобраться.
Применение шаблона информационный эксперт повышает связность модулей и не противоречит свойству инкапсуляции.
В следующем примере на C# класс Customer содержит ссылки на все заказы клиентов, следовательно логично разместить тут метод для подсчета общей стоимости заказов:
public class Customer
{
private readonly List<Order> _orders = new List<Order>();
public decimal GetTotalAmount(Guid orderId)
{
return this._orders.Sum(x => x.Amount);
}
}
Создатель (Creator)
Creator или Создатель — суть ответственности такого объекта в том, что он создает другие объекты. Сразу напрашивается аналогия с абстрактной фабрикой.
По сути шаблон проектирования Абстрактная фабрика (создание объектов концентрируется в отдельном классе) это альтернатива создателя.
Но есть ряд моментов, которые должны выполняться, когда мы наделяем объект ответственностью создателя:
- Создатель содержит или агрегирует создаваемые объекты;
- Создатель использует создаваемые объекты ;
- Создатель знает, как проинициализировать создаваемый объект ;
- Создатель записывает создаваемые объекты
- Создатель имеет данные инициализации для A
Возвращаясь к примеру с Customer'ом, рассмотрим следующий .NET код:
public class Customer
{
private readonly List<Order> _orders = new List<Order>();
public void AddOrder(List<OrderProduct> orderProducts)
{
var order = new Order(orderProducts); // создатель
_orders.Add(order);
}
}
Controller
Шаблон сontroller призван решить проблему разделения интерфейса и логики в интерактивном приложении. Это не что иное, как хорошо известный контроллер из MVC парадигмы. Контролер отвечает за обработку запросов и решает кому должен делегировать запросы на выполнение. Если обобщить назначение сontroller, то он должен отвечать за обработку входных системных сообщений.
Подробнее про контроллер можно почитать в статье: MV-паттерны для проектирования веб-приложений
Слабая связанность (зацепление) или Low Coupling
Если объекты в приложении сильно связанны, то любой их изменение приводит к изменениям во всех связанных объектах. А это неудобно и порождает множество проблем. Low coupling как раз говорит о том что необходимо, чтобы код был слабо связан и зависел только от абстракций. Слабая связанность так же встречается в SOLID принципах как The Dependency Inversion Principle (DIP) и слабая связанность по сути это реализация Dependency Injection принципа. Когда мы уходим от конкретных реализаций и абстрагируемся на уровнях интерфейсов(которые легко подменять нужными нам реализациями), тогда код не завязан на определенные реализации.
Высокая связанность (High Cohesion)
По сути High Cohesion очень тесно связанна с Single Responsibility Principle (SRP) с SOLID принципов. High Cohesion получается в результате соблюдения SRP.
High Cohesion принцип говорит о том, что класс должен стараться выполнять как можно меньше не специфичных для него задач, и иметь вполне определенную область применения. Только с опытом приходит понимание балансировки между High Cohesion и Low Coupling. считается что Low Coupling и High Cohesion) это инь и янь проектирования ПО. Некорректное юзание High Cohesion порождает неправильное Low Coupling и наоборот.
Чистая выдумка (Pure Fabrication)
Pure Fabrication или чистая выдумка, или чистое синтезирование. Здесь суть в выдуманном объекте. Аналогом может быть шаблон Service (сервис) в парадигме DDD.
Какую проблему решает Pure Fabrication?
- Уменьшает зацепление ( Low Coupling);
- Повышает связанность (High Cohesion) ;
- Упрощает повторное использование кода.
Не совсем понятно?
Давайте рассмотрим пример и все станет на свои места ^_^
К примеру у вас есть объект Customer
и следую шаблону информационный эксперт вы наделили его логикой которую мы показывали выше, как вы реализуете сохранение Customera
в БД?
Так вот следуя Pure Fabrication принципу, мы создадим Сервис или репозиторий который будет доставать и сохранять такой объект в базу данных.
Посредник (Indirection)
Indirection или посредник. Можно столкнуться с таким вопросом: «Как определить ответственность объекта и избежать сильной связанности между объектами, даже если один класс нуждается в функционале (сервисах), который предоставляет другой класс?»
Решение: возложите ответственность на промежуточный объект, чтобы он осуществлял связь между другими компонентами или службами, чтобы они не были напрямую связаны. Такое решение можно сделать с помощью GoF паттерна медиатор
К примеру у нас был код:
public class CustomerOrdersController : Controller
{
private readonly IOrdersService _ordersService;
public CustomerOrdersController(IOrdersService ordersService)
{
this._ordersService = ordersService;
}
}
мы можем переписать этот код с помощью mediator для связки между объектами:
public class CustomerOrdersController : Controller
{
private readonly IMediator _mediator;
public CustomerOrdersController(IMediator mediator)
{
this._mediator = mediator;
}
public async Task<IActionResult> AddCustomerOrder(
[FromRoute]Guid customerId,
[FromBody]CustomerOrderRequest request)
{
await _mediator.Send(new AddCustomerOrderCommand(customerId, request.Products));
return Created(string.Empty, null);
}
}
Обратите внимание. Посредник поддерживает "low coupling", но снижает читабельность и понимание всей системы. Вы не знаете, какой класс обрабатывает команду из определения Controller. Это компромисс.
Полиморфизм (Polymorphism)
Полиморфизм позволяет реализовывать одноименные публичные методы, позволяя различным классам выполнять различные действия при одном и том же вызове. То есть объекты классов Square и Circle могут отображаться(реализовывать метод render) по разному несмотря не то, что они оба подклассы Shape, метод render определен в Shape. (Overriding).
Принцип полиморфизма является основополагающим в ООП. В этом контексте принцип тесно связан с GoF паттерном strategy. Это самый яркий пример реализации полиморфизма.
Устойчивость к изменениям (Protected Variations)
Проблема: Как спроектировать объекты, подсистемы и системы таким образом, чтобы изменения или нестабильность этих элементов не оказывали нежелательного влияния на другие элементы?
Решение: Определите точки прогнозируемого изменения или нестабильности, распределите обязанности по созданию стабильного интерфейса вокруг них.
По мнению многих это самый важный принцип который косвенно связан с остальными принципами GRASP. В настоящее время одним из наиболее важных показателей качества кода является простота изменений. Как архитекторы и программисты, мы должны быть готовы к постоянно меняющимся требованиям. Это не является «nice to have» атрибутом - это «must-have» в любом приложении и наша обязанность как программистов и архитекторов нашей системы это обеспечить.