December 20, 2025 (8mo ago) — last updated June 18, 2026 (2mo ago)

Паттерны проектирования в ООП: практическое руководство

Изучите ключевые паттерны ООП — порождающие, структурные и поведенческие — с практическими примерами на TypeScript и советами по рефакторингу.

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

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

Паттерны проектирования в ООП: практическое руководство

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

Введение

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

Твердое владение ключевыми паттернами и принципами ООП, такими как полиморфизм и наследование, позволяет безопасно рефакторить унаследованный код и быстрее общаться с коллегами о дизайне системы. Многие практики опираются на классический набор из 23 паттернов, описанный в «Gang of Four»1, а академические исследования подтверждают преимущества повторного использования паттернов в больших проектах2.

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

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

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

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

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

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

Для практического освежения можно посмотреть сравнение полиморфизма и наследования в нашем гайде по полиморфизму и наследованию: polymorphism vs inheritance.

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

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

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

Обзор категорий

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

Порождающие паттерны: контроль создания объектов

Порождающие паттерны вводят абстракцию над созданием объектов, чтобы клиентский код не зависел от конкретных классов.

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

Singleton — один экземпляр для общего ресурса

Singleton гарантирует один экземпляр класса и глобальную точку доступа. Подходит для логгера или подключения к БД, но добавляет глобальное состояние, что осложняет тестирование и управление зависимостями4.

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

class DatabaseConnection {
  private static instance: DatabaseConnection;

  private constructor() {
    // 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: используйте его для действительно глобальных ресурсов, а для тестируемости предпочитайте внедрение зависимостей4.

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();

Структурные паттерны: гибкая компоновка

Структурные паттерны упрощают взаимодействие компонентов и делают систему более адаптируемой.

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

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 — уведомления подписчикам

Observer устанавливает отношение «один-ко-многим», при котором изменения в Subject автоматически рассылаются подписчикам.

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

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 обеспечивает слабую связанность: Subject не зависит от конкретных реализаций Observer.

Strategy — подмена алгоритмов во время выполнения

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

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

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);

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

Не достаточно просто знать паттерн, важно не злоупотреблять ими. Избегайте анти-паттернов, таких как God Object, и следите за «запахами кода»: слишком длинные методы, избыточные зависимости, монстр-классы. Рефакторинг улучшает структуру без изменения поведения, позволяя безопасно упорядочивать унаследованные системы.

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

Когда вы видите длинный switch или if/else, выделите изменчивое поведение в интерфейс Strategy и конкретные классы стратегий. Это улучшит расширяемость и тестируемость, а также поможет следовать принципам SOLID5.

Частые вопросы (Q&A)

В: С каких паттернов начать изучение?

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

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

О: Не вводите паттерн «на всякий случай». Если паттерн добавляет сложность без реальной пользы, отложите его внедрение, следуя принципу YAGNI.

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

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


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

1.
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
2.
W. G. Griswold et al., “A Formal Foundation for Design Pattern Abstraction and Reuse,” Proceedings of ECOOP ’93, University of California, San Diego. https://cseweb.ucsd.edu/~wgg/CSE210/ecoop93-patterns.pdf
3.
Исследования и обзоры указывают, что значительная часть затрат на ПО приходится на сопровождение, обычно в диапазоне 40–80% жизненного цикла. См. «Software maintenance.» https://en.wikipedia.org/wiki/Software_maintenance
4.
Martin Fowler, “Singleton,” Bliki. https://martinfowler.com/bliki/Singleton.html
5.
Robert C. Martin (Uncle Bob), “The SOLID Principles.” https://8thlight.com/blog/uncle-bob/2012/08/13/the-s-o-l-i-d-principles.html
← Back to blog
🙋🏻‍♂️

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

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