Полный теоретический путеводитель по каноническим принципам проектирования: подробный разбор каждого принципа, визуальные схемы, антипаттерны, примеры кода на C# и корреляция между SOLID и GRASP по программе кафедры ИТМО.
Классический вариант Роберта Мартина: «A class should have one, and only one, reason to change». Под модулем на кафедре понимается любой тип в коде, инкапсулирующий логику и состояние.
Класс Tare сам хранит товары, сам считает весы, сам лезет в базу данных и сам проверяет товарное соседство через правила пожарной безопасности.
Причина проблемы: изменился формат БД — меняем Tare. Изменились правила пожарников — опять меняем Tare.
Tare отвечает только за состояние тары. Проверка совместимости вынесена в NeighborhoodValidator. Сохранение — в ITareRepository.
Каждый класс меняется только по своей единственной изолированной причине.
Мы должны иметь возможность добавлять в систему новое поведение без переписывания и правки существующего протестированного кода.
Внутри сервиса написан гигантский switch (ruleType). Чтобы поддержать правило проверки срока годности, приходится лезть внутрь существующего метода и дописывать новый case, рискуя сломать старое.
Выделен интерфейс IDistributionRule. Новое поведение добавляется созданием нового класса (например, ExpirationRuleDecorator), а старые классы тар и палет не меняются ни на символ.
В нашем проекте: любое правило (TareDistributionRule, PalletDistributionRule, декоратор) можно подставить в любое место, ожидающее IDistributionRule. Ни один потомок не бросает неожиданных исключений вроде NotImplementedException и не меняет контракт контракта.
Назначение ISP по презентации: правило для корректного проектирования связей между типами.
Класс, которому нужно только логирование, вынужден зависеть от методов работы с БД и временем.
Каждый потребитель зависит ровно от того метода, который ему действительно нужен.
Классическая развертка по Мартину: модули верхних уровней не должны зависеть от модулей нижних уровней (оба зависят от абстракций); абстракции не зависят от деталей реализации.
В нашем коде: TareService не создаёт базу данных сам через new Database(). Он принимает интерфейс ITareRepository через конструктор. В тестах мы подставляем InMemoryTareRepository без реальной базы данных.
GRASP (General Responsibility Assignment Software Principles) — общие принципы распределения ответственностей в ПО.
Цитата со слайдов: «GRASP рассматривает те же проблемы, что и SOLID, но с немного другой стороны. SOLID — таблица умножения от мира ООП, а модуль — самодостаточная единица системы, реализующая определённый функционал, инкапсулирующая логику и состояние».
CurrentOvh)? Класс Tare, потому что именно он держит коллекцию Items. Неправильно было бы заставлять внешний сервис бегать и собирать веса руками.
Distribute(Item).
NeighborhoodValidator не занимается поиском тары в репозитории и взвешиванием товара. Он сфокусирован только на одном вопросе: совместимы ли товарные группы?
Tare и Sku.
TareDistributionRule является посредником между клиентом дерева правил и внутренним сервисом TareService.
ITimeProvider и IRuleLogger защищают доменную логику: завтра консольный вывод можно заменить на Serilog или ElasticSearch, не трогая правила.
На экзаменах и защитах в ИТМО обожают задавать вопрос: «А чем GRASP отличается от SOLID, и как они пересекаются?». Вот шпаргалка соответствия:
| Принцип SOLID | Соответствующий паттерн GRASP | В чём общая суть? |
|---|---|---|
| SRP (Single Responsibility) | High Cohesion (Высокая сплочённость) | Класс сфокусирован на одной задаче и имеет одну причину для изменений. |
| OCP (Open-Closed) | Protected Variations (Защита от изменений) | Закрываем точки нестабильности за стабильными интерфейсами (новые правила расширяют поведение, не ломая старое). |
| DIP (Dependency Inversion) | Low Coupling & Indirection | Устраняем прямую зависимость от реализаций, вводя интерфейсы-посредники. |
| DIP + SRP | Pure Fabrication (Чистая выдумка) | Выносим системную логику (логирование, репозитории, валидаторы) в искусственные сервисы. |
Ответы основаны на материалах лекций курса ООП в ИТМО.
Coupling (зацепление/связанность) — это степень взаимозависимости между разными модулями (мы стремимся к Low Coupling, чтобы модули можно было легко переиспользовать и тестировать отдельно).
Cohesion (сплочённость) — это мера того, насколько сильно элементы внутри одного модуля связаны друг с другом по смыслу (мы стремимся к High Cohesion, чтобы класс не делал «всё подряд»).
Оба паттерна имеют одинаковую структуру (оборачивают исходный объект и реализуют тот же интерфейс). Разница в намерении (Intent):
Если бездумно пихать всю логику туда, где лежат данные, мы получим гигантский монолит с низкой сплочённостью (Low Cohesion). Чтобы этого избежать, мы балансируем Information Expert с принципом Pure Fabrication — выносим бизнес-процессы (как проверка товарного соседства или координация добавления) в специализированные сервисы.