В прошлом модуле мы разобрали механизмы:
- Инкапсуляция – что класс прячет внутри;
- Наследование – как переиспользовать чужое поведение;
- Полиморфизм – как один вызов работает с разными типами;
- Абстракция – что оставить в интерфейсе, а что скрыть.
Все они отвечают на вопрос «как это записать в коде». Уровнем выше стоит другой вопрос, который синтаксис не решает: какому классу поручить конкретную обязанность. Кто считает сумму заказа – сам заказ или сервис? Компилятор это не проверит, а неверный выбор тихо копится, пока не вырастет в класс на три тысячи строк, который знает и умеет всё.
Откуда взялся GRASP
К середине 90-х объектные языки стали мейнстримом (этот путь мы прошли в первом модуле курса), и выяснилось: знать про классы и интерфейсы – не то же самое, что уметь спроектировать систему из них. Разработчики, свободно владевшие синтаксисом, всё равно получали хрупкие, запутанные структуры – просто потому, что решение «кто за что отвечает» принималось на глаз. Об этом и книга Крейга Лармана.
Ларман опирался на более раннюю идею responsibility-driven design – проектирования через ответственности, которую сформулировала Ребекка Вирфс-Брок (Rebecca Wirfs-Brock) ещё в конце 80-х. Он собрал из неё девять конкретных, проверяемых на практике правил и назвал их GRASP – General Responsibility Assignment Software Patterns, «общие паттерны распределения ответственности». В поздних изданиях книги слово «patterns» в названии заменили на «principles»: сам Ларман признал, что это не паттерны в смысле GoF (у тех есть имя, проблема и типовое решение), а скорее набор вопросов, которые стоит задать себе перед тем, как писать класс.
Не путайте с паттернами проектирования GOF – это последний модуль курса. GRASP отвечает на вопрос «кому поручить обязанность», GOF – «как типовым образом решить конкретную повторяющуюся задачу». Первое обычно нужно раньше второго.
Девять правил, а не одно
GRASP – это не единый принцип, а чек-лист. Их удобно сгруппировать по тому, какую часть проектирования они закрывают:
- кто чем владеет – Information Expert, Creator: у какого класса данные, тот и отвечает за операции над ними, тот и создаёт связанные объекты;
- кто с кем разговаривает – Controller, Low Coupling, High Cohesion: кто принимает внешние события, как минимизировать зависимости между классами, как не размазывать единственную обязанность класса по десяткам методов;
- что делать, когда чистое решение не находится – Polymorphism (уже разобран в прошлом модуле), Pure Fabrication, Indirection, Protected Variations: искусственный класс ради дела, посредник между двумя другими, защита от изменения интерфейса.
Деление на группы здесь условное – на практике принципы переплетены. Low Coupling почти всегда обсуждают вместе с High Cohesion, а Pure Fabrication – это, по сути, признание поражения Information Expert: способ красиво поступить, когда «естественного» эксперта по данным не нашлось.
Читая следующие уроки, задавайте себе не вопрос «как называется этот паттерн», а вопрос «какую обязанность я сейчас распределяю и почему именно так». Названия забудутся, а привычка спрашивать останется.