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

от автора

Наблюдатель (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     

Результат:


ссылка на оригинал статьи https://habr.com/ru/articles/930094/