Design Patterns, Giga
https://giga.chat/link/gcsUUyXJCB
https://refactoring.guru/ru/design-patterns/classification
Паттерны проектирования на Python
Что такое паттерны проектирования?
Паттерны проектирования — это многократно используемые решения часто встречающихся проблем при проектировании программного обеспечения. Они помогают создавать гибкий, поддерживаемый и расширяемый код.
Основные категории паттернов проектирования
- Creational (Создание объектов)
- Позволяют управлять созданием объектов различными способами.
- Structural (Структурные)
- Определяют структуру классов и объектов для достижения композиции компонентов.
- Behavioral (Поведенческие)
- Описывают взаимодействие между объектами и распределение обязанностей.
Creational (Создательные) паттерны
Singleton (Одиночка)
Обеспечивает наличие единственного экземпляра класса и точку глобального доступа к нему.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Logger(metaclass=SingletonMeta):
pass
Использование:logger1 = Logger()logger2 = Logger()print(logger1 is logger2) # True
--
Factory Method (Фабричный метод)
Определяет интерфейс для создания объектов, позволяя подклассам выбирать реализацию создаваемых объектов.
from abc import ABC, abstractmethod
class Product(ABC):
@abstractmethod
def operation(self):
pass
class ConcreteProductA(Product):
def operation(self):
return 'ConcreteProductA'
class Creator(ABC):
@abstractmethod
def factory_method(self):
pass
def some_operation(self):
product = self.factory_method()
result = f'Creator: {product.operation()}'
return result
class ConcreteCreatorA(Creator):
def factory_method(self):
return ConcreteProductA()
creator_a = ConcreteCreatorA()
result = creator_a.some_operation()
print(result)
class ChatMediator:
def send_message(self, sender, message):
for user in users:
if user != sender:
user.receive(message)
class User:
def __init__(self, mediator):
self.mediator = mediator
def send(self, message):
self.mediator.send_message(self, message)
def receive(self, message):
print(f'{self.name}: received "{message}"')
Основное различие:
- Facade (Фасад) занимается упрощением интерфейса сложной подсистемы, предлагая внешнюю оболочку для обращения к различным внутренним элементам. Он ничего не контролирует относительно коммуникаций между составляющими подсистемы.
- Mediator (Посредник) регулирует и координирует общение между объектами, выступая в роли центрального узла общения. Он активно участвует в управлении взаимодействием элементов системы, уменьшая прямую связанность между ними.
Итак, основное различие между фасадом и посредником сводится к следующему:
- Facade — упрощает доступ к сложным подсистемам, объединяя несколько операций в одну простую.
- Mediator — уменьшает связанность между объектами, регулируя процесс их взаимодействия через центральный узел.
Когда стоит использовать паттерн Mediator?
Ответ
Паттерн Mediator («Посредник») применяется тогда, когда необходимо снизить число прямых зависимостей между компонентами системы и организовать централизованное управление их взаимодействием. Его основной задачей является уменьшение связанной сложности и улучшение управляемости проекта.
Причины выбрать паттерн Mediator:
- Многочисленные двусторонние зависимости:Система включает большое количество объектов, которые напрямую зависят друг от друга. Прямые связи приводят к сильной связанности (coupling), затрудняют тестирование и увеличивают стоимость поддержки. Внедрение медиатора позволит избавиться от большинства непосредственных связей, передав управление ими одному объекту — посреднику.
- Необходимость контролировать потоки событий:Некоторые события или процессы требуют координации действий сразу нескольких участников. Ввод посредника позволяет эффективно управлять распространением событий, определив общую схему передачи сигналов.
- Проект стал слишком громоздким и трудночитаемым:Если взаимодействие объектов настолько сложное, что код начал становиться труднопонимаемым и сложноуправляемым, введение посредника улучшит ситуацию. Медиатор соберёт логику взаимодействия в одном месте, сделав код гораздо чище и аккуратнее.
- Требуется изоляция компонентов:Нужно изолировать отдельные компоненты друг от друга, сохраняя способность быстро реагировать на изменения в структуре и поведении системы. Использование медиатора обеспечит гибкость и простоту адаптации компонентов при изменении требований.
- Планируется дальнейшее расширение системы:Медиаторы отлично подходят для ситуаций, когда ожидается значительное увеличение числа объектов и связей между ними. Благодаря медитору новые компоненты смогут подключаться легко и безболезненно, без внесения серьёзных изменений в существующие модули.
Типичные сценарии использования:
- GUI-интерфейсы:В графическом интерфейсе пользователя кнопки, поля ввода и меню могут зависеть друг от друга. Например, нажатие кнопки должно привести к изменению содержимого полей формы. Медиатор идеально подходит для централизованного управления подобными событиями.
- Управление диалоговыми окнами:Часто возникает ситуация, когда одно окно должно обновляться или закрываться в зависимости от действий в другом окне. Здесь удобно использовать медиатор для координации действий окон.
- Многопоточные приложения:Приложения с многопоточностью также выигрывают от введения посредника. Один объект может выступать координатором потоков, управляя передачей данных и синхронизацией процессов.
- Распределённые системы:В распределённых системах зачастую необходимо обеспечить согласованную работу удалённых узлов. Медиатор поможет реализовать логику передачи и обработки сообщений между отдельными компонентами сети.
Пример практического применения:
Допустим, у вас есть игра, в которой игрок может активировать различные предметы (оружие, броню, зелья здоровья), и каждое из них оказывает воздействие на состояние персонажа. Однако напрямую привязывать поведение каждого предмета к персонажу приведет к большому количеству зависимостей и слабой поддержке. Введя медиатор, вы сможете хранить логику влияния предметов на персонаж в одном классе, значительно снизив связанность:
class PlayerMediator:
def __init__(self, player):
self.player = player
def apply_weapon(self, weapon):
self.player.attack += weapon.damage_bonus
def apply_potion(self, potion):
self.player.health += potion.healing_amount
player_mediator = PlayerMediator(player)
player_mediator.apply_weapon(sword)
player_mediator.apply_potion(potion)
Итоговые рекомендации:
Используйте паттерн Mediator, если ваша система характеризуется высокой степенью связанности между компонентами, либо вам необходимо централизованно управлять взаимодействием объектов, улучшая тем самым гибкость и управляемость вашей архитектуры.
Какие преимущества даёт применение Mediator?
Ответ
Применение паттерна Mediator («Посредник») приносит следующие значительные преимущества:
1. Снижение связанности (low coupling)
Когда компоненты взаимодействуют напрямую, они создают высокую связанность (coupling), ухудшающую поддержку и гибкость системы. Введение медиатора устраняет большинство прямых зависимостей между компонентами, что существенно снижает связанность. Компоненты больше не знают друг о друге и общаются только через посредника.
Например, в приложении чата раньше клиентские окна могли бы отправлять сообщения прямо друг другу, теперь они передают их медиатору, который уже перенаправляет сообщения остальным окнам.
2. Упрощённая поддержка и модификация
За счёт снижения уровня связанности между компонентами внесение изменений в один компонент практически не затрагивает другие компоненты. Модификация отдельного элемента системы проходит гладко, без риска вызвать каскад изменений в остальных частях системы.
Например, если в графическом интерфейсе изменится поведение кнопок, оно повлияет только на медиатор, но не затронет напрямую другие элементы интерфейса.
3. Повышенная гибкость и расширяемость
Централизованная схема взаимодействия через медиатор позволяет гибко адаптироваться к новым требованиям и возможностям. Новые компоненты можно легко подключить к медиатору, не вмешиваясь в существующее взаимодействие между старыми компонентами.
Например, при создании новой опции настройки графики достаточно внедрить соответствующий объект в медиатор, который возьмет на себя координацию обновления интерфейса и параметров графики.
4. Простота тестирования
Так как компоненты перестают взаимодействовать напрямую, появляется возможность тестировать каждый компонент отдельно от других. Тестировать сами взаимодействия также удобнее через медиатор, так как вся логика взаимодействия находится в одном месте.
5. Лучшее управление поведением
Все правила взаимодействия между компонентами собраны в одном месте — медиаторе. Поэтому логику поведения легко проанализировать, оптимизировать и исправить, если обнаружатся баги или несоответствия.
Например, изменения правил перехода состояний в игре (смена игрового режима, начало боя) удобно фиксируются в медиатор-классах, позволяющих оперативно вносить правки.
6. Централизация логики
Логика взаимодействия всех компонентов собирается в одном месте — медиаторе. Такое решение улучшает восприятие кода и ускоряет разработку новых фич, поскольку разработчики точно знают, куда обратиться для добавления нового поведения или коррекции существующих взаимодействий.
7. Оптимизация производительности
При большом количестве объектов, работающих одновременно, медиатор способен эффективно координировать ресурсы и предотвратить излишние вычисления. Например, медиатор может отслеживать активность пользователей и отключать неактивные компоненты.
Пример использования:
Допустим, у вас есть GUI-приложение с кнопками, текстовыми полями и всплывающими уведомлениями. Каждое событие (нажатие кнопки, ввод текста) ранее могло вызывать прямое обновление других элементов интерфейса. Через медиатор всё взаимодействие упорядочивается следующим образом:
class GuiMediator:
def button_clicked(self):
text_field.clear()
notification.show("Button clicked!")
button.on_click(GuiMediator().button_clicked)
Теперь кнопка посылает сигнал медиатору, который самостоятельно вызывает очистку текстового поля и показывает уведомление.
Подытоживая:
Применение паттерна Mediator полезно, когда система страдает от чрезмерной связанности между компонентами, нуждается в централизации управления взаимодействиями или планируется дальнейшая масштабируемость и развитие.
Чем отличается Mediator от Observer?
Ответ
Несмотря на схожесть целей — снижение связанности между компонентами системы, паттерны Mediator («Посредник») и Observer («Наблюдатель») различаются как по своей природе, так и по области применения. Рассмотрим главные отличия между ними:
Key Differences Between Mediator and Observer
1. Nature of Interaction
- Mediator: Управляет двумясторонним взаимодействием между несколькими объектами, устанавливая и координируя связь между ними. Объекты взаимодействуют через единого посредника, а не напрямую друг с другом.
- Observer: Обеспечивает одностороннее наблюдение одного объекта за изменениями другого. Наблюдатели получают уведомления от наблюдаемого объекта и реагируют на изменения, не взаимодействуя обратно с ним.
Example:
- В системе с использованием паттерна Mediator, окно регистрации (объект A) и окно подтверждения (объект B) могут посылать сигналы посреднику, который координирует открытие обоих окон.
- В системе с применением паттерна Observer, объект
NotificationCenter (наблюдаемый объект) может послать уведомление своим подписчикам (наблюдателям), которым не нужно предпринимать никаких действий обратно на центр уведомлений.
2. Complexity of Communication
- Mediator: Обычно применяется, когда существуют многочисленные двунаправленные связи между объектами. Это уменьшает сложность системы, собирая все коммуникации в одном месте.
- Observer: Предназначен для асинхронных взаимодействий, где интересующий объект публикует своё состояние, а подписчики автоматически реагируют на изменения.
Example:
- Если у вас есть форма заказа товаров, и каждому изменению цены товара необходимо обновить корзину покупателя, Mediator сможет координировать реакцию корзины на цену товара и наоборот.
- Если в системе мониторинга есть источник данных (температура), который должен оповестить датчики (наблюдателей), когда температура достигает критической точки, подойдёт паттерн Observer. Датчики пассивно ожидают уведомления и не влияют обратно на источник.
3. Level of Control Over Interactions
- Mediator: Полностью контролирует порядок и последовательность взаимодействий между объектами. Медиатор принимает активное участие в передаче сигнала и реакции на него.
- Observer: Минимизирует контроль посредников над реакциями подписчиков. Субъект просто объявляет изменения своего состояния, а реакция на эти изменения остается на усмотрение самих подписчиков.
Example:
- Медикатором может управляться сложный игровой интерфейс, где изменения положения мыши влияют на положение камеры, звук и освещение сцены. Именно медиатор устанавливает, как и в каком порядке происходят эти изменения.
- Наблюдатель же мог бы использоваться для обновления виджетов панели инструментов при смене активного инструмента в редакторе изображений.
4. Dependency Management
- Mediator: Создает низкую связанность между компонентами, сводя их отношения к единственной зависимости от медиатора.
- Observer: Удаляет жесткую зависимость между субъектом и подписчиками, заменяя её на сигнальную связь. Тем не менее, сама природа публикации-подписки подразумевает небольшую степень зависимости субъектов от своих подписчиков.
Example:
- Представьте систему с несколькими видами экранов (текстовым редактором, окном графика и панелью предпросмотра). При помощи медиатора они могут получать уведомления и действовать совместно, без прямого взаимодействия друг с другом.
- А вот паттерн Observer хорошо подойдет, если есть главное окно приложения, которое должно уведомлять второстепенные окна (такие как информационная панель или журнал активности) о произошедших событиях.
Conclusion
Паттерн Mediator предназначен для организации двунаправленного взаимодействия между компонентами, уменьшив сложность взаимосвязей и собрав все взаимодействия в одном месте. Паттерн Observer служит для асинхронного наблюдения за состоянием объекта и автоматической реакции на его изменения, минимизируя жесткие зависимости между компонентами.
Выбор между ними зависит от характера ваших потребностей:
- Используйте Mediator, если важна комплексная организация двунаправленных взаимодействий.
- Предпочитайте Observer, если нужны простые и асинхронные подписки на изменения состояния объекта.
Какова роль медиатора в архитектуре?
Ответ
Роль медиатора (Mediator) в архитектуре программного обеспечения чрезвычайно значима и связана с управлением взаимодействием между компонентами системы. Медиатор реализует концепцию "централизованной координации", принимая на себя обязанность контроля и регулирования взаимоотношений между объектами, предотвращая появление прямых зависимостей между ними.
Роли и обязанности медиатора:
- Центральный организатор взаимодействий:
- Основной функцией медиатора является управление коммуникацией между компонентами. Другие объекты системы обращаются к медиатору, который берет на себя задачу передать соответствующие команды нужным адресатам.
- Например, в приложении с графическим интерфейсом (GUI) медиатор может координировать взаимодействия между кнопками, формами и панелями, определяя, какая информация должна быть передана и каким образом.
- Удаление прямых зависимостей:
- Одна из главных ролей медиатора — устранение прямых связей между компонентами. За счет этого достигается низкая связанность (low coupling), что повышает гибкость и ремонтопригодность системы.
- До появления медиатора объекты могут напрямую обращаться друг к другу, создавая хаос и мешанину зависимостей. После внедрения медиатора все взаимодействие осуществляется через него, освобождая компоненты от лишней нагрузки.
- Регуляция порядка и последовательности взаимодействий:
- Медиатор контролирует порядок и последовательность обращений между компонентами. Это особенно важно в приложениях с большим числом параллельных процессов или сложным порядком выполнения задач.
- Например, медиатор может определить порядок запуска служб в операционной системе, следя за тем, чтобы каждая служба стартовала строго после завершения предыдущих этапов подготовки.
- Укрепление безопасности и стабильности:
- Поскольку медиатор действует как "контрольный пункт", он может включать дополнительную проверку полномочий, безопасность транзакций и прочие меры защиты.
- В игровых движках медиатор может проверять условия доступности ресурсов перед выполнением дорогостоящих вычислительных операций, защищая систему от ошибок и падений.
- Облегчение рефакторинга и масштабирования:
- Наличие медиатора облегчает процесс модернизации и доработки системы. Вся логика взаимодействия сконцентрирована в одном месте, что сокращает риск возникновения побочных эффектов при внесении изменений.
- Это позволяет плавно интегрировать новые функциональные блоки, увеличивая гибкость и адаптируемость архитектуры.
- Поддержка многопроцессорных и распределённых сред:
- В многопроцессорной среде или в распределённой системе медиатор может играть роль диспетчера, управляющего межпроцессорными и сетевыми взаимодействиями.
- Например, в кластере серверов медиатор может регулировать балансировку нагрузки, гарантируя равномерное распределение запросов между серверами.
Общий итог:
Медиатор играет ключевую роль в современной архитектуре программного обеспечения, помогая решать проблемы связанной сложности и обеспечивать устойчивые и предсказуемые взаимодействия между компонентами. Правильное внедрение медиатора способно повысить надежность, производительность и ремонтопригодность системы, особенно в условиях больших и сложных проектов.
Сравни Mediator и Observer, Observable - python
Ответ
Паттерны Mediator («Посредник») и Observer («Наблюдатель», или «Observable» в некоторых языках) относятся к поведенческим паттернам проектирования, но решают принципиально разные задачи. Оба паттерна направлены на упрощение и улучшение взаимодействия между объектами, однако их подходы отличаются кардинально.
Паттерн Mediator (Посредник)
Назначение: Управление взаимодействием между объектами и устранение прямых зависимостей между ними.
Основные особенности:
- Центрация всех взаимодействий через специальный объект — посредника.
- Повышение независимости компонентов системы путём исключения их прямых связей.
- Координация последовательностей и направлений взаимодействия между объектами.
Типичное использование:
- Организация взаимодействия между большими группами объектов в сложной системе.
- Централизованный контроль взаимодействия между компонентами графического интерфейса (GUI).
Реализация в Python:
class Colleague:
def __init__(self, mediator):
self.mediator = mediator
class Button(Colleague):
def click(self):
self.mediator.button_clicked()
class TextField(Colleague):
def clear(self):
print("Text field cleared.")
class Mediator:
def __init__(self, button, text_field):
self.button = button
self.text_field = text_field
def button_clicked(self):
self.text_field.clear()
if __name__ == "__main__":
mediator = Mediator(Button(None), TextField(None))
button = Button(mediator)
text_field = TextField(mediator)
button.click()
Паттерн Observer (Наблюдатель)
Назначение: Предоставление механизма подписки на изменения состояния объекта, при котором подписчики (наблюдатели) автоматически уведомляются об изменениях.
Основные особенности:
- Непрямая связь между объектом-изменителем (Subject) и подписчиками (Observers).
- Активация наблюдателя происходит асинхронно при изменении состояния субъекта.
- Отсутствие жесткой зависимости между субъектом и наблюдателями.
Типичное использование:
- Автоматическое обновление представления данных при изменении модели.
- Получение уведомлений об изменении статуса процесса или объекта.
Реализация в Python:
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, subject):
pass
class Subject:
def __init__(self):
self._observers = []
self._state = None
def add_observer(self, observer):
self._observers.append(observer)
def remove_observer(self, observer):
self._observers.remove(observer)
def notify_observers(self):
for observer in self._observers:
observer.update(self)
def set_state(self, value):
self._state = value
self.notify_observers()
class ConcreteObserver(Observer):
def update(self, subject):
print(f"Received update from subject with state '{subject._state}'.")
if __name__ == "__main__":
subject = Subject()
observer = ConcreteObserver()
subject.add_observer(observer)
subject.set_state("New State")
Отличительные черты:
- Цель:
- Mediator: Устранение непосредственной связи между объектами и обеспечение общего канала взаимодействия.
- Observer: Установка отношений "один ко многим" между источником и набором наблюдателей, отслеживающих изменения состояния.
- Связанность:
- Mediator: Значительно снижает связанность между объектами, исключая прямые зависимости.
- Observer: Сохраняет определенную связанность, хотя она намного ниже по сравнению с прямыми связями.
- Направленность:
- Mediator: Двусторонняя связь и регулирование потока данных между объектами.
- Observer: Односторонняя связь: субъекты уведомляют наблюдателей о своих изменениях, но обратная связь невозможна.
- Использование:
- Mediator: Графические интерфейсы, многоуровневые взаимодействия между компонентами.
- Observer: Мониторинг и обработка событий, автоматическая реакция на изменения.
Итог:
- Mediator используется для уменьшения прямых зависимостей между компонентами и предоставления общей схемы взаимодействия.
- Observer используется для создания механизмов подписки и автоматического реагирования на изменения состояния объекта.
Оба паттерна важны и полезны в соответствующих случаях, выбор между ними определяется характером решаемой задачи.
Хороший сайт Refactoring Guruгде естьКаталог паттернов проектирования
Ответ
Да, сайт Refactoring.Guru действительно отличный ресурс для изучения паттернов проектирования! Он предоставляет подробную информацию обо всех основных паттернах проектирования, включая:
- Подробные описания каждого паттерна.
- Принцип работы и сферу применения.
- Примеры реализации на популярных языках программирования, включая Python.
- Диаграммы UML для наглядного понимания структуры.
- Практические советы по использованию паттернов.
Сайт выполнен в удобном стиле, материалы изложены ясно и понятно, что делает его отличным выбором для новичков и опытных разработчиков, желающих освежить свои знания.
Кроме того, сайт регулярно обновляется и пополняется новыми материалами, поэтому он остаётся актуальным ресурсом для изучения современных подходов к разработке ПО.
Ссылка на каталог паттернов проектирования на сайте Refactoring.Guru:
- refactoring.guru/design-patterns
Это отличный инструмент для самообразования и профессионального роста программистов любого уровня подготовки.
Перечесли (python)
- Порождающие паттерны (создание)
- Структурные паттерны ( взаимодействие)
- Поведенческие паттерны (процесс работы программы)Расскажи про все это и подправь, если есть ошибка в описании.То что в скобках я не уверен в правильности оценки
Ответ
Описание в целом верное, но давайте посмотрим на каждую категорию немного точнее и приведем пример реализации на Python.
1. Порождающие паттерны (создание объектов)
Эти паттерны отвечают за создание объектов удобным и эффективным способом, сокрытие деталей их внутреннего устройства и предотвращения создания однотипных объектов снова и снова.
Основные порождающие паттерны:
- Factory Method (фабричный метод)Позволяет определять интерфейс для создания объектов, оставляя дочерним классам право выбора конкретного способа создания.
- Abstract Factory (абстрактная фабрика)Предоставляет интерфейс для создания семейств связанных объектов без указания их конкретных классов.
- Builder (строитель)Строит сложные объекты поэтапно, отделяя построение объекта от его представления.
- Prototype (прототип)Позволяет создавать объекты путём копирования прототипа.
- Singleton (одиночка)Гарантирует существование только одного экземпляра класса и предоставляет точку глобального доступа к этому экземпляру.
Пример: Singleton
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2)
2. Структурные паттерны (организация и композиция объектов)
Эти паттерны фокусируются на отношениях между объектами и классами, обеспечивают эффективное соединение компонентов и предоставляют способы компоновки и повторного использования классов.
Основные структурные паттерны:
- Adapter (адаптер)Преобразует интерфейс одного класса в интерфейс, ожидаемый клиентами.
- Bridge (мост)Разделяет абстракцию и реализацию, позволяя им изменяться независимо друг от друга.
- Composite (компоновщик)Позволяет компоновать объекты в древовидные структуры, предоставляя единый интерфейс для работы с одиночными объектами и составными частями.
- Decorator (декоратор)Добавляет новое поведение объектам динамически, не изменяя их внутреннее устройство.
- Proxy (заместитель)Служит суррогатом для реального объекта, управляя доступом к нему.
- Flyweight (легковес)Экономит память, делит общие данные между объектами.
- Facade (фасад)Предоставляет упрощённый интерфейс к сложной подсистеме.
Пример: Proxy
class RealImage:
def load_image(self):
print("Загрузка изображения...")
def display(self):
print("Отображаю изображение.")
class ImageProxy:
def __init__(self):
self.real_image = None
def display(self):
if self.real_image is None:
self.real_image = RealImage()
self.real_image.load_image()
self.real_image.display()
proxy = ImageProxy()
proxy.display()
3. Поведенческие паттерны (управление процессом работы программы)
Эти паттерны определяют способы взаимодействия между объектами, позволяющие назначать обязанности и делегировать задачи между ними.
Основные поведенческие паттерны:
- Chain of Responsibility (цепочка обязанностей)Формирует цепочку объектов, каждый из которых может обработать запрос или передать его дальше по цепи.
- Command (команда)Инкапсулирует запрос в виде объекта, что позволяет параметризовать вызовы и отложить исполнение.
- Interpreter (интерпретатор)Определяет грамматику языка и интерпретирует выражения согласно этой грамматике.
- Iterator (итератор)Предоставляет стандартный способ обхода элементов коллекции последовательно.
- Mediator (посредник)Регулирует взаимодействие между объектами, устраняя их прямые зависимости.
- Memento (хранитель)Запоминает состояние объекта и восстанавливает его позже без нарушения инкапсуляции.
- Observer (наблюдатель)Устанавливает отношение "один ко многим" между объектами, позволяя объектам наблюдать за изменением состояния другого объекта.
- State (состояние)Позволяет объекту изменять свое поведение в зависимости от текущего состояния.
- Strategy (стратегия)Определяет семейство алгоритмов, инкапсулируя каждый из них и позволяя динамическое переключение между ними.
- Template Method (шаблонный метод)Определяет скелет алгоритма, откладывая шаги реализации потомкам.
- Visitor (посетитель)Позволяет добавить новое поведение группе классов без изменения их структуры.
Пример: Command
class Light:
def turn_on(self):
print("Свет включен.")
def turn_off(self):
print("Свет выключен.")
class Command:
def execute(self):
pass
class TurnOnLight(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
light = Light()
turn_on_command = TurnOnLight(light)
turn_on_command.execute()
Резюме:
- Порождающие паттерны — занимаются эффективностью создания объектов.
- Структурные паттерны — организуют и связывают объекты и классы.
- Поведенческие паттерны — задают порядок взаимодействия объектов, распределяя обязанности и контролируя процессы.
Каждый паттерн помогает решить специфическую проблему проектирования, делая код более чистым, поддерживаемым и расширяемым.
К какому классу отнести паттерны Состояние и Стратегия?
Ответ
Паттерны Состояние (State) и Стратегия (Strategy) принадлежат к классу поведенческих паттернов.
Почему именно к поведенческим?
Потому что оба паттерна предназначены для управления поведением объектов в процессе работы программы:
- State (Состояние) позволяет объекту изменять свое поведение в зависимости от текущего состояния. Он основан на идее, что объект может переходить из одного состояния в другое, и его поведение изменяется соответственно.
- Strategy (Стратегия) определяет набор алгоритмов, инкапсулируя каждый из них и позволяя выбирать подходящий алгоритм в зависимости от ситуации. То есть, объект может поменять свою стратегию работы на ходу, что тоже относится к управлению поведением.
Поэтому оба паттерна попадают именно в группу поведенческих паттернов.
Когда применять паттерн State?
Ответ
Паттерн State («Состояние») рекомендуется применять в следующих ситуациях:
1. Объект ведет себя по-разному в зависимости от текущего состояния
Когда объект должен демонстрировать разное поведение в зависимости от своего состояния, и эти состояния имеют четкие границы и ограничения переходов между ними.
Примеры:
- Игра, где герой может находиться в состояниях "ходьба", "бег", "прыжок", "удар" и ведёт себя по-разному в каждом состоянии.
- Состояние банкомата: режим ожидания, выдача денег, проверка карты и т.п., где каждое состояние предполагает собственное поведение.
2. Переходы между состояниями подчиняются строгим правилам
Когда переходы между состояниями объекта жестко регламентированы и нарушать их нельзя. Например, сначала пользователь обязан пройти регистрацию, затем войти в аккаунт, а затем уже совершать покупки — пропуск шага недопустим.
3. Код становится слишком запутанным и тяжело поддерживаемым
Без паттерна State логика определения состояния и реакций на события часто превращается в огромное количество условных операторов (if, switch), которые постепенно усложняются и снижают читаемость кода. Паттерн State позволяет разложить логику состояний по отдельным классам, упрощая код и повышая его читаемость.
4. Необходимо ограничение возможного поведения в определенном состоянии
Когда необходимо ограничить допустимые действия объекта в зависимости от его текущего состояния. Например, невозможно отправить письмо, пока оно не было подготовлено и сохранено, или выйти из комнаты, пока дверь закрыта.
5. Система подвержена частым изменениям состояний
Если программа вынуждена часто обрабатывать смену состояний объекта, и каждая смена состояния влечет за собой существенные изменения в поведении объекта, паттерн State станет хорошим решением для упрощения управления такими ситуациями.
Пример применения паттерна State:
Представьте, что у вас есть автомобиль, который может находиться в трех состояниях: двигатель выключен, прогрев двигателя, движение. Каждое состояние диктует свои правила поведения автомобиля:
from abc import ABC, abstractmethod
class CarState(ABC):
@abstractmethod
def start_engine(self):
pass
@abstractmethod
def drive(self):
pass
class EngineOffState(CarState):
def start_engine(self):
print("Запускаем двигатель...")
return WarmUpState()
def drive(self):
print("Сначала запустите двигатель!")
return self
class WarmUpState(CarState):
def start_engine(self):
print("Двигатель уже запущен.")
return self
def drive(self):
print("Автомобиль прогрелся, начинаем движение...")
return DrivingState()
class DrivingState(CarState):
def start_engine(self):
print("Машина уже едет.")
return self
def drive(self):
print("Продолжаем движение.")
return self
class Car:
def __init__(self):
self.current_state = EngineOffState()
def start_engine(self):
self.current_state = self.current_state.start_engine()
def drive(self):
self.current_state = self.current_state.drive()
car = Car()
car.start_engine()
car.drive()
car.drive()
Почему паттерн State удобен?
- Улучшает читаемость и поддерживаемость кода.
- Легче добавлять новые состояния или изменять старые.
- Сокращает объем вложенных условий и упрощает ветвление.
- Помогает соблюдать принцип единственной ответственности (SRP), выделяя отдельную ответственность за каждое состояние.
Таким образом, паттерн State идеален, когда нужно описать разнообразные состояния объекта и задать строгие правила переходов между ними.
Паттерны - это дальнейшее развитие SOLID ?
Ответ
Нет, паттерны проектирования и принципы SOLID — это две разные концепции, каждая из которых занимает своё важное место в объектно-ориентированном дизайне.
Что такое SOLID?
SOLID — это акроним пяти фундаментальных принципов объектно-ориентированной разработки, направленных на повышение качества кода и упрощение его поддержки:
- Single Responsibility Principle (SRP) — принцип единственной ответственности.
- Open-Closed Principle (OCP) — принцип открытости-закрытости.
- Liskov Substitution Principle (LSP) — принцип замещения Лисков.
- Interface Segregation Principle (ISP) — принцип разделения интерфейсов.
- Dependency Inversion Principle (DIP) — принцип инверсии зависимостей.
Принципы SOLID способствуют созданию чистого, стабильного и расширяемого кода, делая акцент на изоляции и контроле зависимостей.
Что такое паттерны проектирования?
Паттерны проектирования — это готовые проверенные решения общих проблем, возникающих при проектировании программного обеспечения. Они предлагают стандартные подходы к решению типичных задач, таких как создание объектов, организация их взаимодействия и определение поведения.
Известные группы паттернов включают:
- Порождающие (для создания объектов),
- Структурные (для организации компонентов),
- Поведенческие (для взаимодействия и сотрудничества объектов).
Связь между SOLID и паттернами проектирования:
- Паттерны проектирования могут способствовать соблюдению принципов SOLID, но не обязательно подразумевают их обязательное применение.
- Например, паттерн Decorator помогает следовать принципу открытой-закрытости (OCP), потому что позволяет добавлять новую функциональность к объектам без изменения их исходного кода.
- Паттерн Strategy соответствует принципу единственной ответственности (SRP), так как выделяет стратегии в отдельные классы, выполняющие одну операцию.
Однако паттерны проектирования появились задолго до популяризации SOLID и развиваются параллельно. Они решают практические задачи проектирования, тогда как SOLID направлен на достижение лучшего стиля написания кода и соблюдения хороших практик проектирования.
Выводы:
- SOLID — это принципы проектирования, направленные на создание качественного и устойчивого кода.
- Паттерны проектирования — это шаблоны решения типовых задач, применяемые на практике.
- Между ними существует тесная связь, но паттерны не вытекают из SOLID напрямую, а скорее дополняют его, давая конкретные инструменты для реализации рекомендаций SOLID.
Можно сказать, что соблюдение SOLID способствует выбору правильных паттернов проектирования, но сами паттерны возникли раньше и имеют самостоятельное значение вне рамок SOLID.
--------------
Начинаем углубленное описание каждого паттерна
- Фабричный метод
- Суть
- Применение
- итд все что знаешь Подробно
Ответ
Паттерн «Фабричный метод»
1. Суть паттерна
Паттерн «Фабричный метод» — это порождающий шаблон проектирования, предназначенный для создания объектов через косвенное обращение к фабричному методу, который определяет интерфейс для создания экземпляров производных классов. Такой подход позволяет наследникам самим решать, какой конкретно экземпляр создать.
Основная цель паттерна — перенос ответственности за создание объектов из родительского класса в его подклассы, сохраняя единый интерфейс для создания объектов.
2. Проблемы, решаемые паттерном
Проблема: допустим, у вас есть иерархия классов, и вам нужно создать объекты, принадлежащие разным классам, исходя из контекста использования. Проблема возникает, когда нужно минимизировать количество изменений в клиентском коде при появлении новых типов объектов.
Решение: вместо того чтобы клиентская логика напрямую создавала объекты нужного типа, вы выносите создание объектов в отдельный метод (фабрику), который переопределяется в подклассах. Это предотвращает сильное сцепление клиентской логики с конкретными классами и позволяет спокойно расширять иерархию классов без изменения клиентского кода.
3. Применение паттерна
Сценарий 1: Работа с графикой
Допустим, у вас есть приложение, работающее с фигурами (прямоугольники, окружности, линии и т.д.). Вам нужно рисовать фигуры разного типа в зависимости от заданных параметров. При этом каждый тип фигуры создается особым образом, и вы хотите избежать усложнения базовой логики рисования.
Решение с использованием фабрики:
Вы создаете базовый класс с фабрикой, определяющей интерфейс для создания фигуры, а подклассы ответственны за создание определенных типов фигур.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def draw(self):
pass
class Rectangle(Shape):
def draw(self):
print("Рисование прямоугольника")
class Circle(Shape):
def draw(self):
print("Рисование круга")
class ShapeFactory(ABC):
@abstractmethod
def create_shape(self):
pass
class RectangleFactory(ShapeFactory):
def create_shape(self):
return Rectangle()
class CircleFactory(ShapeFactory):
def create_shape(self):
return Circle()
def main():
rectangle_factory = RectangleFactory()
circle_factory = CircleFactory()
shapes = [
rectangle_factory.create_shape(),
circle_factory.create_shape()
]
for shape in shapes:
shape.draw()
if __name__ == "__main__":
main()
Результат выполнения:
Рисование прямоугольника
Рисование круга
Сценарий 2: Форматы файлов
Допустим, ваше приложение сохраняет данные в разных форматах (JSON, XML, CSV и др.) и вам нужно уметь правильно сериализовывать объекты, основываясь на выбранном формате.
Решение с использованием фабрики:
Вы создаете базовую структуру формата с фабрикой, которая создает нужные форматы файлов, а подклассы (форматы) отвечают за реализацию сериализации в конкретном формате.
from abc import ABC, abstractmethod
class Format(ABC):
@abstractmethod
def serialize(self, data):
pass
class JSONFormat(Format):
def serialize(self, data):
print(f"Сериализация в JSON: {data}")
class XMLFormat(Format):
def serialize(self, data):
print(f"Сериализация в XML: {data}")
class FormatFactory(ABC):
@abstractmethod
def create_format(self):
pass
class JSONFactory(FormatFactory):
def create_format(self):
return JSONFormat()
class XMLFactory(FormatFactory):
def create_format(self):
return XMLFormat()
def save_data(factory, data):
format_obj = factory.create_format()
format_obj.serialize(data)
save_data(JSONFactory(), {"key": "value"})
save_data(XMLFactory(), "<root><item>value</item></root>")
Результат выполнения:
Сериализация в JSON: {'key': 'value'}
Сериализация в XML: <root><item>value</item></root>
4. Особенности паттерна
- Интерфейс: Метод создания объекта объявлен в базовом классе, но реализация предоставляется подклассами.
- Наследственность: Важно, чтобы базовый класс знал, какого типа объекты создаются (через абстрактный метод), но не имел понятия о фактическом подклассе.
- Расширяемость: Можно легко добавить новые типы объектов, создавая новые подклассы фабрики.
5. Преимущества паттерна
- Открытая-закрытая архитектура: Новый функционал добавляется добавлением новых подклассов, не изменяя базовый класс.
- Слабое сцепление: Низкая зависимость между клиентами и конкретными типами объектов.
- Четкое разделение ответственности: Родительский класс отвечает за интерфейс, а подклассы — за реализацию.
6. Недостатки паттерна
- Количество классов увеличивается: Количество классов растет пропорционально числу возможных продуктов.
- Трудности с контролем жизненного цикла: Иногда бывает непросто отслеживать весь цикл жизни объектов, созданных фабрикой.
7. Альтернативы
Альтернатива паттерну «Фабричный метод» — паттерн «Абстрактная фабрика», который создает целые семейства связанных объектов, или «Строитель», используемый для построения сложных объектов пошагово.
Следующим будем рассматривать следующий паттерн из списка.
В Питонто есть мы фабричным методом создаем обьект не привязанный к конкретному типуПравильно понимаю.Зачем нам нужна отвязка от конкретного типа ?
Ответ
Верно понимаете!
Фабричный метод позволяет создавать объекты, не привязываясь к конкретному типу, и эта особенность несет несколько важных преимуществ:
Зачем нужна отвязка от конкретного типа?
- Открытость для расширения:
- Ваш клиентский код становится открытым для будущих изменений. Допустим, завтра появится новая версия объекта или вообще новый тип объекта. Тогда, добавив всего один новый подкласс фабрики, вы получаете возможность создавать объекты нового типа без изменения самого клиентского кода.
- Легкость поддержки и изменения:
- Менее связанный код проще поддерживать и развивать. Вы можете сосредоточиться на изменении или добавлении конкретных видов объектов в отдельном месте (в подклассах фабрики), не трогая основной код приложения.
- Инкапсуляция логики создания:
- Детали создания объекта скрыты внутри фабрики. Клиентскому коду неважно, как именно создается объект, и это защищает вашу программу от ненужных подробностей.
- Увеличение гибкости:
- Вместо жесткой фиксации на одном конкретном типе объекта, вы даете возможность подклассам влиять на создание нужных объектов, что делает код более гибким и адаптируемым к будущим потребностям.
- Приспособляемость к агрегациям:
- Если объект состоит из множества мелких компонентов, фабрика может собрать объект целиком, сократив усилия по ручной сборке объекта в клиентском коде.
Пример реальной пользы в Python
Допустим, у вас есть библиотека для работы с изображениями, которая должна поддерживать разные форматы (JPEG, PNG, GIF и т.д.).
Ваш код без фабрики выглядел бы примерно так:
def process_image(filename):
extension = filename.split('.')[-1].lower()
if extension == 'jpg':
image = JPGImage(filename)
elif extension == 'png':
image = PNGImage(filename)
else:
raise ValueError("Unsupported image type")
return image
Но это плохо масштабируется. Если завтра появится SVG или WebP, придется редактировать функцию process_image.
С использованием фабрики:
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def load(self):
pass
class JPGImage(Image):
def load(self):
print("Загрузили JPEG изображение")
class PNGImage(Image):
def load(self):
print("Загрузили PNG изображение")
class ImageFactory(ABC):
@abstractmethod
def create_image(self, filename):
pass
class JPGFactory(ImageFactory):
def create_image(self, filename):
return JPGImage(filename)
class PNGFactory(ImageFactory):
def create_image(self, filename):
return PNGImage(filename)
def process_image(filename, factory):
image = factory.create_image(filename)
image.load()
factory_jpg = JPGFactory()
factory_png = PNGFactory()
process_image("photo.jpg", factory_jpg)
process_image("graph.png", factory_png)
Теперь вы добавили слой абстракции и сделали код готовым к новому формату, без изменения клиентского кода process_image. Просто добавьте новый подкласс фабрики и получите нужный объект.
Итог:
Фабричный метод позволяет вашим программам оставаться открытыми для будущего развития, гибкими и легко поддерживаемыми, а главное — отвязанными от конкретных типов объектов, что положительно сказывается на качестве и долговечности вашего кода.