~/icsd.ir — bash
SYSTEM_ONLINE

Design Patterns در پایتون

Design Patterns راه‌حل‌های آزمایش‌شده برای مسائل تکراری در طراحی نرم‌افزار هستند. در این فصل با محبوب‌ترین الگوها (Singleton، Factory، Observer، Strategy، Decorator، Adapter، Command) و نسخه Pythonic آن‌ها آشنا می‌شویم.

Design Patterns راه‌حل‌های آزمایش‌شده برای مسائل تکراری در طراحی نرم‌افزار هستند. در این فصل با محبوب‌ترین الگوها (Singleton، Factory، Observer، Strategy، Decorator، Adapter، Command) و نسخه Pythonic آن‌ها آشنا می‌شویم.

چرا Design Patterns؟

  • زبان مشترک برای تیم
  • راه‌حل‌های آزموده‌شده
  • کد قابل توسعه و نگهداری
  • جلوگیری از باز کردن چرخ از نو
نکته مهم: در پایتون، بسیاری از الگوهای کلاسیک GoF (که برای جاوا و C++ طراحی شده‌اند) ساده‌تر و Pythonic می‌شوند. زبانی که توابع را first-class می‌داند، نیاز کمتری به برخی الگوها دارد.

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: مدیریت حالت‌های پیچیده

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.