Шаблоны проектирования Python
Шаблоны проектирования — этопереиспользуемые решениядля распространённых проблем в разработке программного обеспечения. Они не являются законченными проектами, которые можно напрямую преобразовать в код, а скореешаблонами или чертежами для решения конкретных задач.。
Основная ценность шаблонов проектирования
Шаблоны проектирования подобны стандартным чертежам в строительной сфере и дают следующие преимущества для разработки программного обеспечения:
- Повышение переиспользуемости кода: Избегайте изобретения велосипеда
- Повышение сопровождаемости кода: Сделайте структуру кода более понятной
- Содействие командной работе: Обеспечивает единый язык программирования и образ мышления
- Улучшение качества кода: Решения, проверенные на практике, надёжнее
Аналогия паттернов проектирования в повседневной жизни
Представьте, что вы собираетесь построить дом:
- Примитивный способ: Проектирование с нуля каждый раз чревато ошибками и неэффективно
- Использование паттернов проектирования: Использование стандартных архитектурных чертежей, знание стандартных планировок для гостиной, кухни и спальни
Паттерны проектирования — это «стандартные архитектурные чертежи» разработки программного обеспечения.
Типы шаблонов проектирования
Согласно классической книге«Design Patterns - Elements of Reusable Object-Oriented Software» (китайский перевод: «Design Patterns: Reusable Object-Oriented Software Elements»)Согласно её определению, существует 23 классических паттерна проектирования. Эти паттерны можно разделить на три основные категории по их направленности: порождающие паттерны, структурные паттерны и поведенческие паттерны. Кроме того, в корпоративной разработке также существует часто используемый класс архитектурных паттернов проектирования.
| Number | Паттерн и описание | Включает |
|---|---|---|
| 1 | Порождающие шаблоны Используется для решения проблем создания объектов, инкапсулируя логику создания экземпляров, скрывая детали конкретной реализации, делая систему более гибкой и расширяемой при создании объектов. |
|
| 2 | Структурные шаблоны Фокус на композиции и взаимодействии классов и объектов, помогая нам строить гибкие, повторно используемые и расширяемые системные структуры. |
|
| 3 | Поведенческие паттерны Ориентация на взаимодействие и распределение обязанностей между объектами, повышение гибкости и сопровождаемости системы за счёт инкапсуляции алгоритмов, состояний или логики запросов. |
|
| 4 | Архитектурные паттерны корпоративного уровня Эти паттерны обычно используются в многоуровневых архитектурах больших систем и ориентированы на взаимодействие между уровнем представления и уровнем бизнес-логики. |
|
На изображении ниже показаны взаимосвязи и иерархическая структура различных паттернов проектирования:
Преимущества паттернов проектирования
- Предоставляют единый язык проектирования, позволяющий разработчикам быстро передавать проектный замысел.
- Предоставляют зрелые, многократно используемые решения типовых проблем.
- Снижают связанность системы и повышают сопровождаемость и расширяемость кода.
- Снижают избыточность проектирования, повышая эффективность разработки и качество кода.
- Помогают новым участникам быстро понять архитектуру системы и философию проектирования.
Шесть принципов паттернов проектирования
1. Принцип открытости/закрытости
Открыт для расширения, закрыт для модификации. Программное обеспечение должно адаптироваться к изменениям путём расширения новой функциональности, а не изменения существующего кода. Ядро реализации этого принципа заключается в использовании абстракций (интерфейсов или базовых классов) для определения стабильного поведения.
2. Принцип подстановки Лисков
Подклассы должны иметь возможность заменять любое место, где появляется базовый класс. Наследование имеет смысл только тогда, когда производные классы могут полностью заменить базовый класс. Этот принцип обеспечивает корректность полиморфизма и является важным дополнением к принципу открытости/закрытости.
3. Принцип инверсии зависимостей
Модули высокого уровня не должны зависеть от модулей низкого уровня; оба должны зависеть от абстракций. Конкретные реализации должны зависеть от интерфейсов или абстрактных классов, а не напрямую от конкретных классов. Этот принцип делает систему проще для расширения и тестирования.
4. Принцип разделения интерфейсов
Класс не должен зависеть от интерфейсов, которые ему не нужны. Большие интерфейсы следует разделять на меньшие, более специфичные, чтобы уменьшить связанность и повысить гибкость.
5. Закон Деметры
Также известен как «Принцип наименьшего знания»: объект должен минимизировать взаимодействия с другими объектами. Иными словами, класс должен знать только об объектах, непосредственно связанных с ним, тем самым уменьшая сложность системы.
6. Принцип композиционного повторного использования
Предпочитайте использовать композицию или агрегацию для повторного использования вместо наследования. Наследование создаёт сильную связанность, а композиция позволяет гибко заменять зависимости во время выполнения, достигая более элегантного структурного дизайна.
Распространённые примеры шаблонов проектирования в Python
Давайте разберём применение шаблонов проектирования в Python на нескольких конкретных примерах.
Шаблон одиночки
Шаблон «Одиночка» гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа.
Пример
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
print("Создание нового подключения к базе данных")
return cls._instance
def connect(self):
print("Подключение к базе данных")
# Пример использования
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"Являются ли db1 и db2 одним и тем же экземпляром? {db1 is db2}") # Вывод: True
Сценарии применения:
- Пул соединений с базой данных
- Менеджер конфигурации
- Logger
Шаблон фабрики
Паттерн «Фабрика» предоставляет интерфейс для создания объектов, позволяя подклассам решать, какой класс создавать.
Пример
# Абстрактный продукт
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:
@staticmethod
def create_notification(notification_type: str) -> Notification:
if notification_type == "email":
return EmailNotification()
elif notification_type == "sms":
return SMSNotification()
else:
raise ValueError("Неподдерживаемый тип уведомления")
# Пример использования
email = NotificationFactory.create_notification("email")
sms = NotificationFactory.create_notification("sms")
email.send("Ваш заказ был отправлен")
sms.send("Код подтверждения: 123456")
Объяснение кода:
Notificationявляется абстрактным базовым классом, который определяет интерфейсEmailNotificationиSMSNotificationявляется конкретной реализациейNotificationFactoryотвечает за создание конкретных объектов уведомлений
Паттерн «Наблюдатель»
Паттерн «Наблюдатель» определяет зависимость «один ко многим» между объектами. Когда состояние одного объекта изменяется, все зависящие от него объекты получают уведомление.
Пример
# Интерфейс наблюдателя
class Observer(ABC):
@abstractmethod
def update(self, message: str):
pass
# Конкретный наблюдатель
class EmailSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} получил уведомление по электронной почте: {message}")
class SMSSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} получил SMS-уведомление: {message}")
# Субъект (наблюдаемый)
class NewsPublisher:
def __init__(self):
self._subscribers = []
def subscribe(self, subscriber: Observer):
self._subscribers.append(subscriber)
def unsubscribe(self, subscriber: Observer):
self._subscribers.remove(subscriber)
def notify_subscribers(self, message: str):
for subscriber in self._subscribers:
subscriber.update(message)
# Пример использования
publisher = NewsPublisher()
# Создать подписчиков
alice = EmailSubscriber("Alice")
bob = SMSSubscriber("Боб")
# Подписаться на новости
publisher.subscribe(alice)
publisher.subscribe(bob)
# Опубликовать новости
publisher.notify_subscribers("Python 3.12 выпущен!")
# Отписаться
publisher.unsubscribe(alice)
publisher.notify_subscribers("Это уведомление, которое может увидеть только Боб")
Особенности паттернов проектирования в Python
Динамическая природа Python
Будучи динамическим языком, Python упрощает реализацию некоторых паттернов проектирования:
Пример
def create_payment(method):
payment_methods = {
'credit_card': CreditCardPayment,
'paypal': PayPalPayment,
'alipay': AlipayPayment
}
return payment_methods[method]()
# Используйте напрямую, сложная иерархия классов не нужна
payment = create_payment('alipay')
Встроенная поддержка паттерна Декоратор
Python имеет встроенный синтаксис декораторов, что делает реализацию паттерна декоратор более элегантной:
Пример
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} время выполнения: {end - start:.2f} секунд")
return result
return wrapper
@log_execution_time
def process_data(data):
# Имитация обработки данных
import time
time.sleep(1)
return f"Обработанные данные: {data}"
# Использование
result = process_data("Пример данных")