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

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

Основная ценность шаблонов проектирования

Шаблоны проектирования подобны стандартным чертежам в строительной сфере и дают следующие преимущества для разработки программного обеспечения:

  • Повышение переиспользуемости кода: Избегайте изобретения велосипеда
  • Повышение сопровождаемости кода: Сделайте структуру кода более понятной
  • Содействие командной работе: Обеспечивает единый язык программирования и образ мышления
  • Улучшение качества кода: Решения, проверенные на практике, надёжнее

Аналогия паттернов проектирования в повседневной жизни

Представьте, что вы собираетесь построить дом:

  • Примитивный способ: Проектирование с нуля каждый раз чревато ошибками и неэффективно
  • Использование паттернов проектирования: Использование стандартных архитектурных чертежей, знание стандартных планировок для гостиной, кухни и спальни

Паттерны проектирования — это «стандартные архитектурные чертежи» разработки программного обеспечения.

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

Согласно классической книге«Design Patterns - Elements of Reusable Object-Oriented Software» (китайский перевод: «Design Patterns: Reusable Object-Oriented Software Elements»)Согласно её определению, существует 23 классических паттерна проектирования. Эти паттерны можно разделить на три основные категории по их направленности: порождающие паттерны, структурные паттерны и поведенческие паттерны. Кроме того, в корпоративной разработке также существует часто используемый класс архитектурных паттернов проектирования.

NumberПаттерн и описаниеВключает
1 Порождающие шаблоны
Используется для решения проблем создания объектов, инкапсулируя логику создания экземпляров, скрывая детали конкретной реализации, делая систему более гибкой и расширяемой при создании объектов.
  • Шаблон фабрики
  • Шаблон абстрактной фабрики
  • Шаблон одиночки
  • Паттерн Строитель
  • Паттерн «Прототип»
2 Структурные шаблоны
Фокус на композиции и взаимодействии классов и объектов, помогая нам строить гибкие, повторно используемые и расширяемые системные структуры.
  • Паттерн «Адаптер»
  • Паттерн «Мост»
  • Паттерн фильтра / критериев
  • Паттерн «Компоновщик»
  • Паттерн Декоратор
  • Паттерн Фасад
  • Паттерн Приспособленец
  • Паттерн Заместитель
3 Поведенческие паттерны
Ориентация на взаимодействие и распределение обязанностей между объектами, повышение гибкости и сопровождаемости системы за счёт инкапсуляции алгоритмов, состояний или логики запросов.
  • Шаблон цепочки обязанностей
  • Шаблон команды
  • Паттерн Интерпретатор
  • Паттерн Итератор
  • Паттерн «Посредник»
  • Паттерн «Хранитель»
  • Паттерн «Наблюдатель»
  • Паттерн «Состояние»
  • Паттерн «Нулевой объект»
  • Паттерн «Стратегия»
  • Паттерн «Шаблонный метод»
  • Паттерн «Посетитель»
4 Архитектурные паттерны корпоративного уровня
Эти паттерны обычно используются в многоуровневых архитектурах больших систем и ориентированы на взаимодействие между уровнем представления и уровнем бизнес-логики.
  • Паттерн MVC (паттерн «Модель-Представление-Контроллер»)
  • Шаблон Business Delegate
  • Шаблон Composite Entity
  • Шаблон объекта доступа к данным
  • Паттерн Front Controller
  • Паттерн «Перехватывающий фильтр»
  • Паттерн «Локатор служб»
  • Паттерн Transfer Object

На изображении ниже показаны взаимосвязи и иерархическая структура различных паттернов проектирования:

设计模式之间的关系

Преимущества паттернов проектирования

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

Шесть принципов паттернов проектирования

1. Принцип открытости/закрытости

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

2. Принцип подстановки Лисков

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

3. Принцип инверсии зависимостей

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

4. Принцип разделения интерфейсов

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

5. Закон Деметры

Также известен как «Принцип наименьшего знания»: объект должен минимизировать взаимодействия с другими объектами. Иными словами, класс должен знать только об объектах, непосредственно связанных с ним, тем самым уменьшая сложность системы.

6. Принцип композиционного повторного использования

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


Распространённые примеры шаблонов проектирования в Python

Давайте разберём применение шаблонов проектирования в Python на нескольких конкретных примерах.

Шаблон одиночки

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

Пример

class DatabaseConnection:
    _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

Шаблон фабрики

Паттерн «Фабрика» предоставляет интерфейс для создания объектов, позволяя подклассам решать, какой класс создавать.

Пример

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:
    @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отвечает за создание конкретных объектов уведомлений

Паттерн «Наблюдатель»

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

Пример

from abc import ABC, abstractmethod

# Интерфейс наблюдателя
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 упрощает реализацию некоторых паттернов проектирования:

Пример

# Простая фабрика в стиле Python
def create_payment(method):
    payment_methods = {
        'credit_card': CreditCardPayment,
        'paypal': PayPalPayment,
        'alipay': AlipayPayment
    }
    return payment_methods[method]()

# Используйте напрямую, сложная иерархия классов не нужна
payment = create_payment('alipay')

Встроенная поддержка паттерна Декоратор

Python имеет встроенный синтаксис декораторов, что делает реализацию паттерна декоратор более элегантной:

Пример

def log_execution_time(func):
    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("Пример данных")
Другие расширения