استفاده از الگوهای طراحی اثبات شده می تواند به طور قابل توجهی کارایی و قابلیت نگهداری کد ما را افزایش دهد. یکی از این الگوها زنجیره مسئولیت(Chain of Responsibility) است که راه حلی زیبا برای مدیریت سناریوهای گردش کار پیچیده ارائه می دهد. در این مقاله، پیچیدگیهای الگوی طراحی زنجیره مسئولیت را بررسی میکنیم و مفاهیم اصلی، مزایا و کاربردهای عملی آن را با ذکر یک مثال مورد بحث قرار میدهیم.
الگوی طراحی زنجیره مسئولیت یا Chain of Responsibility Design Pattern چیست؟
الگوی زنجیره مسئولیت یک الگوی طراحی رفتاری(behavioral design pattern) است که اجازه میدهد یک درخواست در امتداد زنجیرهای از کنترلکنندههای بالقوه ارسال شود تا زمانی که درخواست مناسب برای پردازش آن را پیدا کند. هر کنترل کننده در زنجیره دارای استقلال برای رسیدگی به درخواست یا delegate آن به کنترل کننده بعدی است. یعنی پس از دریافت درخواست، هر کنترل کننده تصمیم می گیرد یا درخواست را پردازش کند یا آن را به کنترل کننده بعدی در زنجیره ارسال کند.
این رویکرد انعطاف پذیری، مقیاس پذیری و قابلیت نگهداری را در سناریوهای مدیریت گردش کار(workFlow) ارتقا می دهد.
مانند بسیاری دیگر از الگوهای طراحی رفتاری، زنجیره مسئولیت متکی بر تبدیل رفتارهای خاص به اشیاء مستقل به نام کنترل کننده(handler) است.
این الگو زمانی توصیه میشود که چندین شی میتوانند یک درخواست را مدیریت کنند و نیازی نیست که کنترلکننده یک شی خاص باشد. همچنین، کنترل کننده در زمان اجرا تعیین می شود.
مثال استفاده از Chain of Responsibility Design Pattern در سی شارپ:
بیایید سناریویی را در نظر بگیریم که در آن یک سری قوانین تخفیف برای یک برنامه تجارت الکترونیکی داریم. بسته به مشخصات مشتری، می خواهیم درصدهای تخفیف متفاوتی را برای سفارشات آنها اعمال کنیم.
قوانین تخفیف به شرح زیر است:
- اگر مشتری VIP است، 20 درصد تخفیف اعمال کنید.
- اگر مشتری مشتری دائمی است، 10 درصد تخفیف اعمال کنید.
- اگر مشتری مشتری جدید است، 5 درصد تخفیف اعمال کنید.
- اگر هیچ یک از قوانین بالا مطابقت نداشت، از تخفیف استفاده نکنید.
در ابتدا، ما می توانیم این منطق را با استفاده از یک سری دستورات If مدیریت کنیم:
public class Customer
{
public bool IsVIP { get; set; }
public bool IsRegular { get; set; }
public bool IsNew { get; set; }
}
Public decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
return customer switch
{
{ IsVIP: true } => orderTotal * 0.8m,
{ IsRegular: true } => orderTotal * 0.9m,
{ IsNew: true } => orderTotal * 0.95m,
_ => 0
};
}
var customer=new Customer { IsNew = true };
Console.WriteLine(CalculateDiscount(customer, 10_000));
در حالی که رویکرد دستورات If کار می کند، زمانی که تعداد قوانین افزایش می یابد، می تواند دشوار شود.
الگوی زنجیره مسئولیت راه حلی انعطاف پذیرتر و قابل نگهداری را ارائه می دهد.
بیایید برای استفاده از این الگو، کد را refactor کنیم.
مرحله 1: یک کلاس کنترل کننده انتزاعی(abstract) به نام DiscountHandler ایجاد کنید که یک رابط مشترک برای همه کنترل کننده های تخفیف تعریف می کند:
public abstract class DiscountHandler
{
protected DiscountHandler _nextHandler;
public DiscountHandler SetNextHandler(DiscountHandler nextHandler)
{
_nextHandler = nextHandler;
return _nextHandler;
}
public abstract decimal CalculateDiscount(Customer customer, decimal orderTotal);
}
مرحله شماره 2: کنترل کننده یا handler های تخفیف را با استفاده از DiscountHandler پیاده سازی کنید. هر کنترل کننده قانون خاصی را مدیریت می کند و تصمیم می گیرد که آیا تخفیف اعمال کند یا درخواست را به کنترل کننده بعدی ارسال کند.
//VIPDiscountHandler
public class VIPDiscountHandler : DiscountHandler
{
public override decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
if (customer.IsVIP)
{
return orderTotal * 0.8m;
}
return _nextHandler.CalculateDiscount(customer, orderTotal);
}
}
//RegularDiscountHandler
public class RegularDiscountHandler : DiscountHandler
{
public override decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
if (customer.IsRegular)
{
return orderTotal * 0.9m;
}
return _nextHandler.CalculateDiscount(customer, orderTotal);
}
}
//NewCustomerDiscountHandler
public class NewDiscountHandler : DiscountHandler
{
public override decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
if (customer.IsNew)
{
return orderTotal * 0.95m;
}
return _nextHandler.CalculateDiscount(customer, orderTotal);
}
}
//NoDiscountHandler
public class NoDiscountHandler : DiscountHandler
{
public override decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
return 0; //no discount
}
}
مرحله 3: با استفاده از این هندلرها، می توانیم زنجیره مسئولیت را با اتصال آن ها به یکدیگر ایجاد کنیم:
var vipHandler = new VIPDiscountHandler();
vipHandler.SetNextHandler(new RegularDiscountHandler())
.SetNextHandler(new NewDiscountHandler())
.SetNextHandler(new NoDiscountHandler());
در نهایت، میتوانیم زنجیره را با فراخوانی متد CalculateDiscount در اولین کنترلر در زنجیره فراخوانی کنیم:
decimal discountAmount = vipHandler.CalculateDiscount(customer, 10_000);
Console.WriteLine(discountAmount);
برای مشاهده این ویدیو در یوتیوب اینجا کلیک کنید.
برای دریافت سورس پروژه اینجا کلیک کنید.
مزایای استفاده از الگوی زنجیره مسئولیت
الگوی زنجیره مسئولیت چندین مزیت را هنگام رسیدگی به گردشهای کاری پیچیده ارائه میکند:
- انعطافپذیری و مقیاسپذیری: این الگو امکان پیکربندی مجدد زنجیره گردش کار را به صورت دینامیک فراهم میآورد و امکان افزودن، حذف یا اصلاح آسان کنترلکنندهها را بدون تأثیر بر منطق اصلی فراهم میکند. این انعطافپذیری، مدیریت یکپارچه گردش کار و گسترش آن در آینده را تسهیل میکند.
- کد قابل نگهداری و ماژولار: هر کنترل کننده بر روی یک کار خاص تمرکز می کند و تضمین می کند که کد مختصر، ماژولار و دارای نگهداری آسان است. تفکیک مسئولیت ها اشکال زدایی، تست و بروز رسانی را ساده می کند.
- توسعه پذیری پیشرفته: الگوی زنجیره مسئولیت استفاده مجدد از کد را ترویج می کند، زیرا کنترل کننده ها را می توان به راحتی تغییر داد یا جایگزین کرد تا نیازهای در حال تحول را برآورده کنند. کنترلکنندههای جدیدی را میتوان برای رسیدگی به مراحل یا الزامات اضافی بدون تغییر کد موجود اضافه کرد.
موارد استفاده از الگوی زنجیره مسئولیت
الگوی زنجیره مسئولیت در حوزه های مختلف کاربردهای عملی پیدا می کند.
- گردش کار و فرآیندهای تأیید: هر سناریویی که شامل گردش کار چند مرحله ای یا فرآیندهای تأیید باشد می تواند از الگوی زنجیره مسئولیت بهره مند شود. این الگو انتقال یکپارچه بین مراحل را امکان پذیر می کند و مدیریت استثناها(exception) یا ردها(rejection) را در هر مرحله تسهیل می کند.
- مدیریت رویداد: سیستمهایی که رویدادها(event) را پردازش میکنند، مانند فریمورک های UI یا معماریهای رویداد محور(event-driven architecture)، میتوانند از الگوی Chain of Responsibility برای انتشار رویدادها از طریق یک سری از کنترلکنندهها استفاده کنند که هر کدام مسئول نوع یا جنبه خاصی از رویداد هستند.
- مدیریت و ثبت خطا: این الگو را میتوان برای مدیریت خطاها(error handling) یا سناریوهای گزارشگیری(logging) که در آن کنترلکنندههای مختلف خطاها یا لاگ ها را بر اساس شدت(severity) یا نوع آنها پردازش میکنند، استفاده کرد.
نتیجهگیری
الگوی طراحی زنجیره مسئولیت راهحلی زیبا و کارآمد برای مدیریت گردشهای کاری پیچیده در سیستمهای نرمافزاری ارائه میدهد. با ارسال درخواستها از طریق زنجیرهای از کنترلکنندهها، به انعطافپذیری، قابلیت نگهداری و توسعهپذیری دست مییابیم.
برای ثبت نظر باید در سایت ثبت نام یا ورود نمایید