الگوهای طراحی رفتاری قدرتمند پایتون: Observer، Strategy و Command را کاوش کنید. با مثالهای عملی، انعطافپذیری، قابلیت نگهداری و مقیاسپذیری کد را بهبود بخشید.
الگوهای رفتاری پایتون: Observer، Strategy و Command
الگوهای طراحی رفتاری ابزارهای ضروری در زرادخانه یک توسعهدهنده نرمافزار هستند. این الگوها به مشکلات رایج ارتباط و تعامل بین اشیاء میپردازند و منجر به کدی انعطافپذیرتر، قابل نگهداریتر و مقیاسپذیرتر میشوند. این راهنمای جامع به سه الگوی رفتاری حیاتی در پایتون میپردازد: Observer، Strategy و Command. ما هدف، پیادهسازی و کاربردهای دنیای واقعی آنها را بررسی خواهیم کرد و شما را با دانش لازم برای استفاده مؤثر از این الگوها در پروژههایتان مجهز خواهیم کرد.
درک الگوهای رفتاری
الگوهای رفتاری بر ارتباط و تعامل بین اشیاء تمرکز دارند. آنها الگوریتمها را تعریف کرده و مسئولیتها را بین اشیاء تقسیم میکنند و از وابستگی سست (loose coupling) و انعطافپذیری اطمینان حاصل میکنند. با استفاده از این الگوها، میتوانید سیستمهایی ایجاد کنید که درک، اصلاح و گسترش آنها آسان باشد.
مزایای کلیدی استفاده از الگوهای رفتاری عبارتند از:
- سازماندهی بهتر کد: با کپسولهسازی رفتارهای خاص، این الگوها ماژولار بودن و وضوح را ترویج میدهند.
- افزایش انعطافپذیری: این الگوها به شما اجازه میدهند رفتار یک سیستم را بدون تغییر اجزای اصلی آن تغییر یا گسترش دهید.
- کاهش وابستگی (Coupling): الگوهای رفتاری وابستگی سست بین اشیاء را ترویج میدهند که نگهداری و تست کد را آسانتر میکند.
- افزایش قابلیت استفاده مجدد: خود الگوها و کدی که آنها را پیادهسازی میکند، میتوانند در بخشهای مختلف برنامه یا حتی در پروژههای مختلف دوباره استفاده شوند.
الگوی Observer
الگوی Observer چیست؟
الگوی Observer یک وابستگی یک-به-چند بین اشیاء تعریف میکند، به طوری که وقتی یک شیء (subject) وضعیت خود را تغییر میدهد، تمام وابستگان آن (observers) به طور خودکار مطلع و بهروز میشوند. این الگو به ویژه زمانی مفید است که نیاز به حفظ ثبات بین چندین شیء بر اساس وضعیت یک شیء واحد دارید. این الگو گاهی اوقات به عنوان الگوی انتشار-اشتراک (Publish-Subscribe) نیز نامیده میشود.
این را مانند اشتراک در یک مجله در نظر بگیرید. شما (observer) برای دریافت بهروزرسانیها (اطلاعیهها) هر زمان که مجله (subject) شماره جدیدی منتشر میکند، ثبتنام میکنید. نیازی نیست که دائماً برای شمارههای جدید بررسی کنید؛ شما به طور خودکار مطلع میشوید.
اجزای الگوی Observer
- Subject (سوژه): شیئی که وضعیت آن مورد توجه است. این شیء لیستی از observerها را نگهداری میکند و متدهایی برای پیوست کردن (اشتراک) و جدا کردن (لغو اشتراک) observerها فراهم میکند.
- Observer (ناظر): یک اینترفیس یا کلاس انتزاعی که متد update را تعریف میکند. این متد توسط subject برای اطلاعرسانی به observerها در مورد تغییرات وضعیت فراخوانی میشود.
- ConcreteSubject: یک پیادهسازی مشخص از Subject که وضعیت را ذخیره میکند و هنگام تغییر وضعیت به observerها اطلاع میدهد.
- ConcreteObserver: یک پیادهسازی مشخص از Observer که متد update را برای واکنش به تغییرات وضعیت در subject پیادهسازی میکند.
پیادهسازی در پایتون
در اینجا یک مثال پایتون برای نمایش الگوی Observer آورده شده است:
class Subject:
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self._state)
@property
def state(self):
return self._state
@state.setter
def state(self, new_state):
self._state = new_state
self.notify()
class Observer:
def update(self, state):
raise NotImplementedError
class ConcreteObserverA(Observer):
def update(self, state):
print(f"ConcreteObserverA: State changed to {state}")
class ConcreteObserverB(Observer):
def update(self, state):
print(f"ConcreteObserverB: State changed to {state}")
# Example Usage
subject = Subject()
observer_a = ConcreteObserverA()
observer_b = ConcreteObserverB()
subject.attach(observer_a)
subject.attach(observer_b)
subject.state = "New State"
subject.detach(observer_a)
subject.state = "Another State"
در این مثال، `Subject` لیستی از اشیاء `Observer` را نگهداری میکند. هنگامی که `state` مربوط به `Subject` تغییر میکند، متد `notify()` را فراخوانی میکند که لیست observerها را پیمایش کرده و متد `update()` آنها را فراخوانی میکند. سپس هر `ConcreteObserver` متناسب با آن به تغییر وضعیت واکنش نشان میدهد.
کاربردهای دنیای واقعی
- مدیریت رویداد (Event Handling): در فریمورکهای رابط کاربری گرافیکی (GUI)، الگوی Observer به طور گسترده برای مدیریت رویدادها استفاده میشود. هنگامی که کاربر با یک عنصر UI تعامل میکند (مثلاً کلیک روی یک دکمه)، آن عنصر (subject) به شنوندگان ثبتشده (observers) رویداد را اطلاع میدهد.
- پخش دادهها (Data Broadcasting): در برنامههای مالی، شاخصهای بورس (subjects) بهروزرسانیهای قیمت را به مشتریان ثبتشده (observers) پخش میکنند.
- برنامههای صفحهگسترده (Spreadsheet): هنگامی که یک سلول در صفحهگسترده تغییر میکند، سلولهای وابسته (observers) به طور خودکار دوباره محاسبه و بهروز میشوند.
- اعلانهای شبکههای اجتماعی: هنگامی که شخصی در یک پلتفرم رسانه اجتماعی پستی منتشر میکند، دنبالکنندگان او (observers) مطلع میشوند.
مزایای الگوی Observer
- وابستگی سست: subject و observerها نیازی به شناخت کلاسهای مشخص یکدیگر ندارند، که این امر ماژولار بودن و قابلیت استفاده مجدد را ترویج میدهد.
- مقیاسپذیری: observerهای جدید را میتوان به راحتی و بدون تغییر subject اضافه کرد.
- انعطافپذیری: subject میتواند observerها را به روشهای مختلفی (مثلاً همزمان یا غیرهمزمان) مطلع کند.
معایب الگوی Observer
- بهروزرسانیهای غیرمنتظره: observerها ممکن است از تغییراتی که به آنها علاقهای ندارند مطلع شوند، که منجر به هدر رفتن منابع میشود.
- زنجیرههای بهروزرسانی: بهروزرسانیهای آبشاری میتوانند پیچیده و اشکالزدایی آنها دشوار شود.
- نشت حافظه (Memory Leaks): اگر observerها به درستی جدا نشوند، ممکن است توسط garbage collector جمعآوری نشوند که منجر به نشت حافظه میشود.
الگوی Strategy
الگوی Strategy چیست؟
الگوی Strategy خانوادهای از الگوریتمها را تعریف میکند، هر یک را کپسوله کرده و آنها را قابل تعویض میکند. Strategy به الگوریتم اجازه میدهد تا مستقل از کلاینتهایی که از آن استفاده میکنند، تغییر کند. این الگو زمانی مفید است که شما چندین روش برای انجام یک کار دارید و میخواهید بتوانید در زمان اجرا بین آنها جابجا شوید بدون اینکه کد کلاینت را تغییر دهید.
تصور کنید از شهری به شهر دیگر سفر میکنید. شما میتوانید استراتژیهای حمل و نقل مختلفی را انتخاب کنید: هواپیما، قطار یا ماشین. الگوی Strategy به شما این امکان را میدهد که بهترین استراتژی حمل و نقل را بر اساس عواملی مانند هزینه، زمان و راحتی انتخاب کنید، بدون اینکه مقصد خود را تغییر دهید.
اجزای الگوی Strategy
- Strategy: یک اینترفیس یا کلاس انتزاعی که الگوریتم را تعریف میکند.
- ConcreteStrategy: پیادهسازیهای مشخص از اینترفیس Strategy که هر کدام نماینده یک الگوریتم متفاوت هستند.
- Context: کلاسی که یک ارجاع به یک شیء Strategy را نگهداری میکند و اجرای الگوریتم را به آن محول میکند. Context نیازی به دانستن پیادهسازی مشخص Strategy ندارد؛ فقط با اینترفیس Strategy تعامل میکند.
پیادهسازی در پایتون
در اینجا یک مثال پایتون برای نمایش الگوی Strategy آورده شده است:
class Strategy:
def execute(self, data):
raise NotImplementedError
class ConcreteStrategyA(Strategy):
def execute(self, data):
print("Executing Strategy A...")
return sorted(data)
class ConcreteStrategyB(Strategy):
def execute(self, data):
print("Executing Strategy B...")
return sorted(data, reverse=True)
class Context:
def __init__(self, strategy):
self._strategy = strategy
def set_strategy(self, strategy):
self._strategy = strategy
def execute_strategy(self, data):
return self._strategy.execute(data)
# Example Usage
data = [1, 5, 3, 2, 4]
strategy_a = ConcreteStrategyA()
context = Context(strategy_a)
result = context.execute_strategy(data)
print(f"Result with Strategy A: {result}")
strategy_b = ConcreteStrategyB()
context.set_strategy(strategy_b)
result = context.execute_strategy(data)
print(f"Result with Strategy B: {result}")
در این مثال، اینترفیس `Strategy` متد `execute()` را تعریف میکند. `ConcreteStrategyA` و `ConcreteStrategyB` پیادهسازیهای متفاوتی از این متد را ارائه میدهند که به ترتیب دادهها را به صورت صعودی و نزولی مرتب میکنند. کلاس `Context` یک ارجاع به یک شیء `Strategy` را نگهداری میکند و اجرای الگوریتم را به آن محول میکند. کلاینت میتواند با فراخوانی متد `set_strategy()` استراتژیها را در زمان اجرا تغییر دهد.
کاربردهای دنیای واقعی
- پردازش پرداخت: پلتفرمهای تجارت الکترونیک از الگوی Strategy برای پشتیبانی از روشهای پرداخت مختلف (مانند کارت اعتباری، PayPal، انتقال بانکی) استفاده میکنند. هر روش پرداخت به عنوان یک استراتژی مشخص پیادهسازی میشود.
- محاسبه هزینه حمل و نقل: فروشگاههای آنلاین از الگوی Strategy برای محاسبه هزینههای حمل و نقل بر اساس عواملی مانند وزن، مقصد و روش حمل و نقل استفاده میکنند.
- فشردهسازی تصویر: نرمافزارهای ویرایش تصویر از الگوی Strategy برای پشتیبانی از الگوریتمهای مختلف فشردهسازی تصویر (مانند JPEG، PNG، GIF) استفاده میکنند.
- اعتبارسنجی دادهها: فرمهای ورود داده میتوانند از استراتژیهای اعتبارسنجی مختلف بر اساس نوع داده وارد شده (مانند آدرس ایمیل، شماره تلفن، تاریخ) استفاده کنند.
- الگوریتمهای مسیریابی: سیستمهای ناوبری GPS از الگوریتمهای مسیریابی مختلف (مانند کوتاهترین مسافت، سریعترین زمان، کمترین ترافیک) بر اساس ترجیحات کاربر استفاده میکنند.
مزایای الگوی Strategy
- انعطافپذیری: شما میتوانید به راحتی استراتژیهای جدید را بدون تغییر context اضافه کنید.
- قابلیت استفاده مجدد: استراتژیها میتوانند در contextهای مختلف دوباره استفاده شوند.
- کپسولهسازی: هر استراتژی در کلاس خود کپسوله شده است که باعث ماژولار بودن و وضوح میشود.
- اصل باز/بسته (Open/Closed Principle): شما میتوانید سیستم را با افزودن استراتژیهای جدید بدون تغییر کد موجود گسترش دهید.
معایب الگوی Strategy
- افزایش پیچیدگی: تعداد کلاسها ممکن است افزایش یابد و سیستم را پیچیدهتر کند.
- آگاهی کلاینت: کلاینت باید از استراتژیهای مختلف موجود آگاه باشد و استراتژی مناسب را انتخاب کند.
الگوی Command
الگوی Command چیست؟
الگوی Command یک درخواست را به عنوان یک شیء کپسوله میکند، بنابراین به شما امکان میدهد کلاینتها را با درخواستهای مختلف پارامتربندی کنید، درخواستها را در صف قرار دهید یا لاگ کنید و از عملیات قابل لغو (undoable) پشتیبانی کنید. این الگو شیئی را که عملیات را فراخوانی میکند از شیئی که میداند چگونه آن را انجام دهد، جدا میکند.
یک رستوران را در نظر بگیرید. شما (client) یک سفارش (command) را به گارسون (invoker) میدهید. گارسون خودش غذا را آماده نمیکند؛ او سفارش را به سرآشپز (receiver) میدهد که در واقع عمل را انجام میدهد. الگوی Command به شما امکان میدهد فرآیند سفارشدهی را از فرآیند پختوپز جدا کنید.
اجزای الگوی Command
- Command: یک اینترفیس یا کلاس انتزاعی که متدی برای اجرای یک درخواست اعلام میکند.
- ConcreteCommand: پیادهسازیهای مشخص از اینترفیس Command که یک شیء receiver را به یک عمل متصل میکنند.
- Receiver: شیئی که کار واقعی را انجام میدهد.
- Invoker: شیئی که از command میخواهد درخواست را اجرا کند. این شیء یک شیء Command را نگه میدارد و متد execute آن را برای شروع عملیات فراخوانی میکند.
- Client: اشیاء ConcreteCommand را ایجاد کرده و receiver آنها را تنظیم میکند.
پیادهسازی در پایتون
در اینجا یک مثال پایتون برای نمایش الگوی Command آورده شده است:
class Command:
def execute(self):
raise NotImplementedError
class ConcreteCommand(Command):
def __init__(self, receiver, action):
self._receiver = receiver
self._action = action
def execute(self):
self._receiver.action(self._action)
class Receiver:
def action(self, action):
print(f"Receiver: Performing action '{action}'")
class Invoker:
def __init__(self):
self._commands = []
def add_command(self, command):
self._commands.append(command)
def execute_commands(self):
for command in self._commands:
command.execute()
# Example Usage
receiver = Receiver()
command1 = ConcreteCommand(receiver, "Operation 1")
command2 = ConcreteCommand(receiver, "Operation 2")
invoker = Invoker()
invoker.add_command(command1)
invoker.add_command(command2)
invoker.execute_commands()
در این مثال، اینترفیس `Command` متد `execute()` را تعریف میکند. `ConcreteCommand` یک شیء `Receiver` را به یک عمل خاص متصل میکند. کلاس `Invoker` لیستی از اشیاء `Command` را نگهداری کرده و آنها را به ترتیب اجرا میکند. کلاینت اشیاء `ConcreteCommand` را ایجاد کرده و آنها را به `Invoker` اضافه میکند.
کاربردهای دنیای واقعی
- نوارهای ابزار و منوهای GUI: هر دکمه یا آیتم منو میتواند به عنوان یک command نمایش داده شود. وقتی کاربر روی یک دکمه کلیک میکند، command مربوطه اجرا میشود.
- پردازش تراکنش: در سیستمهای پایگاه داده، هر تراکنش میتواند به عنوان یک command نمایش داده شود. این امر امکان عملکرد undo/redo و لاگبرداری تراکنشها را فراهم میکند.
- ضبط ماکرو (Macro Recording): ویژگیهای ضبط ماکرو در برنامههای نرمافزاری از الگوی Command برای ضبط و پخش مجدد اقدامات کاربر استفاده میکنند.
- صفهای کار (Job Queues): سیستمهایی که وظایف را به صورت غیرهمزمان پردازش میکنند، اغلب از صفهای کار استفاده میکنند که در آن هر کار به عنوان یک command نمایش داده میشود.
- فراخوانی رویههای از راه دور (RPC): مکانیزمهای RPC از الگوی Command برای کپسولهسازی فراخوانی متدهای از راه دور استفاده میکنند.
مزایای الگوی Command
- جداسازی (Decoupling): invoker و receiver از هم جدا هستند که امکان انعطافپذیری و قابلیت استفاده مجدد بیشتر را فراهم میکند.
- صفبندی و لاگبرداری: commandها میتوانند در صف قرار گرفته و لاگ شوند، که امکاناتی مانند undo/redo و ردپای حسابرسی (audit trails) را فراهم میکند.
- پارامتربندی: commandها را میتوان با درخواستهای مختلف پارامتربندی کرد که آنها را چندمنظورهتر میکند.
- پشتیبانی از Undo/Redo: الگوی Command پیادهسازی عملکرد undo/redo را آسانتر میکند.
معایب الگوی Command
- افزایش پیچیدگی: تعداد کلاسها ممکن است افزایش یابد و سیستم را پیچیدهتر کند.
- سربار (Overhead): ایجاد و اجرای اشیاء command ممکن است مقداری سربار ایجاد کند.
نتیجهگیری
الگوهای Observer، Strategy و Command ابزارهای قدرتمندی برای ساخت سیستمهای نرمافزاری انعطافپذیر، قابل نگهداری و مقیاسپذیر در پایتون هستند. با درک هدف، پیادهسازی و کاربردهای دنیای واقعی آنها، میتوانید از این الگوها برای حل مشکلات رایج طراحی و ایجاد برنامههای قویتر و سازگارتر استفاده کنید. به یاد داشته باشید که معاوضههای مرتبط با هر الگو را در نظر بگیرید و الگویی را انتخاب کنید که به بهترین وجه با نیازهای خاص شما مطابقت دارد. تسلط بر این الگوهای رفتاری، تواناییهای شما را به عنوان یک مهندس نرمافزار به طور قابل توجهی افزایش خواهد داد.