Добавить в корзинуПозвонить
Найти в Дзене

Паттерны GRASP и GoF: проектируем онлайн-кинотеатр

Приветствуем вас в новой статье из нашей серии, посвящённой фундаменту качественной IT-разработки! Мы уже обсудили принципы ООП и такие ключевые концепции, как SOLID, DRY, KISS и YAGNI. Сегодня мы переходим к следующему уровню — паттернам проектирования. Паттерн проектирования — это проверенное временем, эффективное решение часто встречающейся проблемы при проектировании архитектуры программ. В отличие от готового кода или алгоритма, паттерн — это общая концепция или образец решения, который нужно адаптировать под нужды конкретной программы.
Сначала мы рассмотрим GRASP как более общие принципы распределения обязанностей, а затем перейдём к практическим паттернам GoF. Чтобы объяснить всё максимально просто и наглядно, возьмём пример из реального мира — проектирование онлайн-кинотеатра. GRASP (General Responsibility Assignment Software Patterns) — это набор из девяти принципов, предложенных Крейгом Ларманом в книге «Применение UML и шаблонов проектирования». Если паттерны GoF решают кон
Оглавление

Приветствуем вас в новой статье из нашей серии, посвящённой фундаменту качественной IT-разработки! Мы уже обсудили принципы ООП и такие ключевые концепции, как SOLID, DRY, KISS и YAGNI. Сегодня мы переходим к следующему уровню — паттернам проектирования.

Паттерн проектирования — это проверенное временем, эффективное решение часто встречающейся проблемы при проектировании архитектуры программ. В отличие от готового кода или алгоритма, паттерн — это общая концепция или образец решения, который нужно адаптировать под нужды конкретной программы.
Сначала мы рассмотрим GRASP как более общие принципы распределения обязанностей, а затем перейдём к практическим паттернам GoF. Чтобы объяснить всё максимально просто и наглядно, возьмём пример из реального мира — проектирование онлайн-кинотеатра.

GRASP: Общие принципы распределения обязанностей

GRASP (General Responsibility Assignment Software Patterns) — это набор из девяти принципов, предложенных Крейгом Ларманом в книге «Применение UML и шаблонов проектирования». Если паттерны GoF решают конкретные технические задачи (создание, взаимодействие, поведение объектов), то GRASP объясняют, как грамотно назначать ответственность классам и объектам. Соответствие принципам GRASP позволяет создавать системы, которые обладают низкой связанностью и высоким зацеплением, а значит, их легче модифицировать, сопровождать и повторно использовать.

-2

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

Суть. Выполнять задачу должен класс, содержащий максимум необходимой информации.

Пример. Подсчёт общего времени фильмов в плейлисте поручаем объекту Playlist, ведь он знает, какие фильмы там лежат.

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

Суть. Разные объекты должны иметь минимальную взаимосвязь между собой. Изменение в одном месте не должно ломать другое. Мера связности модулей определяется количеством информации, которой располагает один модуль о природе другого.

Пример. Модуль оплаты не должен зависеть от конкретного типа карты (Visa/MasterCard), чтобы можно было легко добавить новый способ, например, Apple Pay.

3. Высокое зацепление (High Cohesion)

Суть. Каждый класс имеет свою сфокусированную задачу. Класс должен содержать максимально связные между собой по смыслу свойства и поведения.

Пример. UserProfile отвечает только за хранение и управление данными пользователя (имя, аватар), а не за обработку его платежей или подбор рекомендаций.

4. Контроллер (Controller)

Суть. Контроллер принимает входные действия пользователя и передаёт задачи компетентным классам.

Пример. MovieController принимает запрос пользователя «Посмотреть фильм» и делегирует его: SubscriptionService (который проверяет подписку) и StreamingService (который запускает видео).

Важный момент! Контроллеры не должны хранить состояние (должны быть stateless). Все данные, необходимые для обработки запроса, должны передаваться в метод контроллера через параметры.

5. Полиморфизм (Polymorphism)

Суть. Разные объекты вызываются через общий интерфейс, но умеют отвечать на один и тот же запрос по-разному.

Пример. Все виды подписок (Free, Premium, Family) вызываются через общий интерфейс Subscription, но каждая по-своему рассчитывает стоимость и доступ к контенту.

6. Чистая выдумка (Pure Fabrication)

Суть. Для разгрузки других классов от технических задач стоит создать «искусственный» класс, который не имеет аналогов в реальном мире, но делает код чище.

Пример. NotificationSender — класс, созданный специально для рассылки писем и push-уведомлений, чтобы не нагружать этой технической логикой класс User.

7. Перенаправление (Indirection)

Суть. Для снижения прямой связности между зависимыми объектами лучше ввести посредника. Посредник берёт на себя ответственность за координацию взаимодействия, позволяя другим объектам сосредоточиться на своих задачах. Посредник может быть легко заменён, что делает систему гибче.

Пример. RecommendationService не обращается напрямую к базе фильмов — он делает это через MovieRepository (посредника).

8. Устойчивость к изменениям (Protected Variations)

Суть. Используется для устранения точек неустойчивости (участков кода, которые подвергаются изменениям чаще всего) путём определения их в качестве интерфейсов и реализации для них различных вариантов поведения.

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

9. Создатель (Creator)

Суть. Создавать объект можно только тогда, когда он нужен, и создавать его должен тот, кто собирается его использовать.

Пример. Класс User создаёт свой собственный Playlist, поскольку он является его «владельцем» и управляет им.

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

GoF: 23 конкретных решения от «Банды четырёх»

Паттерны GoF — это 23 шаблона из книги «Design Patterns: Elements of Reusable Object-Oriented Software» (1994) авторов Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона и Джона Влиссидеса. Вместе они известны как «Банда четырёх» (Gang of Four).

Эти паттерны стали классикой и до сих пор лежат в основе большинства современных фреймворков и библиотек.

-3

Зачем они нужны?

  • Предлагают проверенные временем решения типичных задач.
  • Формируют общий профессиональный язык команды разработки.
  • Делают системы более гибкими, поддерживаемыми и расширяемыми.

Паттерны GoF делятся на три категории: порождающие (creational), структурные (structural) и поведенческие (behavioral).

Порождающие паттерны (Creational)

Абстрагируют создание объектов, делая систему независимой от конкретных классов.

  1. Singleton (Одиночка) — гарантирует, что у класса есть только один экземпляр с глобальным доступом. Полезен для менеджеров конфигураций или соединений с БД.

    Реализация в коде. Статический метод для доступа и приватный конструктор.
  2. Factory Method (Фабричный метод) — создание объектов по одному интерфейсу, оставляя выбор конкретного типа за подклассами. Есть базовый класс-создатель с «пустым» (абстрактным) методом createSomething(). Что именно создать — решает не базовый класс, а его дочерние классы, переопределяя этот метод.

    Реализация в коде. Абстрактный класс или интерфейс с фабричным методом (например, create()).
  3. Abstract Factory (Абстрактная фабрика) — создаёт несколько связанных продуктов за раз (семейство), гарантируя их совместимость и единый стиль/поведение (например, создание элементов интерфейса для web- и mobile-версий).

    Реализация в коде. Интерфейс UIFactory с методами createButton(): Button, createModal(): Modal, где Button и Modal — интерфейсы продуктов; WebUIFactory и MobileUIFactory реализуют UIFactory и возвращают WebButton/WebModal или MobileButton/MobileModal.
  4. Builder (Строитель) — позволяет создавать сложные объекты пошагово, идеален для классов с множеством параметров.

    Реализация в коде. Интерфейс PlayerSessionBuilder с шагами with...() и build(): PlayerSession; класс-билдер хранит выбранные поля (композиция), with...() возвращают this (цепочка), а PlayerSession имеет приватный конструктор и создаётся только через билдер.
  5. Prototype (Прототип) — создание нового объекта путём клонирования существующего (например, дублирование настроек профиля). Используется, когда создание объекта является более затратным по ресурсам, чем его клонирование.

    Реализация в коде. Абстрактный класс или интерфейс с методом clone().

Структурные паттерны (Structural)

Организуют отношения между частями системы, делая их гибкими и взаимозаменяемыми.

  1. Adapter (Адаптер) — делает несовместимые интерфейсы совместимыми, позволяет конвертировать интерфейс одного класса в интерфейс другого.

    Реализация в коде. Класс-обёртка (Wrapper), имеющий private переменную со значением старого класса и конструктор с аргументом в качестве этой переменной. Конструктор — это «вход» адаптера.
  2. Bridge (Мост) — разделяет абстракцию (Player) и реализацию (VideoDecoder), чтобы они могли меняться независимо.

    Реализация в коде. Интерфейсы Player (абстракция) и VideoDecoder (реализация); DefaultPlayer держит поле decoder: VideoDecoder (композиция) и делегирует ему работу. Можно подставлять Av1Decoder или H264Decoder без изменений в DefaultPlayer.
  3. Composite (Компоновщик) — иерархия, позволяющая единообразно работать с отдельными объектами и их группами (например, коллекции фильмов и подборок).

    Реализация в коде. Общий интерфейс для отдельных объектов (фильмов) и их контейнеров (подборок).
  4. Decorator (Декоратор) — динамически добавляет функциональность без изменения класса (например, PremiumMovie с бонусными материалами). Представляет гибкую альтернативу наследованию для расширения функционала.

    Реализация в коде. Класс-обёртка (Wrapper), реализующий тот же интерфейс, что и оборачиваемый объект.
  5. Facade (Фасад) — единая точка входа в сложную подсистему (CinemaFacade.startMovie()).

    Реализация в коде. Класс-фасад, инкапсулирующий вызовы других компонентов через композицию.
  6. Proxy (Заместитель) — подставной объект, который контролирует доступ (например, ParentalControlProxy). Полезен для логирования, кэширования, контроля прав доступа, мониторинга, управления сетевыми соединениями, «ленивой» инициализации.

    Реализация в коде. Класс-обёртка (Wrapper), перехватывающий вызовы (композиция).
  7. Flyweight (Легковес) — экономия памяти за счёт разделения общих данных между множеством объектов (например, MovieMetadata).

    Реализация в коде. Неизменяемый класс MovieMetadata (название, актёры, постер) и фабрика MovieMetadataFactory с кэшем Map<movieId, MovieMetadata>. Метод get(movieId) возвращает уже созданный объект или создаёт и кладёт его в кэш. Изменяемые данные (позиция в списке, оценка пользователя) не храним в легковесе — передаём в методы как параметры.

Поведенческие паттерны (Behavioral)

Отвечают за взаимодействие объектов, управление их поведением и распределение ответственности.

  1. Observer (Наблюдатель) — подписка на события (например, User подписан на уведомления о новых фильмах).

    Реализация в коде. Интерфейс для наблюдателей и список наблюдателей внутри субъекта.
  2. Strategy (Стратегия) — выбор алгоритма (RecommendationStrategy) в зависимости от контекста. Позволяет динамически изменять поведение объекта, выбирая нужный алгоритм во время выполнения.

    Реализация в коде. Интерфейс для семейства стратегий и класс-контекст, содержащий текущую стратегию.
  3. Command (Команда) — оборачивает действие в объект (PlayMovieCommand) для отложенного выполнения или отмены.

    Реализация в коде. Интерфейс с единственным методом execute() и класс-команда, реализующий его.
  4. Template Method (Шаблонный метод) — общий шаблон, позволяющий подклассам переопределять шаги алгоритма, не меняя его структуру.

    Реализация в коде. Абстрактный класс, содержащий «скелет» алгоритма, и подкласс, реализующий методы абстрактного класса.
  5. Iterator (Итератор) — вынесение способов обхода элементов коллекции в отдельный класс, отделяет алгоритм обхода от структуры коллекции.

    Реализация в коде. Отдельный класс-итератор, который определяет методы для обхода коллекции.
  6. Mediator (Посредник) — выносит ответственность за взаимодействие между объектами в отдельный класс, уменьшая связанность.

    Реализация в коде. Интерфейс или абстрактный класс, который определяет методы взаимодействия между объектами. Конкретный посредник — класс-наследник или класс, имплементирующий интерфейс. Классы-«коллеги» взаимодействуют друг с другом через посредника.
  7. Chain of Responsibility (Цепочка обязанностей) — позволяет передавать запросы по цепочке обработчиков. Каждый обработчик решает, может ли он обработать запрос, и либо обрабатывает его, либо передаёт следующему обработчику в цепочке.

    Реализация в коде. Абстрактный класс или интерфейс с методом для обработки запроса и ссылкой на следующий обработчик.
  8. State (Состояние) — изменяет поведение объекта в зависимости от его внутреннего состояния (Player в состояниях «воспроизводит», «на паузе»).

    Реализация в коде. Интерфейс для состояний и класс-контекст, содержащий текущее состояние.
  9. Visitor (Посетитель) — добавление новых операций без изменения классов (AnalyticsVisitor обходит фильмы и считает статистику).

    Реализация в коде. Интерфейсы для посетителя и метод accept(Visitor) в посещаемых классах.
  10. Memento (Хранитель) — сохранение и восстановление внутреннего состояния (позиция просмотра фильма).

    Реализация в коде. Объект-снимок (Memento) и класс-хранитель (Caretaker).
  11. Interpreter (Интерпретатор) — разбирает выражения (фильтры поиска по запросу пользователя).

    Реализация в коде. Классы для элементов грамматики и парсер.

А теперь вопросы, который часто задают на собеседованиях.

В чем разница между Стартегией и Командой?

Ответ:
Стратегия
— это выбор алгоритма под одним интерфейсом («как именно выполнить одну и ту же операцию»). Стратегия используется, когда нужны взаимозаменяемые алгоритмы.
Команда — это «упакованное действие/запрос» в объект («что сделать и когда»), которое можно ставить в очередь, логировать и отменять (undo). Команда используется, когда нужно представлять действия, как объекты (очереди, макрокоманды, undo/redo).

Пример:
Strategy:
RecommendationStrategy — разные алгоритмы рекомендаций (для новичка, киномана или детского профиля) с общим методом recommend(user).
Command: PlayCommand, PauseCommand, SeekCommand — объекты-действия для плеера; UI кладёт их в историю, можно повторить или отменить.

Чем отличается Visitor от Decorator?

Ответ:
Decorator расширяет поведение одного объекта динамически, не изменяя его класс. Декоратор применяется, когда нужно добавить поведение одному объекту и легко включать/отключать функционал во время выполнения.
Visitor добавляет новые операции для множества классов, не изменяя их код. Посетитель применяется, когда нужно добавить новые операции для множества разных типов объектов; классы стабильны, а операции часто меняются.

Пример:
Decorator: У нас есть Movie, а мы хотим добавить премиум-дополнения (комментарии режиссёра, бонусные сцены). Создаём PremiumMovieDecorator, который оборачивает Movie и добавляет новые возможности.
Visitor: Создаём AnalyticsVisitor, который обходит Movie, Series, Trailer и собирает статистику просмотров. Если завтра нужно добавить новую операцию — делаем нового visitor’а, не трогая сами классы.

Заключение

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

Не бойтесь применять эти принципы постепенно — с маленьких проектов. Так вы не просто пишете код, а проектируете решения, которые выдержат испытание временем.

Рекомендуем к прочтению:
https://refactoring.guru/ru/design-patterns/catalog - прекрасно иллюстрированный и понятно объясняющий каталог паттернов проектирования.

https://www.digitalocean.com/community/tutorials/gangs-of-four-gof-design-patterns - статья на английском про паттерны GoF.

Паттерны GoF(Банда 4) - Backend interview - статья на русском языке про паттерны GoF.

👉Подписывайтесь на наш канал в Telegram и группу ВК и скачивайте удобные карточки с паттернами GRASP и GoF.

Наука
7 млн интересуются