در بسیاری از پروژهها یک کار را میتوان به چند روش انجام داد:
محاسبه قیمت، ارسال نوتیفیکیشن، اعتبارسنجی، یا تعیین روش حملونقل.
اگر این رفتارها را داخل یک متد با if/else کنترل کنیم،
کدمان کمکم تبدیل میشود به یک بخش شلوغ، غیرقابلتست و سختتوسعه.
فرض کنید کد ما این شکلی باشد:
public decimal CalculateCost(string type, decimal distance) { if (type == "courier") return 10000 + distance * 2000; if (type == "post") return 40000; if (type == "express") return 20000 + distance * 3500; throw new Exception("invalid type"); }
مشکلاتش:
اضافه کردن هر روش جدید : باید همین متد را تغییر دهیم
نقض اصل Open/Closed
بالا رفتن احتمال بروز باگ
تستپذیری پایین (تست یک رفتار، کل متد را درگیر میکند)
شرطهای تودرتو و پیچیدگی زیاد با بزرگ شدن کسبوکار
رفتار برنامه در runtime قابل تعویض نیست
این دقیقاً همان چیزی است که Strategy Pattern حل میکند.
هر الگوریتم را در یک کلاس مستقل قرار میدهد
همه رفتارها یک اینترفیس مشترک دارند
Context فقط با Interface کار میکند، نه با جزئیات
رفتار برنامه را میتوان در زمان اجرا تغییر داد
نتیجه؟
کدی سادهتر، قابل تستتر، قابل توسعهتر و مطابق اصول SOLID
فرض کنید در یک فروشگاه سه روش ارسال داریم:
Courier : هزینه براساس مسافت
Post : هزینه ثابت
Express Delivery : ترکیب ثابت + متغیر
به جای اینکه چند شرط بنویسیم،
برای هر روش یک strategy مستقل میسازیم
public interface ICalculateTransportingCostStrategy { decimal Calculate(decimal distanceInKm); }
public class CourierStrategy : ICalculateTransportingCostStrategy { public decimal Calculate(decimal distanceInKm) { return 10000 + distanceInKm * 2000; } } public class PostStrategy : ICalculateTransportingCostStrategy { public decimal Calculate(decimal distanceInKm) { return 40000; } } public class ExpressStrategy : ICalculateTransportingCostStrategy { public decimal Calculate(decimal distanceInKm) { return 20000 + distanceInKm * 3500; } }
public class TransportCostCalculator { private readonly ICalculateTransportingCostStrategy _strategy; public TransportCostCalculator(ICalculateTransportingCostStrategy strategy) { _strategy = strategy; } public decimal Calculate(decimal distanceInKm) { return _strategy.Calculate(distanceInKm); } }
var calculator = new TransportCostCalculator(new ExpressStrategy()); var cost = calculator.Calculate(12); Console.WriteLine(cost);
و برای تغییر رفتار:
calculator = new TransportCostCalculator(new PostStrategy());
تعویض رفتار برنامه در runtime
پیروی از Open/Closed Principle
حذف شرطهای طولانی if/switch
سادهتر شدن تست واحد
جدا کردن رفتار از Context (ترکیب بهجای وراثت)
افزایش خوانایی و انعطافپذیری
اگر الگوریتمها کم باشند ⇒ کد اضافی و پیچیدگی بیدلیل
Client باید بداند کدام Strategy مناسب است
گاهی میتوان بهجای Strategy از lambda / delegate استفاده کرد (در زبانهای مدرن)
کلاسها و فایلهای بیشتری ایجاد میشود
Strategy زمانی مناسب است که:
یک کار چند روش مختلف داشته باشد
رفتار برنامه باید قابل تعویض باشد
بخواهیم از if/elseهای زیاد خلاص شویم
بخواهیم برنامه مطابق SOLID و قابل توسعه باشد