Разделение абстракции и реализации. Древовидные структуры через Composite. Применимость в Python.
Bridge разделяет, чтобы объединить. Composite объединяет, чтобы упростить.
Bridge — структурный паттерн, разделяющий абстракцию и реализацию так, чтобы их можно было изменять независимо.
# ❌ ПЛОХО: Взрыв классов при комбинации
class Shape:
def draw(self): pass
class Circle(Shape):
def draw(self):
print("Drawing circle")
class Square(Shape):
def draw(self):
print("Drawing square")
# Теперь нужно для каждого цвета!
class RedCircle(Circle):
def draw(self):
print("Drawing RED circle")
class BlueCircle(Circle):
def draw(self):
print("Drawing BLUE circle")
class RedSquare(Square):
def draw(self):
print("Drawing RED square")
# 3 формы × 5 цветов = 15 классов!# ✅ ХОРОШО: Разделение через композицию
from abc import ABC, abstractmethod
# Реализация
class Renderer(ABC):
@abstractmethod
def render_circle(self, radius: int): pass
@abstractmethod
def render_square(self, side: int): pass
class VectorRenderer(Renderer):
def render_circle(self, radius: int):
print(f"Vector: circle r={radius}")
def render_square(self, side: int):
print(f"Vector: square side={side}")
class RasterRenderer(Renderer):
def render_circle(self, radius: int):
print(f"Raster: circle r={radius}, pixels={radius*2}")
def render_square(self, side: int):
print(f"Raster: square side={side}, pixels={side*side}")
# Абстракция
class Shape(ABC):
def __init__(self, renderer: Renderer):
self._renderer = renderer
@abstractmethod
def draw(self): pass
class Circle(Shape):
def __init__(self, renderer: Renderer, radius: int):
super().__init__(renderer)
self.radius = radius
def draw(self):
self._renderer.render_circle(self.radius)
class Square(Shape):
def __init__(self, renderer: Renderer, side: int):
super().__init__(renderer)
self.side = side
def draw(self):
self._renderer.render_square(self.side)
# Использование
vector = VectorRenderer()
raster = RasterRenderer()
circle = Circle(vector, 10)
circle.draw() # Vector: circle r=10
square = Square(raster, 5)
square.draw() # Raster: square side=5, pixels=25
# Можно менять реализацию в runtime
circle._renderer = raster
circle.draw() # Raster: circle r=10, pixels=20Преимущества:
Composite — структурный паттерн, организующий объекты в древовидные структуры и позволяющий работать с ними единообразно.
# ❌ ПЛОХО: Клиент знает о типах объектов
class File:
def __init__(self, name: str, size: int):
self.name = name
self.size = size
class Folder:
def __init__(self, name: str):
self.name = name
self.children = []
def add(self, child):
self.children.append(child)
# Клиент должен проверять тип
def get_size(item):
if isinstance(item, File):
return item.size
elif isinstance(item, Folder):
total = 0
for child in item.children:
total += get_size(child) # Рекурсия!
return total
else:
raise TypeError("Unknown type")# ✅ ХОРОШО: Единый интерфейс для всех
from abc import ABC, abstractmethod
from typing import List
class FileSystemItem(ABC):
@abstractmethod
def get_size(self) -> int:
pass
@abstractmethod
def print_structure(self, indent: int = 0):
pass
class File(FileSystemItem):
def __init__(self, name: str, size: int):
self.name = name
self.size = size
def get_size(self) -> int:
return self.size
def print_structure(self, indent: int = 0):
print(" " * indent + f"📄 {self.name} ({self.size} bytes)")
class Folder(FileSystemItem):
def __init__(self, name: str):
self.name = name
self.children: List[FileSystemItem] = []
def add(self, child: FileSystemItem):
self.children.append(child)
def remove(self, child: FileSystemItem):
self.children.remove(child)
def get_size(self) -> int:
return sum(child.get_size() for child in self.children)
def print_structure(self, indent: int = 0):
print(" " * indent + f"📁 {self.name}")
for child in self.children:
child.print_structure(indent + 2)
# Использование — клиент работает с любым типом единообразно
root = Folder("root")
docs = Folder("docs")
file1 = File("readme.md", 1024)
file2 = File("notes.txt", 512)
root.add(docs)
root.add(File("config.yaml", 256))
docs.add(file1)
docs.add(file2)
# Единый интерфейс
print(f"Total size: {root.get_size()} bytes") # 1792 bytes
root.print_structure()
# 📁 root
# 📁 docs
# 📄 readme.md (1024 bytes)
# 📄 notes.txt (512 bytes)
# 📄 config.yaml (256 bytes)from abc import ABC, abstractmethod
from typing import List, Optional
class Component(ABC):
def __init__(self, name: str):
self.name = name
self.parent: Optional["Component"] = None
def set_parent(self, parent: "Component"):
self.parent = parent
def get_parent(self) -> Optional["Component"]:
return self.parent
@abstractmethod
def operation(self) -> str:
pass
class Leaf(Component):
def __init__(self, name: str, value: str):
super().__init__(name)
self.value = value
def operation(self) -> str:
return f"{self.name}: {self.value}"
class Composite(Component):
def __init__(self, name: str):
super().__init__(name)
self._children: List[Component] = []
def add(self, component: Component) -> None:
self._children.append(component)
component.set_parent(self)
def remove(self, component: Component) -> None:
self._children.remove(component)
component.set_parent(None)
def operation(self) -> str:
results = [child.operation() for child in self._children]
return f"{self.name}: [{', '.join(results)}]"
def is_composite(self) -> bool:
return True
def find(self, name: str) -> Optional[Component]:
"""Поиск по дереву"""
if self.name == name:
return self
for child in self._children:
if child.is_composite():
result = child.find(name)
if result:
return result
elif child.name == name:
return child
return None
# Использование
root = Composite("root")
leaf1 = Leaf("leaf1", "value1")
leaf2 = Leaf("leaf2", "value2")
sub_composite = Composite("sub")
sub_composite.add(Leaf("sub_leaf1", "sub_value1"))
root.add(leaf1)
root.add(leaf2)
root.add(sub_composite)
print(root.operation())
# root: [leaf1: value1, leaf2: value2, sub: [sub_leaf1: sub_value1]]
# Поиск
found = root.find("sub_leaf1")
print(found.operation()) # sub_leaf1: sub_value1from abc import ABC, abstractmethod
class UIComponent(ABC):
@abstractmethod
def render(self) -> str:
pass
@abstractmethod
def click(self, x: int, y: int) -> bool:
pass
class Button(UIComponent):
def __init__(self, text: str, x: int, y: int, width: int, height: int):
self.text = text
self.x = x
self.y = y
self.width = width
self.height = height
def render(self) -> str:
return f"[Button '{self.text}' at ({self.x}, {self.y})]"
def click(self, x: int, y: int) -> bool:
return (self.x <= x <= self.x + self.width and
self.y <= y <= self.y + self.height)
class Panel(UIComponent):
def __init__(self, x: int, y: int, width: int, height: int):
self.x = x
self.y = y
self.width = width
self.height = height
self.children: List[UIComponent] = []
def add(self, child: UIComponent):
self.children.append(child)
def render(self) -> str:
rendered = [f"[Panel at ({self.x}, {self.y})]"]
for child in self.children:
rendered.append(" " + child.render())
return "\n".join(rendered)
def click(self, x: int, y: int) -> bool:
if not (self.x <= x <= self.x + self.width and
self.y <= y <= self.y + self.height):
return False
for child in self.children:
if child.click(x, y):
return True
return False
# Использование
panel = Panel(0, 0, 200, 300)
panel.add(Button("OK", 10, 10, 50, 30))
panel.add(Button("Cancel", 70, 10, 70, 30))
print(panel.render())
# [Panel at (0, 0)]
# [Button 'OK' at (10, 10)]
# [Button 'Cancel' at (70, 10)]
# Обработка клика
if panel.click(15, 15):
print("Clicked inside panel!")class MenuItem(ABC):
@abstractmethod
def get_label(self) -> str:
pass
@abstractmethod
def execute(self):
pass
class ActionMenuItem(MenuItem):
def __init__(self, label: str, action):
self.label = label
self.action = action
def get_label(self) -> str:
return self.label
def execute(self):
print(f"Executing: {self.label}")
self.action()
class Menu(MenuItem):
def __init__(self, label: str):
self.label = label
self.items: List[MenuItem] = []
def add(self, item: MenuItem):
self.items.append(item)
def get_label(self) -> str:
return self.label
def execute(self):
print(f"\n=== {self.label} ===")
for i, item in enumerate(self.items, 1):
print(f"{i}. {item.get_label()}")
def select(self, index: int):
if 1 <= index <= len(self.items):
self.items[index - 1].execute()
# Использование
file_menu = Menu("File")
file_menu.add(ActionMenuItem("New", lambda: print("Creating new file")))
file_menu.add(ActionMenuItem("Open", lambda: print("Opening file")))
file_menu.add(ActionMenuItem("Save", lambda: print("Saving file")))
edit_menu = Menu("Edit")
edit_menu.add(ActionMenuItem("Cut", lambda: print("Cutting")))
edit_menu.add(ActionMenuItem("Copy", lambda: print("Copying")))
edit_menu.add(ActionMenuItem("Paste", lambda: print("Pasting")))
main_menu = Menu("Main")
main_menu.add(file_menu)
main_menu.add(edit_menu)
main_menu.execute()
# === Main ===
# 1. File
# 2. Editfrom abc import ABC, abstractmethod
from typing import List
# Реализация: каналы отправки
class NotificationChannel(ABC):
@abstractmethod
def send(self, recipient: str, message: str):
pass
class EmailChannel(NotificationChannel):
def send(self, recipient: str, message: str):
print(f"📧 Email to {recipient}: {message[:50]}...")
class SMSChannel(NotificationChannel):
def send(self, recipient: str, message: str):
print(f"📱 SMS to {recipient}: {message[:50]}...")
class PushChannel(NotificationChannel):
def send(self, recipient: str, message: str):
print(f"🔔 Push to {recipient}: {message[:50]}...")
# Абстракция: типы уведомлений
class Notification(ABC):
def __init__(self, channel: NotificationChannel):
self._channel = channel
self._recipients: List[str] = []
def add_recipient(self, recipient: str):
self._recipients.append(recipient)
@abstractmethod
def get_message(self) -> str:
pass
def send(self):
message = self.get_message()
for recipient in self._recipients:
self._channel.send(recipient, message)
class AlertNotification(Notification):
def __init__(self, channel: NotificationChannel, priority: str):
super().__init__(channel)
self.priority = priority
def get_message(self) -> str:
return f"[{self.priority.upper()}] ALERT: System warning!"
class NewsletterNotification(Notification):
def __init__(self, channel: NotificationChannel, issue: int):
super().__init__(channel)
self.issue = issue
def get_message(self) -> str:
return f"Newsletter #{self.issue}: Weekly updates..."
# Использование
email = EmailChannel()
sms = SMSChannel()
alert = AlertNotification(email, "high")
alert.add_recipient("admin@example.com")
alert.send() # 📧 Email to admin@example.com: [HIGH] ALERT...
newsletter = NewsletterNotification(sms, 42)
newsletter.add_recipient("+1234567890")
newsletter.send() # 📱 SMS to +1234567890: Newsletter #42...
# Можно легко комбинировать
alert._channel = sms # Смена канала в runtime
alert.send()# ❌ ПЛОХО: Bridge без необходимости
class Renderer(ABC):
@abstractmethod
def render(self, text: str): pass
class ConsoleRenderer(Renderer):
def render(self, text: str):
print(text)
class Text(ABC):
def __init__(self, renderer: Renderer):
self._renderer = renderer
@abstractmethod
def get_text(self) -> str: pass
def display(self):
self._renderer.render(self.get_text())
# Зачем, если можно:
def display_text(text: str):
print(text)# ❌ ПЛОХО: Composite для одного уровня
class Component(ABC):
@abstractmethod
def operation(self): pass
class Leaf(Component):
def operation(self): pass
class Composite(Component):
def __init__(self):
self.children = []
def operation(self):
for child in self.children:
child.operation()
# Если нет вложенности — Composite избыточен| Паттерн | Когда использовать | Pythonic-реализация |
|---|---|---|
| Bridge | Несколько независимых измерений (форма × цвет × рендерер) | Композиция + протоколы |
| Composite | Древовидные структуры (файлы, UI, меню) | Общий базовый класс |
Главный принцип: Bridge разделяет для гибкости, Composite объединяет для единообразия.
Изучите тему Decorator и contextlib для динамического добавления поведения.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.