wings
wings
خواندن ۱۳ دقیقه·۵ سال پیش

الگوی طراحی Strategy

الگوی Strategy یک الگوی Behavioral هست که به ما این اجازه رو میده که خانواده‌ای از الگوریتم‌های مختلف رو به عنوان کلاس‌های مجزا تعریف کنیم و اشیا آن‌ها را قابل تعویض کنیم(یعنی بتونن با اشیا دیگری از همون کلاس‌ها جایگزین بشن).


مدتی هست که تصمیم گرفتم چیزهای جدیدی که یاد میگیرم رو به اشتراک بزارم و امروز یه مقاله در مورد این الگو خوندم و قصد داشتم چیزی که خودم یادگرفتم رو بنویسم ولی دیدم این مقاله ساختاربهتری داره و تصمیم گرفتم ترجمش کنم که بقیه هم بتونن ازش استفاده کنن . سعی کردم چیزی رو که درک کردم رو ترجمه کنم پس اگر اشتباهی دیدید معذرت میخوام.

این مقاله یکم پیشرفته هست و اگر به تازگی شروع به برنامه‌نویسی کردید ممکن هست که درکش نکنید.

مسئله

فرض کنید قراره یه اپلکیشن مسیریابی برای توریست‌ها طراحی کنید که قابلیت مسیریابی هوشمند رو داشته باشه و سریع‌‎ترین مسیر رو به کاربر پیشنهاد بده.

برای اولین نسخه اپلکیشن فقط مسیرهای ماشین رو پیشنهاد داده میشه ولی بعد از مدتی کاربران درخواست مسیرهایی برای عابرین پیاده میکنن و یا عده‌ای دیگر درخواست مسیرهایی رو داشته باشن که از طریق وسایل حمل و نقل عمومی به مقصد برسن. خیلی زود نیاز به مسیر برای دوچرخه سوارها هم به وجود میاد و بعضی از کاربران درخواست مسیرهایی رو میکنن که از مکان‌های توریستی هم میگذرن.

از لحاظ تجاری همه چیز خوب بوده و ما موفق عمل کردیم, ولی از لحاظ فنی مشکلات زیادی ایجاد شده و با اضافه شدن هر ویژگی جدید اندازه کلاس اصلی بزرگتر و بزرگتر میشه.

هر تغییر کوچکی روی الگوریتم‌ها باعث میشه با چندین خطای دیگه روبه‌رو بشیم. از طرف دیگه کار تیمی هم با مشکل روبه‌رو شده و توسعه دهنده‌ها زمان زیادی رو صرف merge conflict ها میکنن و هر تغییر کوچیکی زمان زیادی از اون‌ها میگیره.

راه حل

الگوی Strategy راهکاری رو ارائه میده که یک کلاس یک کار مشخص رو به چندین روش مختلف انجام میده و تمام اون روش‌ها رو از کلاس ها مجزا که اون‌ها را strategy مینامیم استخراج میکنه.

کلاس اصلی رو کلاس Context مینامیم و این کلاس باید یک رابطه با یکی از کلاس‌های Strategy داشته باشد و کلاس Context باید به جای انجام این عمل توسط خودش وظیفه را به آن کلاس Strategy محول کند.

کلاس Context وظیفه انتخاب الگوریتم مناسب را بر عهده ندارد. در عوض Client کلاس Strategy دلخواه را به Context میدهد. در حقیقت کلاس Context اطلاعات زیادی در مورد strategy ها ندارد. این کلاس از طریق یک رابط (interface) عمومی که فقط یک متد برای فراخوانی الگوریتم‌های استراتژی در اختیار قرار میدهد با Strategy ها کار میکند.
در این حالت کلاس Context به طور مستقل فعالیت میکند و با افزودن Strategy های جدید یا تغییر Strategy های قبلی تغییری در کلاس Context ایجاد نمیشود.

استراتژی‌های مسیریابی
استراتژی‌های مسیریابی


در برنامه مسیریابی ما، هر الگوریتم مسیریابی را می توان با فراخوانی متد buildRoute از کلاس خود استخراج کرد. این روش مبدا و مقصد را می پذیرد و مجموعه ای از مسیرها را برمی گرداند.

اگرچه با توجه به همان استدلال ها، هر کلاس مسیریابی می تواند مسیری متفاوتی را ایجاد کند، کلاس اصلی مسیریاب اهمیتی نمی دهد که چه الگوریتم انتخاب شده باشد زیرا کار اصلی آن ارائه مجموعه ای از مسیرها در نقشه است. کلاس دارای روشی برای تغییر استراتژی مسیریابی فعال است، بنابراین Client های آن می توانند به راحتی رفتار مسیریاب انتخاب شده در حال حاضر را با دیگری جایگزین کنند.

مثال در دنیای واقعی

استراتژی های مختلفی برای رسیدن به فرودگاه.
استراتژی های مختلفی برای رسیدن به فرودگاه.

تصور کنید که باید به فرودگاه بروید. می توانید اتوبوس بگیرید، یک تاکسی سفارش دهید یا دوچرخه خود را سوار شوید. این راهکارهای حمل و نقل شماست. بسته به عواملی مانند محدودیت بودجه یا زمان، می توانید یکی از این استراتژی ها را انتخاب کنید.

ساختار


سودوکد Pseudocode

در این مثال، کلاس Context از چندین استراتژی برای اجرای عملیات‌های متنوع حسابی استفاده می کند.


// The strategy interface declares operations common to all // supported versions of some algorithm. The context uses this // interface to call the algorithm defined by the concrete // strategies. interface Strategy is method execute(a, b) // Concrete strategies implement the algorithm while following // the base strategy interface. The interface makes them // interchangeable in the context. class ConcreteStrategyAdd implements Strategy is method execute(a, b) is return a + b class ConcreteStrategySubtract implements Strategy is method execute(a, b) is return a - b class ConcreteStrategyMultiply implements Strategy is method execute(a, b) is return a * b // The context defines the interface of interest to clients. class Context is // The context maintains a reference to one of the strategy // objects. The context doesn't know the concrete class of a // strategy. It should work with all strategies via the // strategy interface. private strategy: Strategy // Usually the context accepts a strategy through the // constructor, and also provides a setter so that the // strategy can be switched at runtime. method setStrategy(Strategy strategy) is this.strategy = strategy // The context delegates some work to the strategy object // instead of implementing multiple versions of the // algorithm on its own. method executeStrategy(int a, int b) is return strategy.execute(a, b) // The client code picks a concrete strategy and passes it to // the context. The client should be aware of the differences // between strategies in order to make the right choice. class ExampleApplication is method main() is Create context object. Read first number. Read last number. Read the desired action from user input. if (action == addition) then context.setStrategy(new ConcreteStrategyAdd()) if (action == subtraction) then context.setStrategy(new ConcreteStrategySubtract()) if (action == multiplication) then context.setStrategy(new ConcreteStrategyMultiply()) result = context.executeStrategy(First number, Second number) Print result


چه زمانی از الگوی Strategy استفاده کنیم

وقتی می خواهید از انواع مختلفی از الگوریتم درون یک شی استفاده کنید و قابلیت تغییر از یک الگوریتم به الگوریتم دیگری در زمان اجرا را داشته باشید، از الگوی استراتژی استفاده کنید.

الگوی strategy به شما امکان میدهد تا به صورت غیر مستقیم رفتار شی، در زمان اجرا را، از طریق مرتبط کردن آن با اشیايی از زیرکلاس‌های متفاوت که هرکدام وظایف مشخصی را به صورت های متفاوت انجام میدهند، تغییر دهید.

زمانی که تعداد زیادی کلاس مشابه دارید که تفاوت آنها فقط در اجرای بعضی از رفتارهاست، از الگوی strategy استفاده کنید.

الگوی استراتزی به شما این امکان را میدهد که رفتارهای متفاوت را در کلاس های متفاوت قرار دهید و کلاس‌های اصلی را در یک کلاس ترکیب کنید و از این طریق کدهای تکراری را کاهش دهید.

با استفاده از الگوی strategy منطق تجاری (business logic)، از جزییات پیاده‌سازی الگوریتم‌ها، که ممکن است به اندازه محتوای آن logic مهم نباشند، از هم جدا میشوند.

الگوی strategy به شما امکان می دهد تا کد، داده های داخلی و وابستگی های الگوریتم های مختلف را از بقیه کد جدا کنید. client های مختلف برای اجرای الگوریتم ها و تعویض آنها در زمان اجرا، یک رابط (interface) ساده دریافت می کنند.

وقتی که کلاس شما یک اپراتور شرطی بزرگ دارد(زمانی که حالت های مختلفی برای اجرا بررسی میشوند) و بر اساس آن بین پیاده سازی‌های متفاوت از یک الگوریتم(مثلا پیاده سازی های متفاوت الگوریتم مرتب سازی یا پیاده سازی های متفاوت الگوریتم جستجو) یکسان جابجا میشود از الگوی strategy استفاده کنید.

الگوی strategy به شما امکان می دهد با استخراج کلیه الگوریتم ها در کلاس های جداگانه، که همه آنها یک رابط (interface) یکسان را پیاده‌سازی میکنند، از شر چنین شرطی خلاص شوید. شی اصلی به جای پیاده سازی تمام انواع الگوریتم، اجرای را به یکی از این اشیاء واگذار می کند.

روش پیاده‌سازی

  • در کلاس اصلی(context)، الگوریتمی را که مستعد تغییرات مکرر است، شناسایی کنید. همچنین ممکن است یک عبارت شرطی بزرگ باشد که در زمان اجرا نوع دیگری از همان الگوریتم را انتخاب و اجرا کند.
  • یک رابط (interface) مشترک برای تمامی انواع الگوریتم‌ها اعلان کنید.
  • همه الگوریتم ها را یک به یک در کلاس های خود قرار دهید. همه آنها باید رابط (interface) استراتژی را پیاده سازی کنند.
  • در کلاس اصلی یک فیلد برای دسترسی به شی‌ای از نوع strategy قرار دهید. یک متد برای مقدار دهی و تغییر آن فیلد فراهم کنید. کلاس اصلی فقط باید از طریق رابط strategy با شی strategy (همان فیلد نوع strategy) کار کند. کلاس اصلی ممکن است نیاز به پیاده‌سازی یک رابط (interface) برای دادن امکان دسترسی به داده های خود، به شی strategy باشد.
  • سرویس‌گیرندگان (clients) کلاس بایدبا انتخاب یک استراتژی مناسب، که مطابق با نیاز آن‌ها است، از آن استفاده ببرند.



مزایا

  • می توانید الگوریتم های مورد استفاده در داخل یک شی را در زمان اجرا تعویض کنید.
  • می توانید جزئیات پیاده‌سازی یک الگوریتم را از کدی که از آن استفاده می کند، جدا کنید.
  • می توانید ارث‌بری(inheritance) را با ترکیب(composition) جایگزین کنید.
  • اصل Open closed،شما می توانید استراتژی های جدیدی را بدون نیاز به تغییر کلاس اصلی تعریف کنید.

معایب

  • اگر فقط دو الگوریتم دارید و آنها به ندرت تغییر می کنند، هیچ دلیلی وجود ندارد که برنامه را با کلاس ها و رابط های جدیدی که با پیاده سازی الگو لازم میشوند، بیش از حد پیچیده کنید.
  • سرویس‌گیرندگان(clients) باید از تفاوت های بین استراتژی ها آگاه باشند تا بتوانند مورد مناسب را انتخاب کنند.
  • بسیاری از زبانهای برنامه نویسی مدرن دارای پشتیبانی از نوع عملکردی هستند که به شما امکان می دهد نسخه های مختلفی از یک الگوریتم را در داخل مجموعه ای از توابع ناشناس(anonymous) پیاده سازی کنید. سپس می توانید دقیقاً همانگونه که قبلاً از اشیاء استراتژی استفاده کرده اید، از این توابع استفاده کنید ، اما بدون اینکه کد خود را با کلاس و رابط های اضافی، بزرگ کنید.


ارتباط با سایر الگوها

  • الگوهای State، Strategy(و تا حدی adapter) ساختارهای بسیار مشابهی دارند. در واقع، همه این الگوهای مبتنی بر ترکیب(composition) هستند، که کار را به اشیاء دیگر واگذار می کند. با این حال، همه آنها مشکلات متفاوتی را حل می کنند. یک الگو فقط یک دستورالعمل برای ساختن کد شما به یک روش خاص نیست. الگو همچنین میتواند از طریق مشکلی که حل میکند با توسعه دهندگان دیگر ارتباط برقرار کند.
  • الگوهای Command و Strategy ممکن است شبیه به نظر برسند زیرا می توانید از هر دو برای parameterize کردن یک شی با بعضی از عملکردها استفاده کنید. با این حال، آنها اهداف بسیار متفاوتی دارند:
  • برای تبدیل هر عملیاتی به یک شی می توانید از Command استفاده کنید. پارامترهای عملیات به فیلدهای آن شی تبدیل می شوند. تبدیل به شما امکان می دهد اجرای عملیات را به تعویق بیندازید، آن در صف قرار دهید، تاریخچه دستورات را ذخیره کنید، دستورات را به سرویس های راه دور و غیره ارسال کنید.
  • از طرف دیگر، Strategy معمولاً روشهای مختلفی برای انجام همان کار را توصیف می کند، به شما امکان می دهد این الگوریتم ها را در یک کلاس واحد مبادله کنید.
  • الگوی Decorator به شما امکان می دهد پوست یک شی(ظاهر) را تغییر دهید، در حالی که استراتژی به شما امکان می دهد روده ها(محتویات) را تغییر دهید.
  • الگوی Template Method مبتنی بر وراثت(Inheritance) است: به شما امکان می دهد قسمت‌های یک الگوریتم را با گسترش آن قسمت‌ها در زیر کلاس ها تغییر دهید. Strategy مبتنی بر ترکیب(Composition) است: شما می توانید با تهیه Strategy های مختلف مطابق با آن رفتار، بخش هایی از رفتار شیء را تغییر دهید. Template Method در سطح کلاس کار می کند، بنابراین ایستا است. Strategy در سطح شی کار می کند، به شما امکان می دهد رفتارها را در زمان اجرا تغییر دهید.
  • الگوی State را می توان به عنوان یک Strategy در نظر گرفت. هر دو الگوی مبتنی بر ترکیب(Composition) هستند: آنها با واگذاری برخی کارها به اشیاء کمکی (helper objects)، رفتار را تغییر می دهند. Strategy باعث می شود این اشیاء کاملاً مستقل و از یکدیگر بی اطلاع باشند. با این وجود، State وابستگی را محدود نمی کند و به آنها اجازه می دهد وضعیت را متناسب با خواست خود تغییر دهند.

منبع

design patternclean codeprogrammingstrategy pattern
مصطفی مرادی هستم برنامه نویس وب
شاید از این پست‌ها خوشتان بیاید