December 20, 2025 (4mo ago)

Практическое руководство по шаблонам проектирования в ООП

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

← Back to blog
Cover Image for Практическое руководство по шаблонам проектирования в ООП

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

Практическое руководство по шаблонам проектирования в ООП

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

Введение

Шаблоны проектирования в объектно-ориентированном программировании — это проверенные, переиспользуемые шаблоны решения повторяющихся задач проектирования. Это не код для копирования и вставки; это адаптируемые шаблоны, которые помогают структурировать классы и объекты так, чтобы ваш код было проще поддерживать, расширять и тестировать.

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

Что такое шаблоны проектирования в объектно-ориентированном программировании

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

Эскиз поварского колпака, роняющего золотые буквы в открытую кулинарную книгу.

Общий язык разработчиков

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

«Design Patterns: Elements of Reusable Object-Oriented Software», опубликованная в 1994 году Эрихом Гаммой, Ричардом Хелмом, Ральфом Джонсоном и Джоном Влиссидесом, каталогизировала 23 фундаментальных паттерна, которые и поныне остаются обязательным чтением для практиков ООП2. Эти паттерны опираются на более ранние академические исследования, показывающие измеримые преимущества в продуктивности и переиспользовании в крупных проектах1.

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

Уверенное владение ключевыми принципами ООП, такими как полиморфизм и наследование, помогает эффективно применять паттерны. Для практического освежения взгляните на наше руководство, сравнивающее polymorphism vs inheritance.

Три основные категории шаблонов проектирования

Gang of Four сгруппировали паттерны в три категории: порождающие, структурные и поведенческие. Знание этих категорий помогает быстро выбрать правильный подход к задаче.

Диаграмма, иллюстрирующая порождающие, структурные и поведенческие паттерны проектирования с простыми блоковыми иллюстрациями.

Обзор категорий шаблонов проектирования ООП

Категория паттернаОсновная цельРаспространенные примеры
ПорождающиеУправление и абстракция создания объектовFactory, Builder, Singleton, Prototype
СтруктурныеКомпоновка классов и объектов в большие гибкие структурыAdapter, Decorator, Facade, Composite
ПоведенческиеОпределение того, как объекты взаимодействуют и общаютсяObserver, Strategy, Command, Iterator

Порождающие паттерны: специалисты по созданию

Порождающие паттерны контролируют процесс создания объектов, чтобы клиентский код не был жестко связан с конкретными классами. Они скрывают логику создания, повышают гибкость и позволяют управлять инстанцированием (например, обеспечивая единственный экземпляр с помощью Singleton).

Структурные паттерны: архитектурный клей

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

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

Поведенческие паттерны: режиссеры коммуникации

Поведенческие паттерны управляют взаимодействием и ответственностями объектов. Они создают чистые каналы коммуникации — Observer для событийной рассылки и Strategy для подмены алгоритмов без изменения клиентов.

Тщательно управляя путями коммуникации, вы снижаете зависимости и облегчаете понимание и сопровождение кодовой базы.

Создание объектов с помощью порождающих паттернов

Порождающие паттерны вводят уровень абстракции вокруг создания объектов, чтобы вы могли менять реализации без изменения клиентского кода. Ниже два практических примера на TypeScript: Singleton и Factory Method.

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

Паттерн Singleton: обеспечение одного истинного экземпляра

Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа. Это полезно для общих ресурсов, таких как подключение к базе данных или логгер, но может вводить глобальное состояние, которое усложняет тестирование и управление зависимостями3.

Пример: Singleton на TypeScript для подключения к базе данных.

class DatabaseConnection {
  private static instance: DatabaseConnection;

  private constructor() {
    // A private constructor prevents external 'new' calls
    console.log("Connecting to the database...");
  }

  public static getInstance(): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }

  public query(sql: string): void {
    console.log(`Executing query: ${sql}`);
  }
}

// Usage
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();

db1.query("SELECT * FROM users");
console.log(db1 === db2); // true

Компромисс Singleton: используйте Singletons для действительно глобальных ресурсов, а для лучшей тестируемости и модульности предпочитайте внедрение зависимостей3.

Factory Method: позволить подклассам решать, что создавать

Factory Method определяет интерфейс для создания объекта, а подклассы решают, какой конкретный продукт инстанцировать. Это разъединяет клиентский код от конкретных классов, делая систему проще в расширении.

Пример: отрисовка кнопок, специфичных для ОС, на TypeScript.

interface Button {
  render(): void;
  onClick(f: () => void): void;
}

class WindowsButton implements Button {
  render() { console.log("Rendering a button in Windows style."); }
  onClick(f: () => void) { console.log("Windows button click event."); f(); }
}

class MacButton implements Button {
  render() { console.log("Rendering a button in macOS style."); }
  onClick(f: () => void) { console.log("Mac button click event."); f(); }
}

abstract class Dialog {
  abstract createButton(): Button;

  render() {
    const okButton = this.createButton();
    okButton.render();
  }
}

class WindowsDialog extends Dialog {
  createButton(): Button { return new WindowsButton(); }
}

class MacDialog extends Dialog {
  createButton(): Button { return new MacButton(); }
}

// Client
const os: string = "windows";
let dialog: Dialog;
if (os === "windows") dialog = new WindowsDialog(); else dialog = new MacDialog();
dialog.render();

Factory Method позволяет создателю не знать о конкретных продуктах, делая добавление, например, LinuxDialog простым.

Построение гибких систем со структурными паттернами

Структурные паттерны помогают компоновке объектов в надежные, адаптируемые системы. Два практических выбора — Adapter и Decorator.

Нарисованные рукой иллюстрации электрического адаптера и стопки колец, представляющих паттерны проектирования ООП.

Паттерн Adapter: мост между несовместимыми интерфейсами

Adapter оборачивает несовместимый интерфейс, чтобы он соответствовал тому, что ожидает ваша система. Это позволяет избежать вмешательства в наследуемый код.

Пример: адаптация ModernLogger к существующему интерфейсу ILogger.

class ModernLogger {
  public logInfo(message: string): void {
    console.log(`[INFO]: ${message}`);
  }
}

interface ILogger { log(message: string): void; }

class LoggerAdapter implements ILogger {
  private modernLogger: ModernLogger;
  constructor() { this.modernLogger = new ModernLogger(); }
  public log(message: string): void { this.modernLogger.logInfo(message); }
}

const logger: ILogger = new LoggerAdapter();
logger.log("User logged in successfully.");

Паттерн Decorator: динамическое добавление функциональности

Decorator добавляет ответственность объектам во время выполнения, оборачивая их. Это гибче, чем наследование, и следует принципу единственной ответственности.

Пример: компоновка подписки с дополнительными опциями.

interface Subscription { getDescription(): string; getCost(): number; }

class BasicSubscription implements Subscription {
  getDescription(): string { return "Basic Plan"; }
  getCost(): number { return 10; }
}

abstract class SubscriptionDecorator implements Subscription {
  protected subscription: Subscription;
  constructor(subscription: Subscription) { this.subscription = subscription; }
  abstract getDescription(): string;
  abstract getCost(): number;
}

class PremiumSupportDecorator extends SubscriptionDecorator {
  getDescription(): string { return `${this.subscription.getDescription()}, Premium Support`; }
  getCost(): number { return this.subscription.getCost() + 5; }
}

class CloudStorageDecorator extends SubscriptionDecorator {
  getDescription(): string { return `${this.subscription.getDescription()}, 1TB Cloud Storage`; }
  getCost(): number { return this.subscription.getCost() + 7; }
}

let mySubscription: Subscription = new BasicSubscription();
mySubscription = new PremiumSupportDecorator(mySubscription);
mySubscription = new CloudStorageDecorator(mySubscription);
console.log(mySubscription.getDescription());
console.log(mySubscription.getCost());

Такой композиторный подход делает функции модульными и простыми для тестирования.

Управление взаимодействиями с поведенческими паттернами

Поведенческие паттерны организуют коммуникацию объектов так, чтобы системы оставались гибкими и удобными в сопровождении. Два ключевых примера — Observer и Strategy.

Паттерн Observer: уведомление заинтересованных сторон

Observer устанавливает отношение «один-ко-многим», так что когда Subject меняет состояние, Observers уведомляются автоматически. Это идеально для событийно-ориентированных систем.

Пример: простой сервис уведомлений.

interface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(): void; }
interface Observer { update(subject: Subject): void; }

class NotificationService implements Subject {
  public state: string = '';
  private observers: Observer[] = [];
  attach(observer: Observer): void { this.observers.push(observer); }
  detach(observer: Observer): void { const i = this.observers.indexOf(observer); if (i !== -1) this.observers.splice(i, 1); }
  notify(): void { for (const o of this.observers) o.update(this); }
  public createNewPost(title: string): void { this.state = `New Post: ${title}`; console.log(`\nNotificationService: A new post was created.`); this.notify(); }
}

class EmailNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`EmailNotifier: Sending email about "${subject.state}"`); } }
class PushNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`PushNotifier: Sending push notification for "${subject.state}"`); } }

const notificationService = new NotificationService();
const emailer = new EmailNotifier();
const pusher = new PushNotifier();
notificationService.attach(emailer);
notificationService.attach(pusher);
notificationService.createNewPost("Understanding Observer Pattern");
notificationService.detach(pusher);
notificationService.createNewPost("Why Strategy is Awesome");

Observer обеспечивает слабо связанный дизайн: Subjects не обязаны знать конкретные Observers, которых они уведомляют.

Паттерн Strategy: инкапсуляция алгоритмов

Strategy позволяет подменять алгоритмы во время выполнения, избегая больших условных блоков. Он соответствует принципу открытости/закрытости, позволяя добавлять новые стратегии без изменения существующего кода4.

Пример: стратегии оплаты для корзины покупок.

interface PaymentStrategy { pay(amount: number): void; }
class CreditCardStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} with Credit Card.`); } }
class PayPalStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} via PayPal.`); } }

class ShoppingCart {
  private paymentStrategy: PaymentStrategy;
  constructor(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
  public setPaymentStrategy(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
  public checkout(amount: number): void { this.paymentStrategy.pay(amount); }
}

const cart = new ShoppingCart(new CreditCardStrategy());
cart.checkout(150);
cart.setPaymentStrategy(new PayPalStrategy());
cart.checkout(150);

Strategy устраняет условную сложность и делает системы проще для расширения.

Распространенные ошибки проектирования и стратегии рефакторинга

Выбор паттерна — это лишь половина дела. Избегайте анти-паттернов, таких как God Object, который сосредотачивает на себе обязанности и нарушает принцип единственной ответственности. Ищите «запахи кода» — длинные методы, избыточные зависимости или монстр-классы — и решайте их дисциплинированным рефакторингом.

Рефакторинг — это улучшение внутренней структуры без изменения внешнего поведения. Так вы безопасно укрощаете унаследованные системы.

Рефакторинг в паттерн Strategy

Большой switch или длинная цепочка if/else — классический «запах». Рефакторьте, извлекая изменяющееся поведение в интерфейс Strategy с конкретными классами стратегий. Замените условие одним вызовом метода выбранной стратегии. Это улучшает расширяемость, тестируемость и соответствие принципам SOLID4.

Ответы на ваши вопросы о шаблонах проектирования ООП

Сколько паттернов мне стоит выучить?

Сосредоточьтесь сначала на 5–7 ключевых паттернах: Singleton, Factory, Adapter, Decorator, Observer и Strategy. Разберитесь, какую проблему решает каждый паттерн; как только вы это усвоите, изучать дополнительные паттерны станет значительно проще.

Когда стоит избегать использования паттерна?

Избегайте паттернов, которые добавляют сложность без реального решения проблемы. Не вводите паттерн «на всякий случай». Следуйте YAGNI: добавляйте паттерны, когда есть очевидная и текущая потребность.

Можно ли использовать паттерны проектирования с функциональным программированием?

Да. Многие паттерны естественно сопоставляются с функциональными техниками. Strategy может быть реализован как передача функций-параметров, а Decorator — как функции высшего порядка. Главное — применять основную идею, а не жестко копировать формы ООП.


В Clean Code Guy мы помогаем командам строить ПО, которое служит долго, внедряя базовые принципы в их рабочие процессы. Узнайте, как наши аудиты кода и рефакторинг, готовый к использованию с ИИ, могут дать вашей команде уверенность при релизах на https://cleancodeguy.com.

1.
W. G. Griswold et al., «A Formal Foundation for Design Pattern Abstraction and Reuse», Труды ECOOP ’93, Университет Калифорнии, Сан-Диего. https://cseweb.ucsd.edu/~wgg/CSE210/ecoop93-patterns.pdf
2.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). https://en.wikipedia.org/wiki/Design_Patterns
3.
Martin Fowler, «Singleton», Bliki, обсуждает компромиссы Singleton и влияние на тестирование. https://martinfowler.com/bliki/Singleton.html
4.
Robert C. Martin (Uncle Bob), «The SOLID Principles», которые объясняют Open/Closed и связанные цели проектирования. https://8thlight.com/blog/uncle-bob/2012/08/13/the-s-o-l-i-d-principles.html
← Back to blog
🙋🏻‍♂️

ИИ пишет код.
Вы делаете его долговечным.

В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.

Практическое руководство по шаблонам проектирования в ООП | Clean Code Guy