Search
Write a publication
Pull to refresh

Фундаментальные шаблоны проектирования на Python

Level of difficultyMedium
Reading time12 min
Views5.3K

Наблюдатель (observer)

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

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

Пример: рассмотрим ситуацию, когда у нас есть пациент у которого нужно отслеживать температуру и пульс и отправлять информацию на основе полученных данных. Пациент — наш класс, у которого со временем меняются параметры (температура и пульс). Наблюдатели — блоки кода, которые выводят информацию в те моменты, когда меняются параметры.

class AbstractClass:
    """
        Абстрактный класс, у которого определены три функции:
        add_obs - добавить наблюдателя
        remove_obs - удалить наблюдателя
        notify_observer - разослать уведомления наблюдателям
    """

    def __init__(self):
        self.__observers = []

    def add_obs(self, observer):
        self.__observers.append(observer)

    def remove_obs(self, observer):
        self.__observers.remove(observer)

    def notify_observer(self, *arg):
        for i in self.__observers:
            i.update(self, *arg)
class AbstractObserver:
    """
        Абстрактный наблюдатель от которого нужно будет наследоваться конкретным
        наблюдателям и переопределять метод update, который 
    """

    def __init__(self):
        pass

    def update(self):
        pass
class Patient(AbstractClass):
    """
        Конкретный пациент - который в случае изменения параметров вызывает
        функция notify_observer
    """

    def __init__(self, name):
        super().__init__()
        self.name = name
        self.params = {"temperature": 0.0, "heartrate": 0.0}

    def set_value(self, measure_type, val):
        if measure_type in self.params:
            self.params[measure_type] = val
            self.notify_observer()
        else:
            print("Такого параметра нет")

    def get_value(self, measure_type):
        if measure_type in self.params:
            return self.params[measure_type]
        else:
            return None
class HeartbeatMonitor(AbstractObserver):
    """
        Конкретный наблюдатель пульса - в зависимости от значения пульса
        выводит результат 
    """

    def __init__(self):
        super().__init__()

    def update(self, tt):
        if type(tt).__name__ == 'Patient':
            hr = tt.get_value("heartrate")
            if hr > 120:
                print("Пульс слишком быстрый: " + str(hr))
            elif hr < 35:
                print("Пульс слишком медленный:  " + str(hr))
            else:
                print("Пульс в норме: " + str(hr))
        else:
            pass


class Thermometer(AbstractObserver):
    """
        Конкретный наблюдатель температуры - в зависимости от значения температуры
        выводит результат
    """

    def __init__(self):
        super().__init__()

    def update(self, tt):
        if type(tt).__name__ == 'Patient':
            temp = tt.get_value("temperature")
            if temp > 37.8:
                print("Слишком высокая температура: " + str(temp))
            elif temp < 35.0:
                print("Слишком низкая температура: " + str(temp))
            else:
                print("Температура в норме: " + str(temp))
        else:
            pass
import time


if __name__ == "__main__":
    sub = Patient("Кирилл")
    obs1 = Thermometer()
    obs2 = HeartbeatMonitor()

    for i in range(15):
        time.sleep(1)
        print("====== Шаг {} =======".format(i + 1))

        if i == 3:
            sub.add_obs(obs1) # На третью итерацию добавляем наблюдателя температуры
        elif i == 5:
            sub.add_obs(obs2) # На пятую итерацию добавляем наблюдателя пульса
        elif i == 10:
            sub.remove_obs(obs1) # На десятую итерацию убираем наблюдателя температуры

        if i % 3 == 0:
            sub.set_value("temperature", 35.5 + 0.5 * i)
        elif i % 3 == 1:
            sub.set_value("heartrate", 30 + 10 * i)

Результат:

Декоратор (decorator)

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

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

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

import sys


def memoize(f):
    cache = dict()

    def wrapper(x):
        if x not in cache:
            cache[x] = f(x)
        return cache[x]

    return wrapper


@memoize
def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)


if __name__ == "__main__":
    sys.setrecursionlimit(2000)
    print(fib(750))

Результат:

Абстрактная фабрика (abstract factory)

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

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

Пример: создание элементов пользовательского интерфейса для разных операционных систем.

class Button:
    def draw(self):
        raise NotImplementedError


class Checkbox:
    def draw(self):
        raise NotImplementedError


class WindowsButton(Button):
    def draw(self):
        return "Drawing a Windows Button"


class WindowsCheckbox(Checkbox):
    def draw(self):
        return "Drawing a Windows Checkbox"


class MacOSButton(Button):
    def draw(self):
        return "Drawing a MacOS Button"


class MacOSCheckbox(Checkbox):
    def draw(self):
        return "Drawing a MacOS Checkbox"


class GUIFactory:
    def create_button(self):
        raise NotImplementedError

    def create_checkbox(self):
        raise NotImplementedError


class WindowsGUIFactory(GUIFactory):
    def create_button(self):
        return WindowsButton()

    def create_checkbox(self):
        return WindowsCheckbox()


class MacOSGUIFactory(GUIFactory):
    def create_button(self):
        return MacOSButton()

    def create_checkbox(self):
        return MacOSCheckbox()


def create_ui(factory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    return button.draw(), checkbox.draw()


if __name__ == "__main__":
    windows_factory = WindowsGUIFactory()
    windows_button, windows_checkbox = create_ui(windows_factory)
    print(f"Windows UI: {windows_button}, {windows_checkbox}")

    macos_factory = MacOSGUIFactory()
    macos_button, macos_checkbox = create_ui(macos_factory)
    print(f"MacOS UI: {macos_button}, {macos_checkbox}")

Результат:

Фабричный метод (factory method)

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

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

Пример: создание разных типов документов.

class Document:
    def __init__(self, content):
        self.content = content

    def render(self):
        raise NotImplementedError("Subclasses must implement this method")


class PDFDocument(Document):
    def render(self):
        return f"Rendering PDF Document: {self.content}"


class HTMLDocument(Document):
    def render(self):
        return f"Rendering HTML Document: {self.content}"


class DocumentCreator:
    def create_document(self, content):
        raise NotImplementedError("Subclasses must implement this method")

    def display_document(self, content):
        document = self.create_document(content)
        print(document.render())


class PDFDocumentCreator(DocumentCreator):
    def create_document(self, content):
        return PDFDocument(content)


class HTMLDocumentCreator(DocumentCreator):
    def create_document(self, content):
        return HTMLDocument(content)


if __name__ == "__main__":
    pdf_creator = PDFDocumentCreator()
    pdf_creator.display_document("This is a PDF document")

    html_creator = HTMLDocumentCreator()
    html_creator.display_document("This is an HTML document")

Результат:

Одиночка (singleton)

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

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

Пример:

class SingletonClass:
    _instance = None 

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(SingletonClass, cls).__new__(cls)
        return cls._instance


singleton = SingletonClass()
new_singleton = SingletonClass()

print(singleton is new_singleton)

singleton.singl_variable = "2"
print(new_singleton.singl_variable)

Результат:

Команда (command)

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

Простыми словами: позволяет отделить то, что нужно сделать, от того, как это сделать.

Пример: включение/выключение света.

from abc import ABC, abstractmethod


class Command(ABC):
    @abstractmethod
    def execute(self):
        pass


class Light:
    def turn_on(self):
        print("The light is ON")

    def turn_off(self):
        print("The light is OFF")


class TurnOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_on()


class TurnOffCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_off()


class RemoteControl:
    def __init__(self):
        self.command = None

    def set_command(self, command):
        self.command = command

    def press_button(self):
        if self.command:
            self.command.execute()


if __name__ == "__main__":
    light = Light()
    remote = RemoteControl()

    turn_on = TurnOnCommand(light)
    turn_off = TurnOffCommand(light)

    remote.set_command(turn_on)
    remote.press_button()

    remote.set_command(turn_off)
    remote.press_button()

Результат:

Адаптер (adapter)

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

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

Пример:

# Интерфейс, который ожидает клиент
class NotificationService:
    def send_notification(self, message, recipient):
        raise NotImplementedError("Subclasses must implement this method")


# Класс, который нужно адаптировать
class LegacyNotificationSystem:
    def send_legacy_notification(self, user_id, text):
        print(f"Legacy system: Sending notification '{text}' to user {user_id}")


# Адаптер
class NotificationAdapter(NotificationService):
    def __init__(self, legacy_system):
        self.legacy_system = legacy_system

    def send_notification(self, message, recipient):
        # Преобразуем данные в формат, понятный для LegacyNotificationSystem
        self.legacy_system.send_legacy_notification(recipient, message)


class Client:
    def __init__(self, notification_service):
        self.notification_service = notification_service

    def send_message(self, message, recipient):
        self.notification_service.send_notification(message, recipient)


if __name__ == "__main__":
    legacy_system = LegacyNotificationSystem()
    adapter = NotificationAdapter(legacy_system)

    client = Client(adapter)
    client.send_message("Hello, world!", "12345")

Результат:

Фасад (facade)

Определение: паттерн предоставляет унифицированный интерфейс к группе интерфейсов подсистемы.Он определяет высокоуровневый интерфейс, упрощающий работу с подсистемой.

Простыми словами: предоставляет упрощённый интерфейс к сложной системе, скрывает сложность системы и предоставляет клиенту более удобный и простой способ взаимодействия с ней.

Пример: вместо того, чтобы пользователю по-отдельности пользоваться классам Inventory, Payment и Notification был собран один класс OrderFacade, с которым намного проще обращаться.

class Inventory:
    def check_stock(self, product_id):
        print(f"Checking stock for product {product_id}")
        return True

    def update_stock(self, product_id, quantity):
        print(f"Updating stock for product {product_id} by {quantity}")


class Payment:
    def process_payment(self, amount):
        print(f"Processing payment of ${amount}")
        return True


class Notification:
    def send_confirmation(self, order_id):
        print(f"Sending confirmation for order {order_id}")


class OrderFacade:
    def __init__(self):
        self.inventory = Inventory()
        self.payment = Payment()
        self.notification = Notification()

    def place_order(self, product_id, quantity, amount):
        if self.inventory.check_stock(product_id):
            if self.payment.process_payment(amount):
                self.inventory.update_stock(product_id, -quantity)
                self.notification.send_confirmation(product_id)
                print("Order placed successfully")
            else:
                print("Payment processing failed")
        else:
            print("Product is out of stock")


if __name__ == "__main__":
    facade = OrderFacade()
    facade.place_order(product_id=1, quantity=1, amount=100)

Результат:

Шаблонный метод (template method)

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

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

Пример:

class ReportGenerator: 
    def generate_report(self):
        self.collect_data()
        self.format_data()
        self.generate_header()
        self.generate_body()
        self.generate_footer()
        self.output_report()

    def collect_data(self):
        raise NotImplementedError("Subclasses must implement this method")

    def format_data(self):
        raise NotImplementedError("Subclasses must implement this method")

    def generate_header(self):
        print("Generating default header")

    def generate_footer(self):
        print("Generating default footer")

    def output_report(self):
        print("Outputting report to console")


class SalesReportGenerator(ReportGenerator):  # ConcreteClass
    def collect_data(self):
        print("Collecting sales data")
        self.sales_data = ["Sales 1", "Sales 2", "Sales 3"]

    def format_data(self):
        print("Formatting sales data")
        self.formatted_sales_data = "\n".join(self.sales_data)

    def generate_header(self):
        print("Generating Sales Report Header")

    def generate_body(self):
        print("Generating Sales Report Body")
        print(self.formatted_sales_data)


class PerformanceReportGenerator(ReportGenerator):  # ConcreteClass
    def collect_data(self):
        print("Collecting performance data")
        self.performance_data = ["Perf 1", "Perf 2", "Perf 3"]

    def format_data(self):
        print("Formatting performance data for web output")
        self.formatted_performance_data = "<br>".join(self.performance_data)

    def generate_body(self):
        print("Generating Performance Report Body")
        print(self.formatted_performance_data)

    def output_report(self):
        print("Outputting report to web page")


if __name__ == "__main__":
    sales_report = SalesReportGenerator()
    sales_report.generate_report()

    performance_report = PerformanceReportGenerator()
    performance_report.generate_report()

Результат:

Итератор (iterator)

Определение: паттерн предоставляет механизм последовательного перебора элементов коллекции без раскрытия её внутреннего представления.

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

Пример:

class NumberIterator:
    def __init__(self, numbers):
        self._numbers = numbers
        self._index = 0

    def __iter__(self):
        return self

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


numbers = [1, 2, 3, 4, 5]
iterator = NumberIterator(numbers)

for number in iterator:
    print(number)

Результат:

Компоновщик (composite)

Определение: паттерн объединяет объекты в древовидные структуры для представления иерархий «часть/целое». Позволяет клиенту выполнять однородные операции с отдельными объектами и их совокупностями.

Простыми словами: позволяет объединять объекты в древовидные структуры и работать с ними как с единым целым, так и по‑отдельности.

Пример: файловая система.

from abc import ABC, abstractmethod


class FileSystem(ABC):
    @abstractmethod
    def print(self, indent: int = 0) -> None:
        pass
class File(FileSystem):
    def __init__(self, name: str, size: int):
        self.name = name
        self.size = size

    def print(self, indent: int = 0) -> None:
        print(" " * indent + f"Файл: {self.name} (Размер: {self.size} KB)")
class Directory(FileSystem):
    def __init__(self, name: str):
        self.name = name
        self.contents: list = []

    def print(self, indent: int = 0) -> None:
        print(" " * indent + f"Папка: {self.name}")
        for entity in self.contents:
            entity.print(indent + 2)

    def add_entity(self, entity: FileSystem) -> None:
        self.contents.append(entity)

    def remove_entity(self, entity: FileSystem) -> None:
        if entity in self.contents:
            self.contents.remove(entity)
def main():
    file1 = File("file1.txt", 128)
    file2 = File("file2.txt", 1024)
    file3 = File("file3.txt", 2048)

    dir1 = Directory("dir1")
    dir1.add_entity(file1)
    dir1.add_entity(file2)

    nested_dir = Directory("nested_dir")
    nested_dir.add_entity(file3)

    dir1.add_entity(nested_dir)

    root_dir = Directory("root")
    root_dir.add_entity(dir1)
    root_dir.add_entity(File("root_file.txt", 256))

    root_dir.print()


if __name__ == "__main__":
    main()

Результат:

Состояние (state)

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

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

Пример: работа лифта у которого есть два состояния: первый этаж, второй этаж.

from __future__ import annotations
from abc import ABC, abstractmethod


class Elevator:
    _state = None

    def __init__(self, state: State) -> None:
        self.setElevator(state)

    def setElevator(self, state: State):
        self._state = state
        self._state.elevator = self

    def presentState(self):
        print(f"Лифт на {type(self._state).__name__}")

    def pushDownBtn(self):
        self._state.pushDownBtn()

    def pushUpBtn(self):
        self._state.pushUpBtn()


class State(ABC):
    def __init__(self):
        self._elevator = None

    @property
    def elevator(self) -> Elevator:
        return self._elevator

    @elevator.setter
    def elevator(self, elevator: Elevator) -> None:
        self._elevator = elevator

    @abstractmethod
    def pushDownBtn(self) -> None:
        pass

    @abstractmethod
    def pushUpBtn(self) -> None:
        pass
class FirstFloor(State):

    def pushDownBtn(self) -> None:
        print("Уже на первом этаже")

    def pushUpBtn(self) -> None:
        print("Лифт поднимается на второй этаж")
        self.elevator.setElevator(SecondFloor())


class SecondFloor(State):

    def pushDownBtn(self) -> None:
        print("Лифт опускается на первый этаж")
        self.elevator.setElevator(FirstFloor())

    def pushUpBtn(self) -> None:
        print("Лифт уже на втором этаже")
if __name__ == "__main__":
    myElevator = Elevator(FirstFloor())
    myElevator.presentState()

    myElevator.pushUpBtn()
    myElevator.presentState()

    myElevator.pushDownBtn()
    myElevator.presentState()

Результат:

Заместитель (proxy)

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

Простыми словами: заместитель позволяет управлять доступом к объекту не изменяя сам объект.

Пример: отложенная загрузка изображения.

class Image:
    def __init__(self, filename):
        self.filename = filename
        self.image = None  # Изображение еще не загружено

    def display(self):
        raise NotImplementedError


class RealImage(Image):
    def __init__(self, filename):
        super().__init__(filename)
        self.load_from_disk()  # Загрузка сразу при создании

    def load_from_disk(self):
        print(f"Загружаю {self.filename} с диска...")
        self.image = f"информация о {self.filename}"
        print(f"Картинка {self.filename} загружена.")

    def display(self):
        print(f"Отображена {self.image}")


class ProxyImage(Image):
    def __init__(self, filename):
        super().__init__(filename)
        self.real_image = None  # Реальное изображение еще не создано

    def display(self):
        if self.real_image is None:
            self.real_image = RealImage(self.filename)  # Загружаем только при необходимости
        self.real_image.display()


if __name__ == "__main__":
    image1 = ProxyImage("image1.jpg")
    image2 = ProxyImage("image2.png")

    print("Картинки ещё не созданы")

    image1.display()  # Вот тут произойдет загрузка image1
    image2.display()  # Вот тут произойдет загрузка image2
    

Результат:

Tags:
Hubs:
+10
Comments10

Articles