Design Patterns در پایتون
Design Patterns راهحلهای آزمایششده برای مسائل تکراری در طراحی نرمافزار هستند. در این فصل با محبوبترین الگوها (Singleton، Factory، Observer، Strategy، Decorator، Adapter، Command) و نسخه Pythonic آنها آشنا میشویم.
Design Patterns راهحلهای آزمایششده برای مسائل تکراری در طراحی نرمافزار هستند. در این فصل با محبوبترین الگوها (Singleton، Factory، Observer، Strategy، Decorator، Adapter، Command) و نسخه Pythonic آنها آشنا میشویم.
چرا Design Patterns؟
- زبان مشترک برای تیم
- راهحلهای آزمودهشده
- کد قابل توسعه و نگهداری
- جلوگیری از باز کردن چرخ از نو
Singleton – تنها یک نمونه
هدف: اطمینان از وجود فقط یک نمونه از یک کلاس.
۱. با Metaclass
class SingletonMeta(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=SingletonMeta):
def __init__(self):
print("اتصال جدید به دیتابیس")
self.connection = "connected"
db1 = Database() # اتصال جدید به دیتابیس
db2 = Database() # (پیغامی چاپ نمیشود)
print(db1 is db2) # True
۲. با Decorator
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Logger:
def __init__(self):
self.logs = []
def log(self, msg):
self.logs.append(msg)
logger1 = Logger()
logger2 = Logger()
print(logger1 is logger2) # True
۳. Pythonic – استفاده از Module
# config.py
class _Config:
def __init__(self):
self.api_url = "https://api.example.com"
self.debug = False
# instance ماژولی
config = _Config()
# main.py
from config import config
print(config.api_url)
# همه جا یک instance
Factory – تولید اشیاء
هدف: ساخت شیء بدون مشخص کردن کلاس دقیق.
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount): pass
class ZarinPalPayment(Payment):
def pay(self, amount):
return f"پرداخت {amount} از زرینپال"
class IDPayPayment(Payment):
def pay(self, amount):
return f"پرداخت {amount} از آیدیپی"
class PayIRPayment(Payment):
def pay(self, amount):
return f"پرداخت {amount} از پی"
class PaymentFactory:
@staticmethod
def create(provider):
providers = {
"zarinpal": ZarinPalPayment,
"idpay": IDPayPayment,
"payir": PayIRPayment,
}
if provider not in providers:
raise ValueError(f"درگاه ناشناخته: {provider}")
return providers[provider]()
# استفاده
payment = PaymentFactory.create("zarinpal")
print(payment.pay(50000))
# پرداخت 50000 از زرینپال
Pythonic – dict مستقیم
PAYMENT_PROVIDERS = {
"zarinpal": ZarinPalPayment,
"idpay": IDPayPayment,
"payir": PayIRPayment,
}
provider_name = "zarinpal"
payment = PAYMENT_PROVIDERS[provider_name]()
Observer – الگوی ناظر
هدف: اطلاعرسانی به چند شیء وقتی یک شیء تغییر میکند.
class Observable:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self, event):
for observer in self._observers:
observer.update(event)
class NewsAgency(Observable):
def publish(self, news):
print(f"خبر: {news}")
self.notify(news)
class Subscriber:
def __init__(self, name):
self.name = name
def update(self, event):
print(f"{self.name} دریافت کرد: {event}")
# استفاده
agency = NewsAgency()
agency.attach(Subscriber("علی"))
agency.attach(Subscriber("سارا"))
agency.publish("افتتاح نمایشگاه فرش")
# خبر: افتتاح نمایشگاه فرش
# علی دریافت کرد: افتتاح نمایشگاه فرش
# سارا دریافت کرد: افتتاح نمایشگاه فرش
Pythonic – با callbacks
class NewsAgency:
def __init__(self):
self.subscribers = []
def subscribe(self, callback):
self.subscribers.append(callback)
return callback # برای استفاده بهعنوان decorator
def publish(self, news):
for callback in self.subscribers:
callback(news)
agency = NewsAgency()
@agency.subscribe
def email_alert(news):
print(f"ایمیل: {news}")
@agency.subscribe
def sms_alert(news):
print(f"پیامک: {news}")
agency.publish("فروش ویژه!")
# ایمیل: فروش ویژه!
# پیامک: فروش ویژه!
Strategy – تعویض الگوریتم
هدف: تعریف خانوادهای از الگوریتمها قابل تعویض در زمان اجرا.
from abc import ABC, abstractmethod
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, data): pass
class ZipCompression(CompressionStrategy):
def compress(self, data):
return f"فشردهسازی ZIP: {data}"
class GzipCompression(CompressionStrategy):
def compress(self, data):
return f"فشردهسازی GZIP: {data}"
class TarCompression(CompressionStrategy):
def compress(self, data):
return f"فشردهسازی TAR: {data}"
class FileCompressor:
def __init__(self, strategy: CompressionStrategy):
self.strategy = strategy
def compress_file(self, data):
return self.strategy.compress(data)
# استفاده
compressor = FileCompressor(ZipCompression())
print(compressor.compress_file("data.txt"))
# تعویض در زمان اجرا
compressor.strategy = GzipCompression()
print(compressor.compress_file("data.txt"))
Pythonic – با توابع
def zip_compress(data):
return f"ZIP: {data}"
def gzip_compress(data):
return f"GZIP: {data}"
# تابع بهعنوان strategy
class FileCompressor:
def __init__(self, strategy):
self.strategy = strategy
def compress_file(self, data):
return self.strategy(data)
c = FileCompressor(zip_compress)
print(c.compress_file("data"))
c.strategy = gzip_compress
print(c.compress_file("data"))
Decorator Pattern
هدف: افزودن قابلیت به شیء بدون تغییر کلاس آن. (متفاوت از @decorator پایتون!)
class Coffee:
def cost(self):
return 5000
def description(self):
return "قهوه ساده"
class CoffeeDecorator:
def __init__(self, coffee):
self.coffee = coffee
class Milk(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 2000
def description(self):
return self.coffee.description() + ", شیر"
class Sugar(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 1000
def description(self):
return self.coffee.description() + ", شکر"
class Caramel(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 3000
def description(self):
return self.coffee.description() + ", کاراملا"
# استفاده
coffee = Coffee()
coffee = Milk(coffee)
coffee = Sugar(coffee)
coffee = Caramel(coffee)
print(coffee.description()) # قهوه ساده, شیر, شکر, کاراملا
print(coffee.cost()) # 11000
Adapter – وفق دادن
هدف: تطبیق interface یک کلاس با interface دیگر.
# کلاس قدیمی (شاید از library خارجی)
class OldPaymentSystem:
def make_payment(self, amount_in_rials):
return f"پرداخت {amount_in_rials} ریال"
# Interface جدید مورد انتظار
class ModernPayment:
def pay(self, amount_in_tomans):
pass
# Adapter
class PaymentAdapter(ModernPayment):
def __init__(self, old_system):
self.old_system = old_system
def pay(self, amount_in_tomans):
# تبدیل تومان به ریال
amount_in_rials = amount_in_tomans * 10
return self.old_system.make_payment(amount_in_rials)
# استفاده
old = OldPaymentSystem()
adapter = PaymentAdapter(old)
print(adapter.pay(5000)) # پرداخت 50000 ریال
Command – دستور بهعنوان شیء
هدف: کپسوله کردن درخواست در یک شیء، برای undo، queue، log.
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self): pass
@abstractmethod
def undo(self): pass
class TextEditor:
def __init__(self):
self.text = ""
class WriteCommand(Command):
def __init__(self, editor, text):
self.editor = editor
self.text = text
def execute(self):
self.editor.text += self.text
def undo(self):
self.editor.text = self.editor.text[:-len(self.text)]
class History:
def __init__(self):
self.commands = []
def execute(self, command):
command.execute()
self.commands.append(command)
def undo(self):
if self.commands:
command = self.commands.pop()
command.undo()
# استفاده
editor = TextEditor()
history = History()
history.execute(WriteCommand(editor, "سلام "))
history.execute(WriteCommand(editor, "دنیا"))
print(editor.text) # سلام دنیا
history.undo()
print(editor.text) # سلام
history.undo()
print(editor.text) # (خالی)
State – حالتها
from abc import ABC, abstractmethod
class OrderState(ABC):
@abstractmethod
def next(self, order): pass
@abstractmethod
def cancel(self, order): pass
class Pending(OrderState):
def next(self, order):
order.state = Processing()
return "به Processing تغییر کرد"
def cancel(self, order):
order.state = Cancelled()
return "لغو شد"
class Processing(OrderState):
def next(self, order):
order.state = Shipped()
return "ارسال شد"
def cancel(self, order):
order.state = Cancelled()
return "لغو شد (پس از تولید)"
class Shipped(OrderState):
def next(self, order):
order.state = Delivered()
return "تحویل شد"
def cancel(self, order):
return "نمیتوان پس از ارسال لغو کرد"
class Delivered(OrderState):
def next(self, order):
return "سفارش قبلاً تحویل داده شده"
def cancel(self, order):
return "نمیتوان لغو کرد"
class Cancelled(OrderState):
def next(self, order):
return "سفارش لغو شده"
def cancel(self, order):
return "قبلاً لغو شده"
class Order:
def __init__(self):
self.state = Pending()
def next(self):
return self.state.next(self)
def cancel(self):
return self.state.cancel(self)
# استفاده
order = Order()
print(order.next()) # به Processing تغییر کرد
print(order.next()) # ارسال شد
print(order.cancel()) # نمیتوان پس از ارسال لغو کرد
print(order.next()) # تحویل شد
الگوهای Pythonic
| الگوی کلاسیک | راه Pythonic |
|---|---|
| Singleton | module یا @lru_cache |
| Factory | dict یا تابع کارخانه |
| Strategy | تابع بهعنوان پارامتر |
| Observer | callback یا signal/blinker |
| Decorator pattern | @decorator |
| Iterator | generator |
| Template Method | تابع با callback |
بهترین شیوهها
- الگو وقتی نیاز شد استفاده کنید، نه پیشاپیش
- پایتونیکترین راه را اول بسنجید
- composition بر inheritance ترجیح دارد
- الگوها نباید کد را پیچیدهتر کنند
- “Simple is better than complex”
جمعبندی
- Singleton: یک نمونه – در پایتون با module سادهتر
- Factory: ساخت شیء انتزاعی
- Observer: اطلاعرسانی تغییرات
- Strategy: تعویض الگوریتم – با تابع سادهتر
- Decorator: افزودن قابلیت پویا
- Adapter: تطبیق interface
- Command: کپسوله کردن دستور برای undo
- State: مدیریت حالتهای پیچیده