← Все темы

Шаблоны проектирования

Вопросов: 29

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

Зачем он нужен?
- Упрощение разработки: Предоставляет готовые решения, сокращая время разработки.
- Повышение качества кода: Способствует использованию проверенных практик.
- Улучшение поддерживаемости: Делает код более понятным и легко расширяемым.
- Снижение сложности: Помогает организовать код и разделить ответственность между компонентами.

Шаблоны проектирования и алгоритмы служат разным целям в разработке программного обеспечения.

- Алгоритмы — это строго определенные шаги или процедуры для решения конкретных задач или выполнения определенных операций. Они описывают как достичь результата, например, сортировку данных или поиск пути.

- Шаблоны проектирования — это проверенные временем структурные решения для общих проблем проектирования в разработке программного обеспечения. Они предоставляют архитектурные подходы к организации кода и компонентов, такие как Singleton, Observer или Factory.

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

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

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

Основные отличия:
- Назначение:
- Шаблоны проектирования предоставляют конкретные решения для определённых задач.
- Принципы разработки дают общие направления и рекомендации для построения качественного кода.
- Уровень абстракции:
- Шаблоны более конкретны и применимы к специфическим проблемам.
- Принципы более абстрактны и универсальны, применимы к различным аспектам разработки.
- Использование:
- Шаблоны часто основаны на принципах и реализуют их на практике.
- Принципы могут применяться независимо от использования конкретных шаблонов.

Примеры:
- Принципы разработки: SOLID, DRY, KISS.
- Шаблоны проектирования: Singleton, Factory, Observer.

Заключение: Принципы разработки обеспечивают общие руководства для создания качественного кода, тогда как шаблоны проектирования предлагают конкретные решения для реализации этих принципов в реальных проектах.

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

Недостатки использования шаблонов проектирования:
- Могут привести к избыточной сложности системы при ненужном применении.
- Требуют времени на изучение и понимание различных шаблонов.
- Возможны избыточные абстракции, усложняющие код.
- Могут ограничивать гибкость при нестандартных требованиях проекта.
- Неправильное применение шаблонов может ухудшить качество кода.

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

- Свойства: данные и состояние объекта.
- Методы: поведение и функциональность объекта.
- Взаимодействия: связи и зависимости с другими объектами или компонентами системы.

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

Мы стремимся уменьшить связанность по следующим причинам:

- Упрощение поддержки: Меньшая зависимость облегчает внесение изменений без затрагивания смежных компонентов.
- Повышение повторного использования: Независимые объекты легче использовать в различных контекстах и проектах.
- Улучшение тестируемости: Объекты с низкой связанностью проще изолировать и тестировать отдельно.

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

python
class IService:
def perform(self):
pass

class ServiceA(IService):
def perform(self):
print("ServiceA выполняет действие")

class Client:
def __init__(self, service: IService):
self.service = service

def execute(self):
self.service.perform()

service = ServiceA()
client = Client(service)
client.execute()


В этом примере Client зависит от абстракции IService, а не от конкретной реализации ServiceA, что снижает связанность между объектами.

Существует три основных типа шаблонов проектирования:

- Порождающие: Отвечают за создание объектов, обеспечивая гибкость и контроль над процессом создания.
- Примеры: Singleton, Factory Method, Abstract Factory, Builder, Prototype.

- Структурные: Определяют способы компоновки классов и объектов для формирования сложных структур.
- Примеры: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.

- Поведенческие: Описывают взаимодействие между объектами и распределение обязанностей.
- Примеры: Observer, Strategy, Command, Chain of Responsibility, Iterator, Mediator, Memento, State, Template Method, Visitor.

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

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


Зачем они нужны?
- Управление сложностью создания объектов в больших системах.
- Повышение гибкости и расширяемости кода.
- Снижение связности между компонентами системы.
- Повторное использование кода при создании объектов.


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


Основные порождающие шаблоны:

- Фабричный метод (Factory Method)
- Абстрактная фабрика (Abstract Factory)
- Строитель (Builder)
- Прототип (Prototype)
- Одиночка (Singleton)

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

Структурные шаблоны проектирования

- Что такое: Структурные шаблоны проектирования фокусируются на том, как классы и объекты комбинируются для формирования крупных структур, обеспечивая при этом гибкость и повторное использование кода.

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

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

- Основные шаблоны:
- Adapter — преобразует интерфейс класса в другой интерфейс, который ожидают клиенты.
- Bridge — разделяет абстракцию и её реализацию, позволяя изменять их независимо.
- Composite — позволяет сгруппировать объекты в древовидные структуры для представления иерархий «часть-целое».
- Decorator — динамически добавляет объектам новые обязанности.
- Facade — предоставляет унифицированный интерфейс к набору интерфейсов в подсистеме.
- Flyweight — уменьшает количество объектов за счёт разделения общего состояния.
- Proxy — предоставляет заместитель или суррогат для другого объекта для контроля доступа к нему.

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

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

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

Когда применять: Поведенческие шаблоны применяются, когда необходимо:

- Управлять сложными алгоритмами и потоками управления.
- Обеспечивать эффективное взаимодействие между объектами.
- Разделять обязанности между объектами для повышения гибкости системы.

Примеры поведенческих шаблонов:
- Цепочка обязанностей - позволяет передавать запросы последовательно по цепочке обработчиков.
- Команда - инкапсулирует запрос как объект, позволяя параметризовать клиентов различными запросами.
- Наблюдатель - определяет зависимость "один ко многим" между объектами, уведомляя зависимые объекты об изменениях.
- Стратегия - позволяет выбирать алгоритм поведения во время выполнения.
- Состояние - позволяет объекту изменять свое поведение при изменении его внутреннего состояния.
- Итератор - предоставляет способ последовательного доступа к элементам коллекции без раскрытия ее внутреннего представления.
- Посредник - централизует взаимодействие между объектами, уменьшая их связанность.

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

Когда применять:
- Когда класс не может заранее определить тип создаваемого объекта.
- Нужно отделить код создания объектов от их использования.
- Требуется возможность расширять систему, добавляя новые типы объектов без изменения существующего кода.

Пример для web:
Представим, что у нас есть веб-приложение, которое обрабатывает разные типы уведомлений (Email, SMS).
Мы можем использовать фабричный метод для создания нужного объекта уведомления.


from abc import ABC, abstractmethod

# Абстрактный продукт
class Notification(ABC):
@abstractmethod
def send(self, message: str):
pass

# Конкретные продукты
class EmailNotification(Notification):
def send(self, message: str):
print(f"Отправка Email: {message}")

class SMSNotification(Notification):
def send(self, message: str):
print(f"Отправка SMS: {message}")

# Абстрактный создатель
class NotificationFactory(ABC):
@abstractmethod
def create_notification(self) -> Notification:
pass

def notify(self, message: str):
notification = self.create_notification()
notification.send(message)

# Конкретные создатели
class EmailNotificationFactory(NotificationFactory):
def create_notification(self) -> Notification:
return EmailNotification()

class SMSNotificationFactory(NotificationFactory):
def create_notification(self) -> Notification:
return SMSNotification()

# Использование
def client_code(factory: NotificationFactory, message: str):
factory.notify(message)

if __name__ == "__main__":
client_code(EmailNotificationFactory(), "Привет через Email!")
client_code(SMSNotificationFactory(), "Привет через SMS!")


Итого:
Фабричный метод позволяет удобно расширять функциональность (например, добавлять новые способы уведомлений) без изменений в клиентском коде и основным бизнес-логике.

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

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

Пример на Python:

from abc import ABC, abstractmethod

# Абстрактные продукты
class Button(ABC):
@abstractmethod
def click(self):
pass

class Checkbox(ABC):
@abstractmethod
def check(self):
pass

# Конкретные продукты для Windows
class WindowsButton(Button):
def click(self):
print("Windows button clicked")

class WindowsCheckbox(Checkbox):
def check(self):
print("Windows checkbox checked")

# Конкретные продукты для Mac
class MacButton(Button):
def click(self):
print("Mac button clicked")

class MacCheckbox(Checkbox):
def check(self):
print("Mac checkbox checked")

# Абстрактная фабрика
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass

@abstractmethod
def create_checkbox(self) -> Checkbox:
pass

# Конкретные фабрики
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()

def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()

class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()

def create_checkbox(self) -> Checkbox:
return MacCheckbox()

# Клиентский код
def client_code(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
button.click()
checkbox.check()

# Использование
if __name__ == "__main__":
factory = WindowsFactory()
client_code(factory)

factory = MacFactory()
client_code(factory)


В этом примере абстрактная фабрика `GUIFactory` создает семейства продуктов (кнопки и чекбоксы) для разных платформ (Windows и Mac) без указания их конкретных классов. Клиентский код работает с фабрикой через абстрактный интерфейс, что позволяет легко переключаться между различными семействами продуктов.

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

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

Преимущества:
- Позволяет создавать объекты пошагово.
- Упрощает создание сложных объектов.

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


Пример на Python:
class Product:
def __init__(self):
self.parts = []

def add(self, part):
self.parts.append(part)

def show(self):
print("Product parts:", self.parts)

class Builder:
def __init__(self):
self.product = Product()

def build_part_a(self):
self.product.add("Part A")
return self

def build_part_b(self):
self.product.add("Part B")
return self

def get_product(self):
return self.product

# Использование
builder = Builder()
product = builder.build_part_a().build_part_b().get_product()
product.show()


Вывод:
Product parts: ['Part A', 'Part B']

В этом примере класс Builder поэтапно строит объект Product, добавляя различные части. Это позволяет гибко создавать объекты с разными конфигурациями.

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

Когда применять:
- Нужно создавать много похожих объектов с некоторыми изменениями.
- Создание объектов ресурсоёмкое или сложно.
- Требуется избежать прямого использования класса-конструктора и упростить клонирование.

Пример на Python для backend:

import copy

class JobConfig:
def __init__(self, retries, timeout, priority):
self.retries = retries
self.timeout = timeout
self.priority = priority

def clone(self):
return copy.deepcopy(self)

# Базовая конфигурация задачи (прототип)
base_config = JobConfig(retries=3, timeout=60, priority='high')

# Клонируем прототип и изменяем параметры для новой задачи
new_job = base_config.clone()
new_job.timeout = 120


Объяснение:
base_config — исходный объект, служащий прототипом. Метод clone() создаёт глубокую копию объекта, благодаря чему новая задача new_job может иметь свои уникальные параметры, не влияя на исходный прототип. Такой подход упрощает создание вариантов объектов и следование принципам повторного использования.

Шаблон Одиночка (Singleton) гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

Когда применять:
- Когда необходимо ограничить создание объекта одним экземпляром.
- Для управления доступом к общему ресурсу, например, к базе данных или конфигурационному файлу.
- Когда нужен централизованный контроль над некоторыми действиями или данными.

Пример на Python:


class Singleton:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance

# Использование
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # Выведет: True


Преимущества:
- Контроль над созданием экземпляров.
- Экономия ресурсов при использовании единственного ресурса.

Недостатки:
- Может скрывать зависимости в коде.
- Труднее тестировать из-за глобального состояния.

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

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

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

Пример использования в Python:


class Target:
def request(self):
return "Target: Вызов метода request"

class Adaptee:
def specific_request(self):
return "Adaptee: Вызов специфического запроса"

class Adapter(Target):
def __init__(self, adaptee):
self.adaptee = adaptee

def request(self):
return f"Adapter: {self.adaptee.specific_request()}"

# Клиентский код
adaptee = Adaptee()
adapter = Adapter(adaptee)
print(adapter.request())


В этом примере Adapter приводит интерфейс Adaptee в соответствие с интерфейсом Target, позволяя клиенту взаимодействовать с адаптируемым классом без изменения его кода.

Шаблон Мост (Bridge) — это структурный шаблон проектирования, который разделяет абстракцию и её реализацию так, чтобы они могли изменяться независимо друг от друга.

Когда применять:
- Нужно избегать «жёсткой» связи между абстракцией и реализацией.
- Требуется возможность изменять реализацию и абстракцию независимо.
- Нужно работать с несколькими вариантами реализации, избегая множества подклассов.

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

Пример из web:
Допустим, у нас есть абстракция Отображение данных, а реализации — разные способы получения данных: через REST API или через WebSocket.


class DataFetcher:
def fetch(self):
raise NotImplementedError

class RESTFetcher(DataFetcher):
def fetch(self):
return "Данные через REST API"

class WebSocketFetcher(DataFetcher):
def fetch(self):
return "Данные через WebSocket"

class DataView:
def __init__(self, fetcher: DataFetcher):
self.fetcher = fetcher

def show(self):
data = self.fetcher.fetch()
return f"<div>Отображение: {data}</div>"

# Использование
rest_view = DataView(RESTFetcher())
ws_view = DataView(WebSocketFetcher())

print(rest_view.show()) # Отображение данных через REST
print(ws_view.show()) # Отображение данных через WebSocket


В результате:
- DataView — абстракция отображения данных.
- DataFetcher и его наследники — реализация способов получения данных.
- Можно добавить новые методы получения данных без изменения интерфейса отображения.

Таким образом, шаблон Бридж помогает сделать архитектуру гибкой и расширяемой.

Шаблон Компоновщик (Composite) используется для построения иерархий объектов в виде дерева, где отдельные объекты и их группы обрабатываются одинаково. Это позволяет клиентскому коду работать с отдельными элементами и их композициями единообразно.

Когда применять:
- При необходимости представить иерархическую структуру в виде дерева.
- Когда нужно, чтобы объекты и их контейнеры имели единый интерфейс.
- Для упрощения кода, обрабатывающего структуры с вложенными элементами.

Пример для backend web (Python, Flask-подобный сервис):
Представим, что у нас есть система управления правами доступа, где роли могут содержать другие роли и права. Нужно проверять, есть ли у пользователя определённое право, учитывая вложенность ролей.


from abc import ABC, abstractmethod

class PermissionComponent(ABC):
@abstractmethod
def has_permission(self, perm: str) -> bool:
pass

class Permission(PermissionComponent):
def __init__(self, name: str):
self.name = name

def has_permission(self, perm: str) -> bool:
return self.name == perm

class Role(PermissionComponent):
def __init__(self, name: str):
self.name = name
self.children = []

def add(self, component: PermissionComponent):
self.children.append(component)

def has_permission(self, perm: str) -> bool:
for child in self.children:
if child.has_permission(perm):
return True
return False

# Использование:

read_perm = Permission("read")
write_perm = Permission("write")

user_role = Role("User")
user_role.add(read_perm)

admin_role = Role("Admin")
admin_role.add(user_role)
admin_role.add(write_perm)

# Проверка прав:
print(user_role.has_permission("write")) # False
print(admin_role.has_permission("write")) # True


Итого:
Компоновщик облегчает работу с иерархическими структурами (например, роли/права, меню, файлы/папки), позволяя единообразно обращаться как к отдельным элементам, так и к их группам.

Шаблон Декоратор позволяет динамически добавлять объектам новые обязанности без изменения их структуры. Этот шаблон структурного типа упрощает расширение функциональности объектов путем обертывания их в дополнительные классы-декораторы.

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

Пример на Python с использованием встроенных механизмов:


def decorator(func):
def wrapper(*args, **kwargs):
print("Перед вызовом функции")
result = func(*args, **kwargs)
print("После вызова функции")
return result
return wrapper

@decorator
def say_hello(name):
print(f"Hello, {name}!")

say_hello("Мир")


Выход:
- Перед вызовом функции
- Hello, Мир!
- После вызова функции

Шаблон Фасад (Facade) относится к структурным шаблонам проектирования. Он предоставляет упрощённый интерфейс к сложной системе классов, библиотекам или фреймворкам, скрывая сложность системы от клиента.

Цель: Сократить зависимость клиента от внутренних компонентов системы, облегчить использование и повысить уровень абстракции.

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

Пример реализации на Python:

class SubsystemA:
def operation_a1(self):
return "SubsystemA: Operation A1"

class SubsystemB:
def operation_b1(self):
return "SubsystemB: Operation B1"

class Facade:
def __init__(self):
self._subsystem_a = SubsystemA()
self._subsystem_b = SubsystemB()

def simple_operation(self):
result = []
result.append(self._subsystem_a.operation_a1())
result.append(self._subsystem_b.operation_b1())
return " | ".join(result)

# Использование фасада
facade = Facade()
print(facade.simple_operation())


В данном примере Facade объединяет операции двух подсистем и предоставляет простой метод simple_operation, который скрывает сложность взаимодействия с каждой подсистемой отдельно.

Шаблон Легковес (Flyweight) — это структурный паттерн проектирования, который позволяет эффективно использовать память путем разделения общих частей состояния между множеством объектов.

Когда применять:
- Если нужно создать большое количество похожих объектов.
- Если значительная часть состояния объектов общая и может быть разделена.
- Для уменьшения расхода памяти и повышения производительности.

Основная идея: разделить объект на две части:
- Внутреннее состояние — разделяемое и неизменное, хранится в легковесе.
- Внешнее состояние — уникальное для каждого клиента, передается при использовании.

Пример для backend web на Python:
Допустим, у нас есть система обработки пользовательских запросов, где много пользователей используют одинаковые настройки конфигурации.


class Config:
def __init__(self, setting):
self.setting = setting # Внутреннее состояние - разделяемое

class ConfigFactory:
_configs = {}

@classmethod
def get_config(cls, setting):
if setting not in cls._configs:
cls._configs[setting] = Config(setting)
return cls._configs[setting]

class UserRequest:
def __init__(self, user_id, config_setting):
self.user_id = user_id # Внешнее состояние
self.config = ConfigFactory.get_config(config_setting)

def process(self):
# Использует разделяемую конфигурацию
print(f"User {self.user_id} with setting {self.config.setting} is processed")

# Использование:
req1 = UserRequest(1, 'dark_mode')
req2 = UserRequest(2, 'dark_mode')
req3 = UserRequest(3, 'light_mode')

req1.process()
req2.process()
req3.process()

# req1.config и req2.config указывают на один объект Config


В результате:
- Экземпляры Config создаются только для уникальных настроек.
- Множество UserRequest используют общий объект конфигурации, экономя память.

Итог: шаблон Легковес полезен для оптимизации памяти при большом числе похожих объектов с общим состоянием.

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

Когда применять шаблон Прокси:
- Контроль доступа: Когда необходимо ограничить доступ к реальному объекту, например, для реализации уровней доступа или аутентификации.
- Ленивая инициализация: Когда создание или загрузка реального объекта требует значительных ресурсов, и его стоит отложить до момента фактического использования.
- Удаленный доступ: Для представления объекта, находящегося в другом адресном пространстве, например, в другой системе или процессе.
- Кэширование: Для хранения результатов дорогостоящих операций и предотвращения повторных вызовов к реальному объекту.
- Логирование и мониторинг: Для отслеживания вызовов методов и сбора статистики о взаимодействиях с объектом.

Пример реализации Прокси на Python:

from abc import ABC, abstractmethod

class Subject(ABC):
@abstractmethod
def request(self):
pass

class RealSubject(Subject):
def request(self):
return "Реальный объект выполняет запрос."

class Proxy(Subject):
def __init__(self, real_subject):
self._real_subject = real_subject

def request(self):
# Дополнительная функциональность
print("Прокси: проверка доступа перед запросом.")
return self._real_subject.request()

# Использование
real = RealSubject()
proxy = Proxy(real)
print(proxy.request())


В этом примере класс Proxy контролирует доступ к RealSubject, добавляя проверку перед выполнением запроса.

Шаблон Цепочка обязанностей (Chain of Responsibility) является поведенческим шаблоном проектирования, который позволяет передавать запросы последовательно по цепочке обработчиков до тех пор, пока один из них не обработает запрос. Это позволяет избежать жесткой привязки отправителя запроса к его получателю и поддерживать гибкость в выборе обработчиков.

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

Пример на Python:
class Handler:
def __init__(self, successor=None):
self.successor = successor

def handle(self, request):
handled = self.process(request)
if not handled and self.successor:
self.successor.handle(request)

def process(self, request):
raise NotImplementedError

class ConcreteHandlerA(Handler):
def process(self, request):
if request == "A":
print("Handler A обработал запрос")
return True
return False

class ConcreteHandlerB(Handler):
def process(self, request):
if request == "B":
print("Handler B обработал запрос")
return True
return False

# Создаем цепочку
handler = ConcreteHandlerA(ConcreteHandlerB())

# Отправляем запросы
handler.handle("A") # Выведет: Handler A обработал запрос
handler.handle("B") # Выведет: Handler B обработал запрос
handler.handle("C") # Ничего не выведет


В данном примере запросы передаются по цепочке обработчиков `ConcreteHandlerA` и `ConcreteHandlerB`, пока один из них не обработает запрос.

Шаблон Команда (Command) — это поведенческий шаблон проектирования, который инкапсулирует запрос как отдельный объект. Такой объект содержит всю информацию, необходимую для выполнения операции, что позволяет отделить отправителя запроса от получателя.

Когда применять:
- Нужно параметризовать объекты операциями.
- Реализовать очередь запросов, логирование, отмену/повтор команд.
- Разделить обязанности между отправителем и исполнителем.
- Вызов операций должен быть отложен или выполнен асинхронно.

Пример для бэкенда с Kafka:
Представим, что у нас есть сервис, который отправляет сообщения в Kafka через объект-команду.


from abc import ABC, abstractmethod

# Интерфейс команды
class Command(ABC):
@abstractmethod
def execute(self):
pass

# Реализация команды — отправка сообщения в Kafka
class SendKafkaMessageCommand(Command):
def __init__(self, producer, topic, message):
self.producer = producer
self.topic = topic
self.message = message

def execute(self):
self.producer.send(self.topic, self.message)
self.producer.flush()

# Клиентский код
class KafkaProducerMock:
def send(self, topic, message):
print(f"Отправлено в тему '{topic}': {message}")

def flush(self):
print("Очистка буфера продюсера")

# Использование
producer = KafkaProducerMock()
command = SendKafkaMessageCommand(producer, "events", {"event": "user_signup", "user_id": 123})

# Команда может быть передана, сохранена или выполнена позже
command.execute()


Итог:
Команда позволяет создавать объекты-запросы, которые легко управлять, что удобно при работе с асинхронными системами, такими как Kafka.

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

Компоненты шаблона:
- Субъект (Subject): хранит список наблюдателей и уведомляет их об изменениях.
- Наблюдатель (Observer): интерфейс для объектов, которые должны получать уведомления.

Когда применять:
- Когда изменение состояния одного объекта должно автоматически приводить к изменению других объектов.
- Когда нужно обеспечить слабую связанность между объектами.
- Когда набор наблюдателей неизвестен заранее или может изменяться динамически.

python
class Subject:
def __init__(self):
self._observers = []
self._state = None

def attach(self, observer):
self._observers.append(observer)

def detach(self, observer):
self._observers.remove(observer)

def notify(self):
for observer in self._observers:
observer.update(self._state)

def change_state(self, state):
self._state = state
self.notify()

class Observer:
def update(self, state):
pass

class ConcreteObserver(Observer):
def update(self, state):
print(f'Состояние изменилось на {state}')

subject = Subject()
observer = ConcreteObserver()
subject.attach(observer)
subject.change_state('Новое состояние')

Шаблон Стратегия (Strategy) — это поведенческий шаблон проектирования, который позволяет определить семейство схожих алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Это позволяет изменять поведение объекта во время выполнения без изменения его кода.

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

Пример для backend web на Python: предположим, у нас есть сервис отправки уведомлений с разными стратегиями доставки: по Email, SMS и Push.


from abc import ABC, abstractmethod

class NotificationStrategy(ABC):
@abstractmethod
def send(self, message: str, to: str):
pass

class EmailStrategy(NotificationStrategy):
def send(self, message: str, to: str):
print(f"Email sent to {to}: {message}")

class SMSStrategy(NotificationStrategy):
def send(self, message: str, to: str):
print(f"SMS sent to {to}: {message}")

class PushStrategy(NotificationStrategy):
def send(self, message: str, to: str):
print(f"Push notification sent to {to}: {message}")

class NotificationContext:
def __init__(self, strategy: NotificationStrategy):
self._strategy = strategy

def set_strategy(self, strategy: NotificationStrategy):
self._strategy = strategy

def notify(self, message: str, to: str):
self._strategy.send(message, to)

# Использование:
context = NotificationContext(EmailStrategy())
context.notify("Hello!", "user@example.com")

context.set_strategy(SMSStrategy())
context.notify("Hello!", "+123456789")

context.set_strategy(PushStrategy())
context.notify("Hello!", "user_device_token")

Шаблон Состояние (State) — это поведенческий шаблон проектирования, который позволяет объекту изменять своё поведение в зависимости от внутреннего состояния, будто объект меняет свой класс.

Когда применять:
- Когда поведение объекта должно зависеть от его состояния и изменяться во время выполнения.
- Чтобы избежать громоздких условных операторов, управляющих состояниями.
- Если нужно локализовать логику каждого состояния в отдельный класс.

Пример для web (управление состоянием пользователя при логине):

class State:
def handle(self, context):
pass

class LoggedOut(State):
def handle(self, context):
return "Пользователь не авторизован. Показываем форму входа."

class LoggedIn(State):
def handle(self, context):
return "Пользователь авторизован. Показываем личный кабинет."

class Context:
def __init__(self):
self.state = LoggedOut()

def set_state(self, state: State):
self.state = state

def request(self):
return self.state.handle(self)

# Использование
context = Context()
print(context.request()) # Пользователь не авторизован. Показываем форму входа.

context.set_state(LoggedIn())
print(context.request()) # Пользователь авторизован. Показываем личный кабинет.

Шаблон Итератор

Итератор — это поведенческий шаблон проектирования, который предоставляет способ последовательного доступа ко всем элементам составного объекта без раскрытия его внутреннего представления.

Когда применять Итератор?
- - Когда необходимо предоставить последовательный доступ к элементам объекта без раскрытия его внутреннего устройства.
- - Когда требуется реализовать несколько способов обхода элементов.
- - Для работы с коллекциями различных типов единообразным образом.
- - При необходимости отделить логику перебора элементов от самой коллекции.

Пример реализации Итератора на Python:
python
class Iterator:
def __init__(self, collection):
self._collection = collection
self._index = 0

def __next__(self):
if self._index < len(self._collection):
item = self._collection[self._index]
self._index += 1
return item
else:
raise StopIteration

class IterableCollection:
def __init__(self, items):
self._items = items

def __iter__(self):
return Iterator(self._items)

# Использование
collection = IterableCollection([1, 2, 3])
for item in collection:
print(item)

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

Основные цели шаблона Посредник:
- Уменьшение зависимости между классами-компонентами.
- Централизация управления взаимодействием между объектами.
- Упрощение поддержки и расширения системы.

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

Пример на Python:
class Mediator:
def notify(self, sender, event):
pass

class ConcreteMediator(Mediator):
def __init__(self, component1, component2):
self.component1 = component1
self.component1.mediator = self
self.component2 = component2
self.component2.mediator = self

def notify(self, sender, event):
if event == "A":
self.component2.do_c()
elif event == "D":
self.component1.do_b()

class BaseComponent:
def __init__(self, mediator=None):
self.mediator = mediator

class Component1(BaseComponent):
def do_a(self):
print("Component1 does A.")
self.mediator.notify(self, "A")

def do_b(self):
print("Component1 does B.")

class Component2(BaseComponent):
def do_c(self):
print("Component2 does C.")

def do_d(self):
print("Component2 does D.")
self.mediator.notify(self, "D")

# Использование
c1 = Component1()
c2 = Component2()
mediator = ConcreteMediator(c1, c2)

c1.do_a()
c2.do_d()


В данном примере ConcreteMediator управляет взаимодействием между Component1 и Component2, обеспечивая их слабую связанность.