ویرگول
ورودثبت نام
mohadese.entezari
mohadese.entezari
mohadese.entezari
mohadese.entezari
خواندن ۲ دقیقه·۲ ماه پیش

Strategy Pattern

چرا گاهی باید رفتارهای برنامه را قابل تعویض کنیم؟

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

اگر این رفتارها را داخل یک متد با if/else کنترل کنیم،
کدمان کم‌کم تبدیل می‌شود به یک بخش شلوغ، غیرقابل‌تست و سخت‌توسعه.

اگر از Strategy Pattern استفاده نکنیم چه می‌شود؟

فرض کنید کد ما این شکلی باشد:

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 حل می‌کند.

Strategy Pattern دقیقاً چه کاری انجام می‌دهد؟

  • هر الگوریتم را در یک کلاس مستقل قرار می‌دهد

  • همه رفتارها یک اینترفیس مشترک دارند

  • Context فقط با Interface کار می‌کند، نه با جزئیات

  • رفتار برنامه را می‌توان در زمان اجرا تغییر داد

نتیجه؟
کدی ساده‌تر، قابل تست‌تر، قابل توسعه‌تر و مطابق اصول SOLID

مثال کاربردی: سیستم محاسبه هزینه ارسال

فرض کنید در یک فروشگاه سه روش ارسال داریم:

  • Courier : هزینه براساس مسافت

  • Post : هزینه ثابت

  • Express Delivery : ترکیب ثابت + متغیر

به جای اینکه چند شرط بنویسیم،
برای هر روش یک strategy مستقل می‌سازیم

1. تعریف Interface مشترک

public interface ICalculateTransportingCostStrategy { decimal Calculate(decimal distanceInKm); }

2. پیاده‌سازی استراتژی‌ها

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; } }

3. کلاس Context

public class TransportCostCalculator { private readonly ICalculateTransportingCostStrategy _strategy; public TransportCostCalculator(ICalculateTransportingCostStrategy strategy) { _strategy = strategy; } public decimal Calculate(decimal distanceInKm) { return _strategy.Calculate(distanceInKm); } }

4. نحوه استفاده از Strategy

var calculator = new TransportCostCalculator(new ExpressStrategy()); var cost = calculator.Calculate(12); Console.WriteLine(cost);

و برای تغییر رفتار:

calculator = new TransportCostCalculator(new PostStrategy());

مزایای Strategy

  • تعویض رفتار برنامه در runtime

  • پیروی از Open/Closed Principle

  • حذف شرط‌های طولانی if/switch

  • ساده‌تر شدن تست واحد

  • جدا کردن رفتار از Context (ترکیب به‌جای وراثت)

  • افزایش خوانایی و انعطاف‌پذیری

معایب Strategy

  • اگر الگوریتم‌ها کم باشند ⇒ کد اضافی و پیچیدگی بی‌دلیل

  • Client باید بداند کدام Strategy مناسب است

  • گاهی می‌توان به‌جای Strategy از lambda / delegate استفاده کرد (در زبان‌های مدرن)

  • کلاس‌ها و فایل‌های بیشتری ایجاد می‌شود

جمع‌بندی

Strategy زمانی مناسب است که:

  • یک کار چند روش مختلف داشته باشد

  • رفتار برنامه باید قابل تعویض باشد

  • بخواهیم از if/else‌های زیاد خلاص شویم

  • بخواهیم برنامه مطابق SOLID و قابل توسعه باشد

design patternsolidobject oriented
۱
۰
mohadese.entezari
mohadese.entezari
شاید از این پست‌ها خوشتان بیاید