Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерны ООП (Design Patterns)
За 10+ лет разработки я использовал много паттернов проектирования. Они помогают решить общие проблемы в коде. Я разделю их на три категории: Creational, Structural, Behavioral.
Creational Patterns (Создание объектов)
1. Singleton
Суть: класс имеет только один экземпляр во всей программе
# Плохо: глобальная переменная
logger = None
def get_logger():
global logger
if logger is None:
logger = Logger()
return logger
# Хорошо: Singleton паттерн
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=Singleton):
def __init__(self):
self.connection = connect_to_db()
db1 = Database()
db2 = Database()
print(db1 is db2) # True
Когда использовать:
- Database connection
- Logger
- Configuration manager
- Cache
Минусы:
- Сложно тестировать (трудно мокировать)
- Global state (плохо для параллелизма)
- Может быть легче заменить на Dependency Injection
2. Factory Pattern
Суть: создание объектов без указания их точного класса
# Плохо: в коде много if/else
class PaymentProcessor:
def process(self, method, amount):
if method == 'credit_card':
processor = CreditCardProcessor()
elif method == 'paypal':
processor = PayPalProcessor()
elif method == 'bitcoin':
processor = BitcoinProcessor()
else:
raise ValueError()
return processor.pay(amount)
# Хорошо: Factory паттерн
class PaymentFactory:
_processors = {
'credit_card': CreditCardProcessor,
'paypal': PayPalProcessor,
'bitcoin': BitcoinProcessor,
}
@classmethod
def create(cls, method):
processor_class = cls._processors.get(method)
if not processor_class:
raise ValueError(f"Unknown payment method: {method}")
return processor_class()
# Использование
processor = PaymentFactory.create('paypal')
processor.pay(100)
Когда использовать:
- Создание разных видов объектов
- Когда класс не знает, какой подкласс создавать
- Plugin systems
3. Builder Pattern
Суть: строишь сложный объект пошагово
# Плохо: много параметров в конструкторе
class Report:
def __init__(self, title, author, template, date_format,
include_summary, include_charts, include_footer):
self.title = title
self.author = author
self.template = template
# ... и т.д.
report = Report(
title="Q4 Report",
author="John",
template="corporate",
date_format="dd/mm/yyyy",
include_summary=True,
include_charts=False,
include_footer=True
)
# Хорошо: Builder паттерн
class ReportBuilder:
def __init__(self):
self._title = None
self._author = None
self._template = None
self._options = {}
def with_title(self, title):
self._title = title
return self # Для цепочки вызовов
def with_author(self, author):
self._author = author
return self
def with_template(self, template):
self._template = template
return self
def include_summary(self, value=True):
self._options['summary'] = value
return self
def include_charts(self, value=True):
self._options['charts'] = value
return self
def build(self):
return Report(
title=self._title,
author=self._author,
template=self._template,
**self._options
)
# Использование (гораздо читабельнее!)
report = (ReportBuilder()
.with_title("Q4 Report")
.with_author("John")
.with_template("corporate")
.include_summary()
.include_charts(False)
.build())
Когда использовать:
- Сложные объекты с множеством параметров
- Хотим избежать громоздких конструкторов
- Хотим гибкость в создании
4. Prototype Pattern
Суть: копирование объекта вместо создания нового
import copy
class UserProfile:
def __init__(self, name, email, roles):
self.name = name
self.email = email
self.roles = roles # list
def clone(self):
return copy.deepcopy(self)
# Использование
original = UserProfile("John", "john@example.com", ["admin", "user"])
clone = original.clone()
clone.name = "Jane" # Не влияет на original
Structural Patterns (Структура объектов)
1. Adapter Pattern
Суть: адаптируешь один интерфейс под другой
# Старый код
class LegacyPaymentGateway:
def charge(self, amount):
# Старый API
pass
# Новый код ожидает другой интерфейс
class PaymentProcessor:
def process_payment(self, amount):
pass
# Adapter
class PaymentAdapter(PaymentProcessor):
def __init__(self, legacy_gateway):
self.gateway = legacy_gateway
def process_payment(self, amount):
# Адаптируем старый API под новый
return self.gateway.charge(amount)
# Использование
legacy = LegacyPaymentGateway()
adapted = PaymentAdapter(legacy)
adapted.process_payment(100)
Когда использовать:
- Интеграция старого и нового кода
- Работа со сторонними library с неудобным API
2. Decorator Pattern
Суть: добавляешь новую функциональность к объекту динамически
# Базовый класс
class Coffee:
def cost(self):
return 5
def description(self):
return "Coffee"
# Декораторы (добавляем функциональность)
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 2
def description(self):
return self.coffee.description() + ", Milk"
class SugarDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost() + 0.5
def description(self):
return self.coffee.description() + ", Sugar"
# Использование
coffee = Coffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(coffee.description()) # Coffee, Milk, Sugar
print(coffee.cost()) # 7.5
Python версия (проще):
def log_execution(func):
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@log_execution
def expensive_operation():
return "Result"
3. Facade Pattern
Суть: предоставляешь простой интерфейс к сложной системе
# Сложная система
class EmailService:
def send(self, email, message): pass
class SMSService:
def send(self, phone, message): pass
class SlackService:
def post(self, channel, message): pass
# Facade
class NotificationFacade:
def __init__(self):
self.email = EmailService()
self.sms = SMSService()
self.slack = SlackService()
def notify_user(self, user, message):
"""Простой интерфейс, скрывает сложность"""
self.email.send(user.email, message)
self.sms.send(user.phone, message)
self.slack.post(f"user_{user.id}", message)
# Использование
notifier = NotificationFacade()
notifier.notify_user(user, "Hello!")
Behavioral Patterns (Поведение объектов)
1. Strategy Pattern
Суть: encapsulate алгоритм в класс, чтобы его менять
# Плохо: if/else для разных алгоритмов
class Discount:
def calculate(self, amount, customer_type):
if customer_type == 'regular':
return amount * 0.1
elif customer_type == 'vip':
return amount * 0.2
elif customer_type == 'gold':
return amount * 0.3
# Хорошо: Strategy паттерн
class DiscountStrategy:
def get_discount(self, amount):
pass
class RegularDiscount(DiscountStrategy):
def get_discount(self, amount):
return amount * 0.1
class VIPDiscount(DiscountStrategy):
def get_discount(self, amount):
return amount * 0.2
class GoldDiscount(DiscountStrategy):
def get_discount(self, amount):
return amount * 0.3
class Order:
def __init__(self, amount, strategy):
self.amount = amount
self.strategy = strategy
def get_final_price(self):
discount = self.strategy.get_discount(self.amount)
return self.amount - discount
# Использование
order = Order(100, VIPDiscount())
order.get_final_price() # 80
2. Observer Pattern
Суть: объект уведомляет других об изменении состояния
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, event):
for observer in self._observers:
observer.update(event)
class UserRegistered:
def __init__(self, user_id):
self.user_id = user_id
class EmailNotifier:
def update(self, event):
if isinstance(event, UserRegistered):
print(f"Send welcome email to user {event.user_id}")
class AnalyticsTracker:
def update(self, event):
if isinstance(event, UserRegistered):
print(f"Track registration: {event.user_id}")
# Использование
subject = Subject()
subject.attach(EmailNotifier())
subject.attach(AnalyticsTracker())
subject.notify(UserRegistered(123))
# Output:
# Send welcome email to user 123
# Track registration: 123
3. Chain of Responsibility
Суть: передаёшь запрос по цепочке обработчиков
class Handler:
def __init__(self, next_handler=None):
self.next = next_handler
def handle(self, request):
if self.can_handle(request):
return self.process(request)
elif self.next:
return self.next.handle(request)
return None
def can_handle(self, request):
raise NotImplementedError
def process(self, request):
raise NotImplementedError
class AuthHandler(Handler):
def can_handle(self, request):
return request.needs_auth
def process(self, request):
return f"Auth: {request.path}"
class LogHandler(Handler):
def can_handle(self, request):
return request.should_log
def process(self, request):
return f"Log: {request.path}"
class ProcessHandler(Handler):
def can_handle(self, request):
return True
def process(self, request):
return f"Process: {request.path}"
# Использование
log_handler = LogHandler()
auth_handler = AuthHandler(log_handler)
process_handler = ProcessHandler(auth_handler)
result = auth_handler.handle(request)
4. State Pattern
Суть: меняешь поведение объекта в зависимости от состояния
class OrderState:
def next(self, order):
raise NotImplementedError
class PendingState(OrderState):
def next(self, order):
order.state = ConfirmedState()
class ConfirmedState(OrderState):
def next(self, order):
order.state = ShippedState()
class ShippedState(OrderState):
def next(self, order):
order.state = DeliveredState()
class Order:
def __init__(self):
self.state = PendingState()
def proceed(self):
self.state.next(self)
print(f"Order state: {self.state.__class__.__name__}")
# Использование
order = Order()
order.proceed() # Order state: ConfirmedState
order.proceed() # Order state: ShippedState
SOLID Principles (не паттерны, но близко)
# S — Single Responsibility
# Класс должен иметь только одну причину для изменения
class User:
def save(self): pass # Плохо: не сохраняй сам
class UserRepository:
def save(self, user): pass # Хорошо: отдельный класс
# O — Open/Closed
# Открыт для расширения, закрыт для модификации
class PaymentProcessor:
def process(self, method, amount):
# Не добавляй новые if, используй Strategy
return method.process(amount)
# L — Liskov Substitution
# Подтипы должны быть заменяемы базовыми типами
class Bird:
def fly(self): pass
class Penguin(Bird): # Плохо: пингвины не летают
def fly(self): raise NotImplementedError
# D — Dependency Inversion
# Завись от интерфейсов, не реализаций
class UserService:
def __init__(self, db): # Плохо: конкретная БД
self.db = db
class UserService:
def __init__(self, repository): # Хорошо: интерфейс
self.repository = repository
Какие паттерны я использую каждый день
✅ Все время:
- Decorator (@property, @classmethod)
- Factory
- Strategy
- Observer (event listeners, callbacks)
✅ Часто:
- Builder
- Adapter
- Facade
- State
⚠️ Когда нужно:
- Singleton (Database)
- Chain of Responsibility
- Proxy
- Template Method
❌ Редко:
- Prototype (используем copy вместо)
- Interpreter
- Visitor (сложновато)
Итог: паттерны — это проверенные решения распространённых проблем. Не нужно использовать все, но нужно знать основные. Главное — решить проблему, потом подумать, какой паттерн подойдёт.