الگوی Strategy

الگوی Strategy، خانواده ای از الگوریتم ها را تعریف می کند که هر کدام از آنها encapsulate شده اند و به صورت interchangeable استفاده می شوند. strategy کمک می کند که از الگوریتم به صورت مستقل از client استفاده کنیم. این امکان از طریق encapsulate کردن هر کدام از الگوریتم ها در داخل یک پیاده سازی و انتخاب آن ها بصورت runtime، ایجاد می شود.الگوی strategy در دسته الگوهای behavioral قرار می گیرد.الگوی Strategyدر strategy ما در مورد الگوریتم صحبت می کنیم. برای مثال الگوریتم مرتب سازی، تخفیف دادن و غیره که در مورد انجام یک کار، روش های مختلفی را ارائه می کنند. زمانی که روش های مختلفی برای انجام یک کار داشته باشیم، در زمان استفاده باید بسته به نوع مساله، آن ها را مقایسه کنیم و آگاهانه انتخاب کنیم. بنابراین موضوع trade-off مطرح می شود.به عبارتی، کلاس هایی داریم که الگوریتم های متفاوت یک کار را پیاده سازی می کنند و در مورد یک موضوع، رفتار شبیه به هم دارند. ما می خواهیم که یک انتزاع داشته باشیم و بتوانیم پیاده سازی آن را تغییر دهیم. برای مثال در مقایسه دو لیست، strategy ما دارای خاصیت case-sensitive باشد یا خیر. یا در مثال دیگر در مورد تخفیف می توانیم فکر کنیم که ممکن است الگوریتم های متفاوتی برای محاسبه تخفیف یک کالا داشته باشیم. تخفیف بر اساس نوع مشتری یا بر اساس یک مقدار مشخص می تواند پیاده سازی متفاوتی داشته باشد.در strategy، یک کلاس به عنوان context معرفی می شود که در داخل خودش یک abstraction از strategy دارد و آن strategy پیاده سازی های مختلفی دارد.برای مثال می توانیم متد Except در Linq را در نظر بگیریم که بر روی دو مجموعه انجام می شود.var list1 = new List<string>(); var list2 = new List<string>(); var result = list1.Except(list2);متد Except، یک پارامتر به عنوان IEqualityComparer دریافت می کند و زمانی که می خواهد مقدار list2 را از list1 کم کند، این امکان را به ما می دهد که نحوه مقایسه را در قالب یک interface به آن پاس دهیم.var list1 = new List<string>(); var list2 = new List<string>(); var result = list1.Except(list2,new MyCustomComparer()); public class MyCustomComparer : IEqualityComparer<string> { public bool Equals(string? x, string? y) { throw new NotImplementedException(); } public int GetHashCode([DisallowNull] string obj) { throw new NotImplementedException(); } }ما در نقش استفاده کننده از این api به عنوان client شناخته می شویم و Except به عنوان context در این مساله می باشد.در این الگو، client باید در مورد strategyهای مختلف آگاه باشد و بتواند یکی از آن ها را انتخاب کند. این موضوع می تواند در برخی مواقع بین client و پیاده سازی، وابستگی ایجاد کند. بنابراین الگوی strategy باید زمانی استفاده شود که رفتار برای client، موضوعیت داشته باشد. این موضوع می تواند حتی در قالب یک پنل در اختیار client قرار بگیرد.در ارتباط بین context و client، معمولا client یک concrete strategy را می سازد و به context پاس می دهد.در ارتباط بین strategy و context، یک context زمانی که می خواهد strategy را فراخوانی کند، داده های مربوط به الگوریتم را به strategy پاس می دهد. همچنین در روش دیگر، context می تواند خودش را به عنوان یک پارامتر پاس دهد و strategy تصمیم می گیرد که از چه داده ای از آن context استفاده کند.در یک مثال، یک کلاس Basket در نظر بگیرید که یک Total Price درون خود دارد و یک رفتار که می تواند روش های تخفیف مختلفی را اعمال کند. این روش ها می توانند بسته به لحظه ای که می خواهیم این کار را انجام دهیم متفاوت باشند. بنابراین یک abstract برای اعمال تخفیف داریم که وظیفه آن محاسبه تخفیف است. public class Basket { public long TotalPrice { get; set; } public void ApplyDiscount(IDiscountStrategy strategy) { TotalPrice -= strategy.Calculate(TotalPrice); } } public interface IDiscountStrategy { long Calculate(long totalPrice); }در بدنه ApplyDiscount نیز می توانیم تصمیم بگیریم که آیا تخفیف تعلق می گیرد یا خیر و یا هر عمل دیگری که مدنظر باشد.در اینجا، Basket به عنوان context در نظر گرفته می شود و DiscountStrategy هم strategy می باشد که می تواند تعدادی concrete strategy داشته باشد.public class VipCustomerOff : IDiscountStrategy { public long Calculate(long totalPrice) { return (long)(totalPrice * 0.3); } }در این مساله، context یک داده را به strategy پاس می دهد. با فرض اینکه، المان های دیگری مثل تاریخ ثبت مشتری هم در این context قرار بگیرد، باید بدونیم که آیا آن هم می تواند در تخفیف تاثیر داشته باشد یا خیر.بنابراین به عنوان یک trade-off باید در نظر بگیریم که آیا داده هایی که strategy نیاز دارد را پاس دهیم یا کل context را به عنوان یک پارامتر پاس دهیم.پاس دادن context به strategy، به این معناست که strategy، آن context را می شناسد. در این حالت context implement detail ممکن است expose شود. بنابراین دانش concrete stragegyها نسبت به context زیاد می شود. این می تواند یک وابستگی دو طرفه ایجاد کند و در تغییرات احتمالی ما را دچار مشکل کند. از طرفی flexibility را در concrete strategyها ایجاد می کند. در مثالی که در ابتدای مطلب از متد Except ملاحظه کردید، می تونیم پارامتر IEqualityComparer را به آن پاس ندهیم. بنابراین یک رفتار پیش فرض برای آن در نظر گرفته می شود. وجود مقادیر null، یک مشکل را وارد کدهای ما می کند. و آن مساله چک کردن مقدار است.روشی که در الگوی strategy می تواند استفاده شود، استفاده از الگوی null object است. در این الگو به دنبال پیاده سازی یک رفتار خنثی هستیم. برای مثال در محاسبه discount، رفتار بدون تخفیف را می تونیم برای null object در نظر بگیریم.public class NoDiscount : IDiscountStrategy { public long Calculate(long totalPrice) { return 0; } }از این طریق می تونیم پیچیدگی کار با objectهای null را کم کنیم. به طور خلاصه، در این الگو الگوریتم و نحوه انجام یک کار را به عنوان variability در نظر می گیریم و ماهیت خروجی کار یا outcome را به عنوان commonality در نظر می گیریم.