GRASP

Назначение: паттерны назначения обязанностей (responsibility assignment) — как решать, какой объект за что отвечает: кому доверить создание, кто обрабатывает системное событие, где живёт знание о данных и как защитить систему от изменений.

Аудитория: разработчики, техлиды, архитекторы.

Статус: устоявшийся набор паттернов (Крейг Ларман, Applying UML and Patterns, издания 1997, 2001, 2005).

Не путать с: паттернами GoF (готовый каталог шаблонов решения), SOLID (пять принципов проектирования классов), DDD (стратегическое и тактическое проектирование предметной области).

Общее

GRASP — акроним General Responsibility Assignment Software Patterns, «обобщённые паттерны назначения обязанностей в программном обеспечении». Это девять эвристик, отвечающих на главный вопрос объектно-ориентированного проектирования: кому какую ответственность назначить. Автор набора — Крейг Ларман (Craig Larman), описавший его в книге Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development. Книга выдержала три издания — 1997, 2001 и 2005 годов, — и именно в ней впервые систематически изложен GRASP.

Ключевая идея Лармана, с которой начинается всё изложение книги: проектирование — это прежде всего назначение обязанностей (responsibility assignment as the core of object design). Обязанность (responsibility) в этом словаре — это контракт объекта: обязательство, которое он взял на себя, — знать что-то (знание о данных, о других объектах, о конфигурации) или делать что-то (вычислять, координировать, взаимодействовать с внешним миром). Каждое проектное решение — «куда положить этот метод», «кто создаёт этот объект», «кто реагирует на это событие» — есть решение о назначении обязанности. GRASP даёт именованный набор рассуждений, с помощью которых такие решения принимаются осознанно, а не по наитию.

Исторически GRASP опирается на направление Responsibility-Driven Design (RDD, «проектирование, управляемое обязанностями») Ребекки Уирфс-Брок (Rebecca Wirfs-Brock) — книгу Designing Object-Oriented Software (1990), где объекты впервые системно описывались через роли и обязанности, а не через данные. Ларман перенял этот словарь, добавил недостающие эвристики и придал набору дидактическую форму: каждый паттерн сформулирован как вопрос, который проектировщик задаёт себе в момент решения. В ранних изданиях книги центральное место занимали пять паттернов — Information Expert, Creator, Controller, Low Coupling и High Cohesion; их принято называть «базовыми». В последующих изданиях набор был расширен ещё четырьмя — Polymorphism, Pure Fabrication, Indirection и Protected Variations, — доведёнными до итоговых девяти.

ИзданиеГодВклад в GRASP
Applying UML and Patterns, 1-е изд.1997Первое изложение: пять «базовых» паттернов назначения обязанностей
Applying UML and Patterns, 2-е изд.2001Переработка под Unified Process; расширение набора эвристиками об изменчивости
Applying UML and Patterns, 3-е изд.2005Канонический состав: девять паттернов; систематический разбор GoF через GRASP

Отдельная заслуга Лармана — форма изложения. Книга написана как учебник итеративной разработки: обязанности назначаются постепенно, по мере роста требований, а не в одном большом акте проектирования. Инструмент, с которым GRASP исторически связан, — CRC-карточки (Class–Responsibility–Collaborator, «класс–обязанность–соучастник»): на каждой карточке записывается имя класса, его обязанности и те, с кем он для этого сотрудничает. Набор таких карточек — материализованная таблица назначения обязанностей: паттерны GRASP подсказывают, что писать в средний столбец, а колода карточек делает связи между классами видимыми и обсуждаемыми всей командой.

GRASP важно отличать от двух соседних и часто путаемых с ним систем — паттернов GoF и SOLID.

Отличие от GoF. Каталог «банды четырёх» (Design Patterns, Гамма, Хелм, Джонсон, Влиссидес, 1994) — это двадцать три готовых шаблона решения типовых задач: именованные конструкции («Стратегия», «Декоратор», «Наблюдатель») с известной структурой классов. GRASP — не каталог решений, а принципы рассуждения о назначении обязанностей: способы ответить на вопрос «кому отдать ответственность», предшествующие выбору конкретной конструкции. Уровни не конкурируют, а дополняют друг друга: GoF-паттерны сами могут быть объяснены через GRASP — так, Фасад — это применение Controller и Pure Fabrication, Адаптер — Indirection и Protected Variations, Стратегия — Polymorphism и Protected Variations. Ларман в книге именно так и поступает: описывая GoF-паттерны, он каждый раз показывает, какие GRASP-решения в них зашиты.

Отличие от SOLID. SOLID — пять принципов проектирования классов, систематизированных Робертом Мартином: ограничения на то, каким должен быть класс (одна причина изменения, закрытость для модификации, подставляемость подтипов, узкие интерфейсы, зависимость от абстракций). GRASP шире по охвату и другой по жанру: это девять эвристик распределения обязанностей, применимых к любым взаимодействующим элементам — объектам, компонентам, модулям, сервисам, — и сформулированных не как ограничения, а как вопросы и критерии выбора. GRASP старше SOLID (1997 против ~2000) и содержит его прообразы: Information Expert предвосхищает Single Responsibility, Polymorphism — Open/Closed и Liskov Substitution, Pure Fabrication и Indirection — Dependency Inversion. Подробное сопоставление — в разделе «Связь с SOLID и GoF».

Сводная таблица девяти паттернов:

№ПаттернВопрос, на который отвечаетСуть
1Information ExpertКому назначить обязанность в целом?Тому, кто владеет нужными данными
2CreatorКто должен создавать объект?Тот, кто агрегирует, содержит или тесно использует его
3ControllerКто обрабатывает системное событие?Не-класс интерфейса, оркеструющий сценарий
4Low CouplingКак уменьшить связанность?Минимизировать число и силу зависимостей
5High CohesionКак сохранить сфокусированность?Обязанности элемента — едины по смыслу
6PolymorphismКак обрабатывать вариации поведения?Полиморфные типы вместо условных ветвлений по типу
7Pure FabricationКому дать обязанность, если «естественного» владельца нет?Искусственному классу, выдуманному ради связности и слабой связанности
8IndirectionКак избежать прямой связанности?Через промежуточный объект-посредник
9Protected VariationsКак защититься от изменений?Обернуть изменчивое стабильным интерфейсом

Дальше каждый паттерн разобран по единой схеме: суть → когда применять → мини-пример.

Девять паттернов

Information Expert (информационный эксперт)

Суть. Обязанность назначается тому, кто владеет информацией, необходимой для её выполнения, — информационному эксперту. Это самый фундаментальный из паттернов: он решает общую задачу «куда положить поведение» и является частным случаем инкапсуляции — данные и работа с ними живут вместе.

Когда применять. Каждый раз, когда возникает вопрос «в каком классе должен быть этот метод». Эксперт по данным даст ответ с наименьшим количеством передач информации: ничему не нужно знать о внутреннем устройстве другого. Осторожность нужна в двух случаях: когда обязанность требует данных из многих несвязанных областей (тогда единый эксперт разрастётся — см. High Cohesion) и когда эксперт из предметной области пришлось бы связать с технической инфраструктурой (тогда помогает Pure Fabrication).

Мини-пример. Подсчёт суммы заказа. Неэкспертное решение — сервис, выгребающий внутренности позиций:

class OrderService {
  public decimal Total(Order order) {
    decimal sum = 0;
    foreach (var line in order.GetLines())          // чужие данные...
      sum += line.Qty * line.Product.Price;         // ...и их внутренности
    return sum;
  }
}

Экспертное — заказ сам знает свои строки и их устройство:

class Order {
  private readonly List<OrderLine> _lines = new();
  public decimal Total() => _lines.Sum(l => l.Subtotal()); // знание о строках — здесь
}

OrderService исчезает: обязанность ушла к владельцу данных. Изменение структуры позиции (Subtotal начал учитывать скидку) затрагивает один класс.

Creator (создатель)

Суть. Обязанность создавать объект B назначается классу A, если выполняется хотя бы одно из условий: A агрегирует B, A содержит B, A записывает B, A тесно использует B, A располагает данными инициализации B. Создатель — частный случай Information Expert, вынесенный Ларманом в отдельный паттерн из-за частоты вопроса «кто создаёт».

Когда применять. При любом появлении оператора new стоит спросить: правильно ли место? Если создание попадает в класс, не связанный с создаваемым объектом ни одним из условий, — это запах: связанность растёт, а логика инициализации расползается. Когда ни один кандидат не подходит (семейства объектов, сложная инициализация), класс-создатель выдумывают специально — так рождаются порождающие паттерны GoF: Factory Method, Abstract Factory, Builder, Prototype.

Мини-пример. Кто создаёт позицию заказа? Кандидат A — Order: он агрегирует позиции и располагает данными для их создания.

class Order {
  private readonly List<OrderLine> _lines = new();
  public OrderLine AddItem(Product product, int qty) {
    var line = new OrderLine(product, qty); // Order — агрегирует и инициализирует
    _lines.Add(line);
    return line;
  }
}

Создание позиций в OrderService или, хуже, в контроллере нарушило бы инкапсуляцию агрегата: список _lines пришлось бы выставить наружу.

Controller (контроллер)

Суть. Обязанность обрабатывать системное событие — ввод пользователя, HTTP-запрос, сообщение из очереди — назначается специальному не-классу интерфейса: классу, представляющим либо весь сценарий (контроллер варианта использования, use-case контроллер), либо всю подсистему (фасад). Контроллер — не «кнопка» и не «окно»: он не выполняет работу сам, а делегирует её экспертам.

Когда применять. Всегда, когда внешний ввод запускает сценарий. Контроллер — первая точка входа события в систему, и от его худости зависит всё остальное: если он начнёт выполнять работу лично, превратится в «раздутый контроллер» (bloated controller) — типичный антипаттерн, когда в одном классе скапливается логика десятков сценариев. Лекарство — делегирование экспертам и разбиение на сценарные контроллеры; в этом Controller смыкается с Single Responsibility из SOLID.

Мини-пример. Событие «оформить заказ» из веб-интерфейса:

class PlaceOrderController {                 // представляет сценарий, не интерфейс
  private readonly OrderService _orders;
  public PlaceOrderController(OrderService orders) => _orders = orders;

  public IActionResult Handle(PlaceOrderDto dto) {
    var result = _orders.Place(dto.CustomerId, dto.ItemIds); // работа — экспертам
    return result.Success ? Ok(result.OrderId) : BadRequest(result.Error);
  }
}

Контроллер переводит транспорт в термины сценария и делегирует; ни одного бизнес-правила в нём нет. Терминологическая перекличка с MVC не случайна: контроллер MVC — прямое применение GRASP-контроллера, а на уровне архитектуры эту же роль играют интеракторы Clean Architecture.

Low Coupling (низкая связанность)

Суть. Из нескольких вариантов назначения обязанности выбирать тот, при котором связанность (coupling) — число и сила зависимостей между элементами — остаётся низкой. Элемент, зависящий от многих, хрупок: изменение любого соседа его задевает; его невозможно проверить в изоляции и переиспользовать отдельно.

Когда применять. Это паттерн-фильтр: он не назначает обязанности сам, а оценивает чужие решения. Взвешивать связанность стоит при каждом «кто должен знать о ком»: перед передачей ссылки на класс, перед прямым вызовом инфраструктуры из домена, перед общим состоянием. На уровне архитектуры низкая связанность — критерий проведения границ между модулями монолита и контрактная дисциплина между микросервисами: чем слабее связь через границу, тем дешевле изменения по разные её стороны. Предел, разумеется, известен: система с нулевой связанностью ничего не делает; цель — минимум зависимостей, необходимых для задачи.

Мини-пример. Сервис уведомлений о заказе можно связать с доменом напрямую — тогда домен знает о почте:

class Order {                       // связан с SMTP: домен импортирует инфраструктуру
  private readonly SmtpNotifier _notifier = new();
  public void Confirm() => _notifier.Send($"order {Id} confirmed");
}

Решение по Low Coupling — зависеть от абстракции, а не от транспорта (как именно — показывают Pure Fabrication и Indirection ниже).

High Cohesion (высокая связность)

Суть. Обязанности элемента должны быть связны (cohesion): относиться к одной задаче, одному назначению. Элемент с высокой связностью делает немного связанных вещей и делает их хорошо; элемент с низкой — «всё понемногу», превращаясь в свалку. Связанность и связность — парные меры: первая смотрит на связи между элементами, вторая — на целостность внутри.

Когда применять. Как и Low Coupling, это фильтр над чужими решениями. Сигнал нарушения — класс, к описанию которого приходится начинать со слова «и»: «скачивает прайс, парсит его, считает скидки и пишет отчёт». Другой сигнал — методы, использующие непересекающиеся подмножества полей: класс на самом деле несколько классов. Высокая связность — то, что Single Responsibility из SOLID формулирует как «одна причина для изменения», поэтому оба вопроса задаются вместе: «связны ли эти обязанности?» и «меняются ли они по одной причине?».

Мини-пример. «Сервис заказа», выросший в склад услуг:

class OrderService {
  public void Place(Order o) { ... }        // бизнес: оформление
  public byte[] ExportPdf(Order o) { ... }  // представление: PDF
  public void SyncToErp(Order o) { ... }    // интеграция: ERP
  public void SendEmail(Order o) { ... }    // доставка: почта
}

Четыре независимые причины изменения в одном классе. Разрезание по связности даёт PlaceOrder (бизнес), OrderPdfExporter, ErpSync и OrderNotifier — каждый меняется по своей причине и тестируется отдельно.

Polymorphism (полиморфизм)

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

Когда применять. При каждом появлении ветвления по типу: if (shape.Kind == ...), switch (payment.Method). Условная версия связывает вызывающего со всеми вариантами сразу: новый вариант требует правок во всех точках ветвления — классическая хрупкость, которую устраняет Open/Closed из SOLID, а корректность подстановки вариантов обеспечивает Liskov Substitution. В контейнерах GoF полиморфизм — механизм Стратегии, Состояния и Команды; в чистом виде он и есть ответ GRASP на вопрос «как обрабатывать вариации поведения?».

Мини-пример. Скидка по типу клиента — от ветвления к полиморфизму:

interface DiscountPolicy { decimal RateFor(Customer c); }

class RegularDiscount : DiscountPolicy {
  public decimal RateFor(Customer c) => 0.05m;
}
class VipDiscount : DiscountPolicy {
  public decimal RateFor(Customer c) => 0.20m;
}

Расчёт суммы перестаёт знать о существовании типов клиентов: он вызывает RateFor и получает ответ. Новый тип клиента — новый класс, ни одна строка существующего кода не меняется.

Pure Fabrication (чистое выдумывание)

Суть. Если назначение обязанности «естественному» владельцу из предметной области разрушает связность или связанность, обязанность отдают искусственному классу, не имеющему представителя в домене, — чистой выдумке ради качества проектирования. Класс не изображает ничего из реального мира; он существует, чтобы проект получался лучше.

Когда применять. Классический запуск — персистентность: обязанность «сохранять заказы» по Information Expert следует отдать Order, но тогда доменная сущность узнает о SQL, соединениях и транзакциях (рост связанности, падение связности, угроза принципам SOLID и, шире, доменной чистоте DDD). Компромисс, который предлагает GRASP, — выдумать OrderRepository: класса «репозиторий» нет в предметной области, но он принимает обязанность хранения, оставляя домен незапятнанным. Pure Fabrication — самый «прагматичный» паттерн набора: он легализует отступление от доменной естественности. Именно так в коде появляются интерфейсы шлюзов, адаптеры и прикладные сервисы; на архитектурном уровне этот паттерн в связке с Dependency Inversion строит границы Clean Architecture и порты гексагональной архитектуры. Терминологическое предостережение: выдумка — не лицензия на абстракции ради абстракций; она оправдана, когда решает конкретную задачу связности/связанности, о чём напоминает DRY / KISS / YAGNI.

Мини-пример. Хранение заказов вынесено в выдуманный класс:

interface OrderRepository {          // в домене таких сущностей нет
  Order? Find(int id);
  void Save(Order order);
}

class SqlOrderRepository : OrderRepository {  // выдумка знает о SQL, домен — нет
  public Order? Find(int id) { /* SELECT ... */ return null; }
  public void Save(Order order) { /* INSERT ... */ }
}

Order остаётся чистым экспертом по бизнес-правилам; выдуманный репозиторий — экспертом по хранению.

Indirection (посредник)

Суть. Чтобы два элемента не связывались напрямую, между ними вводится промежуточный объект, через который они общаются. Посредник — реализация низкого связывания в чистом виде: вместо зависимости «A → B» возникают «A → M» и «M → B», и когда B меняется (или заменяется целиком), A не замечает ничего.

Когда применять. Когда прямая связь опасна: элемент изменчив, внешен по отношению к системе, может быть заменён или должен подменяться в тестах. Типичные посредники — интерфейс, адаптер, брокер сообщений, событийная шина. Паттерн вводит косвенность, а косвенность стоит понимания, поэтому применение оправдано там, где вероятность изменения реальна, — в этом Indirection разделяет общее с ними правило «абстракция под доказанную изменчивость». В каталоге GoF через посредника устроены Адаптер, Медиатор, Фасад, Заместитель, Наблюдатель.

Мини-пример. Домен зависит от платежей через посредника, а не от клиентской библиотеки платёжной системы:

interface PaymentGateway {                    // посредник между доменом и Stripe
  PaymentResult Charge(decimal amount, string currency);
}

class OrderService {
  private readonly PaymentGateway _payments;  // зависимость от посредника
  public OrderService(PaymentGateway payments) => _payments = payments;
}

Замена провайдера, песочница в тестах, новая версия SDK — всё это меняет реализацию за посредником, не затрагивая OrderService.

Protected Variations (защищённые изменения)

Суть. Определить точки, вероятные к изменению, и обернуть их стабильным интерфейсом, чтобы изменения по одну сторону обёртки не распространялись на другую. Ларман называет этот паттерн стержнем большинства принципов и паттернов проектирования: «идентифицируй изменчивость и инкапсулируй её» — та же мысль, что у Мейера и у Open/Closed из SOLID.

Когда применять. При любом риске изменения: внешние системы, аппаратные особенности, разные платформы, экспериментальные алгоритмы, изменяющиеся требования. Механизм обёртки — любой из уже названных: интерфейс (Indirection), выдуманный контракт (Pure Fabrication), полиморфная иерархия (Polymorphism). На архитектурном масштабе Protected Variations — это и антикоррупционные слои DDD, изолирующие домен от чужих моделей, и «порты» гексагональной архитектуры, и правило зависимостей Clean Architecture: везде стабильное ядро защищено от изменчивой периферии. Важно направление усилия: защищать стоит вероятные изменения — обёртка вокруг того, что никогда не изменится, — чистые издержки.

Мини-пример. Источник курса валют изменчив (файл, API, кэш) — обёртка фиксирует контракт:

interface ExchangeRates {
  decimal Rate(string from, string to);
}

class CachingRates : ExchangeRates {          // реализаций может быть много,
  private readonly ExchangeRates _inner;      // потребители не меняются
  public CachingRates(ExchangeRates inner) => _inner = inner;
  public decimal Rate(string f, string t) => /* кэш..мимо.. */ _inner.Rate(f, t);
}

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

Связь с SOLID и GoF

GRASP, SOLID и GoF отвечают на вопросы разных уровней, но описывают одно проектирование. GRASP — способ рассуждения, предшествующий выбору конструкции: сначала решается, кому достанется обязанность и что защищается от изменения, и лишь затем выбирается конкретное средство — принцип SOLID как ограничение на устройство класса или паттерн GoF как готовый шаблон. Не случайно Ларман излагает GRASP раньше паттернов GoF: паттерны каталога «банды четырёх» раскладываются у него на GRASP-решения.

Таблица соответствий (основные линии):

GRASPSOLIDGoF и другие каталоги
Information ExpertSRP, инкапсуляция— (базовая эвристика, лежит под всеми)
Creator—Factory Method, Abstract Factory, Builder, Prototype
ControllerSRPFacade, Mediator; контроллер MVC; интерактор Clean Architecture
Low CouplingDIP, ISPFacade, Mediator, Adapter; границы монолита и микросервисов
High CohesionSRP— (метрическая цель: LCOM и аналоги)
PolymorphismOCP, LSPStrategy, State, Command, Template Method
Pure FabricationDIPAdapter, Facade; Repository из DDD; порты и адаптеры
IndirectionDIPAdapter, Mediator, Proxy, Facade, Observer
Protected VariationsOCPAdapter, Strategy, Observer; антикоррупционный слой DDD

Несколько линий стоит проговорить подробнее.

Information Expert и SRP. Оба про концентрацию ответственности: эксперт — «у кого данные, тому и поведение», Single Responsibility — «у класса одна причина изменения». Первый отвечает на вопрос «куда положить», второй — «не слишком ли много положили». Это последовательные, а не конкурирующие фильтры: сначала обязанность назначается эксперту, затем проверяется, не разросся ли эксперт до нескольких причин изменения.

Pure Fabrication + Indirection и DIP. Dependency Inversion из SOLID описывает целевое состояние: модуль зависит от абстракции, а не от детали. GRASP объясняет, откуда эта абстракция берётся: она выдумана (Pure Fabrication — интерфейс репозитория не существует в предметной области) и вставлена между сторонами (Indirection — домен зависит от посредника, а не от SQL-кода). В этой связке GRASP — генезис, DIP — итоговая формулировка правила.

Controller и Facade/Mediator. GRASP-контроллер в «широкой» вариации — представитель подсистемы, что механически совпадает с Facade; в «сценарной» — с прикладным сервисом DDD и интерактором Clean Architecture. Посреднические паттерны GoF (Mediator, Facade) при этом сами объясняются через пару Controller + Low Coupling: один объект принимает обязанность координации, остальные избавляются от связей друг с другом.

Protected Variations и OCP. Это одна идея в двух формулировках: «оберни изменчивое стабильным интерфейсом» (Ларман) и «открыт для расширения, закрыт для модификации» (Мейер, Мартин). Разница жанра: OCP — принцип, проверяющий конструкцию постфактум, Protected Variations — вопрос, задаваемый в момент решения: «что здесь вероятнее всего изменится и что я этому противопоставлю?».

GRASP и архитектура. Пять из девяти паттернов (Low Coupling, High Cohesion, Pure Fabrication, Indirection, Protected Variations) не привязаны к классам и масштабируются до границ системы: они определяют, где проходят швы между слоями Clean Architecture и гексагональной архитектуры, насколько независимы модули монолита и насколько слабо связаны микросервисы. В этом смысле GRASP — редкий пример набора, работающего одинаково на уровне строки кода и на уровне диаграммы контекстов.

Как применять: порядок рассуждений

Паттерны GRASP не образуют алгоритма — это набор вопросов, — но на практике складываются в устойчивый порядок применения.

Шаг 1 — назначить обязанность эксперту. Для каждой новой обязанности первый кандидат — Information Expert: класс, владеющий данными. Этот шаг закрывает большинство повседневных решений и попутно укрепляет инкапсуляцию: данные перестают выгребаться наружу.

Шаг 2 — определить создателя и контроллер. Кто создаёт новый объект (Creator) и кто принимает системное событие (Controller), — два вопроса, которые решаются сразу после появления обязанности. Ответы фиксируют скелет взаимодействия: границы агрегатов и точки входа сценариев.

Шаг 3 — оценить по мерам. Полученный эскиз проверяется фильтрами Low Coupling и High Cohesion: не породило ли назначение лишних зависимостей, не свалилось ли несколько несвязанных обязанностей в один класс. Если оценка провалена — обязанность переносится или дробится.

Шаг 4 — обработать вариативность. Там, где поведение ветвится по типу, применяется Polymorphism; там, где нет естественного владельца или прямая связь опасна, — Pure Fabrication и Indirection; изменчивые точки оборачиваются по Protected Variations. Эти четыре паттерна дороже базовых, поэтому подключаются по доказанной потребности, а не заранее.

Шаг 5 — зафиксировать решение. Именованные рассуждения естественно ложатся в коммуникацию команды: формулировка «репозиторий — Pure Fabrication ради персистентности, домен от SQL защищён по Protected Variations» готова для код-ревью, а для решений масштабнее класса — для Architecture Decision Records: GRASP поставляет словарь, ADR — форму фиксации.

Такой порядок — не предписание Лармана, а сложившаяся практика применения его набора: базовые паттерны работают всегда и стоят дёшево, расширенные — по мере накопления информации об изменчивости. В этом GRASP солидарен с эволюционным взглядом на архитектуру: решения о границах принимаются тогда, когда для них появляется основание.

Плюсы

Именованные рассуждения вместо интуиции. Главный выигрыш — словарь. «Почему метод здесь?» перестаёт быть вопросом вкуса: ответ формулируется ссылкой на паттерн («эксперт по данным — Order», «посредник, потому что провайдер изменчив»), и проектные решения становятся проверяемыми аргументами в ревью.

Явная процедура для повседневных решений. Девять паттернов покрывают самые частые вопросы проектирования — куда положить поведение, кто создаёт, кто принимает событие, как изолировать изменение. Это делает процесс назначения обязанностей повторяемым и обучаемым: junior в команде рассуждает по той же схеме, что и ведущий инженер.

Устойчивость к изменениям. Protected Variations, Indirection и Low Coupling вместе дают систему, в которой изменения локализованы: правится реализация за стабильным интерфейсом, а не цепочка вызовов. Это прямой вклад в сопровождаемость и в стоимость владения.

Производительность коммуникации. Терминология GRASP компактна в обсуждениях и документации: «это контроллер сценария», «репозиторий — чистая выдумка ради персистентности», «обернём по Protected Variations». Фраза сворачивает абзац объяснений.

Фундамент для других систем. Через GRASP легко объяснять и SOLID, и GoF, и порты с адаптерами: все они оказываются следствиями одних и тех же решений о данных, создании, событиях и изменчивости. Один словарь обслуживает три уровня — код, компоненты, архитектуру.

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

Минусы

Скрытая цена косвенности. Посредники, выдуманные классы и обёртки добавляют уровней вызовов. Применённые без доказанной изменчивости, они усложняют трассировку потока выполнения: чтобы понять, кто на самом деле сохраняет заказ, приходится раскрывать интерфейс, реализацию и точку сборки. Аналог риска, о котором предупреждает DRY / KISS / YAGNI.

Наводка на переусложнение. «Применить паттерн» соблазнительнее, чем «оставить простым»: под действием GRASP легко плодить репозитории, шлюзы и иерархии там, где хватало бы одной функции. Ларман подчёркивает, что паттерны — эвристики, а не правила, но дидактическая форма набора провоцирует механическое применение.

Субъективность критериев. «Низкая связанность» и «высокая связность» — качественные суждения; одно и то же решение разные инженеры оценят по-разному. В отличие от форматирования или типов, GRASP нельзя проверить автоматически — только обсудить.

Устаревание контекста. Набор сформулирован в мире классического ООП конца 1990-х: примеры Лармана — C++/Java, диалоговые окна, реляционные СУБД. В функциональных и мультипарадигменных языках часть паттернов выполняется другими средствами (чистые функции, алгебраические типы, композиция), и буквальный перенос классовых конструкций излишен.

Незавершённость как системы. Девять паттернов не покрывают проектирование целиком: в них нет про интерфейсы пользователя, конкурентность, распределённость, транзакционность. GRASP — полезный слой рассуждений об обязанностях, но не замена архитектурным стилям и не ответ на системные вопросы масштабирования и развёртывания.

Связанные статьи

  • SOLID — пять принципов проектирования классов; исторически более поздняя систематизация части тех же идей (Information Expert ↔ SRP, Polymorphism ↔ OCP/LSP, Pure Fabrication + Indirection ↔ DIP).
  • Паттерны проектирования — каталог GoF: готовые шаблоны, которые сами объясняются через GRASP (Facade ↔ Controller, Adapter ↔ Indirection/Protected Variations, Strategy ↔ Polymorphism).
  • DRY / KISS / YAGNI — противовес избыточной абстракции: выдумывать посредников и обёртки — под доказанную изменчивость, а не на всякий случай.
  • Domain-Driven Design (DDD) — Repository как каноническая Pure Fabrication; антикоррупционный слой как Protected Variations в масштабе интеграций.
  • Clean Architecture — правило зависимостей как архитектурный масштаб Pure Fabrication, Indirection и Protected Variations.
  • Hexagonal (Ports & Adapters) и Onion — порты и адаптеры: посредники между ядром и изменчивой периферией.
  • Монолитная архитектура и Микросервисы — Low Coupling и High Cohesion как критерии проведения внутренних границ монолита и контрактов между сервисами.
  • Введение в архитектуру — контекст: место паттернов назначения обязанностей среди задач архитектуры ПО.

Краткий вывод

GRASP — девять именованных способов отвечать на главный вопрос объектного проектирования: кому какую обязанность назначить. Пять базовых паттернов (Information Expert, Creator, Controller, Low Coupling, High Cohesion) закрывают повседневные решения — куда положить поведение, кто создаёт объект, кто принимает событие, какими мерами оценивать результат; четыре добавленных (Polymorphism, Pure Fabrication, Indirection, Protected Variations) — работу с вариативностью и изменчивостью. Набор старше и шире SOLID, лежит под паттернами GoF и масштабируется до архитектурных границ. Ценность GRASP не в паттернах как таковых, а в дисциплине: каждое решение о размещении кода становится явным, именованным и обсуждаемым — а значит, проверяемым в ревью и воспроизводимым командой. Применять его стоит как словарь вопросов, помня о цене: посредники и обёртки оправданы доказанной изменчивостью, а не эстетикой диаграммы.

Первоисточники

  • Craig Larman. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development. 3rd ed. Addison-Wesley, 2005. Каноническое издание: GRASP изложен в главах о назначении обязанностей (responsibility assignment) и объектном проектировании; там же — разбор паттернов GoF через GRASP.
  • Craig Larman. Applying UML and Patterns. 1st ed., 1997; 2nd ed. (…and the Unified Process), 2001. Первые издания: пять «базовых» паттернов, позднее дополненные до девяти.
  • Rebecca Wirfs-Brock, Brian Wilkerson, Lauren Wiener. Designing Object-Oriented Software. Prentice Hall, 1990. Responsibility-Driven Design — прямая предшественница GRASP: проектирование через роли и обязанности.
  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. Каталог GoF, соотнесение с которым — в разделе «Связь с SOLID и GoF».
  • Robert C. Martin. Agile Software Development: Principles, Patterns, and Practices. Prentice Hall, 2002. Систематизация SOLID — для сопоставления с GRASP по таблице соответствий.