🔗 Chain of Responsibility
C# GEEKS(.NET)🎯Intent
الگوی Chain of Responsibility یک الگوی رفتاری (Behavioral Design Pattern) است که به شما اجازه میدهد درخواستها را در امتداد یک زنجیره از handlerها عبور دهید.
با دریافت یک request، هر handler تصمیم میگیرد که یا خودش درخواست را پردازش کند یا آن را به handler بعدی در زنجیره ارسال کند 🔄

❓Problem
فرض کنید در حال کار روی یک سیستم سفارشگیری آنلاین هستید 🛒
میخواهید دسترسی به سیستم را محدود کنید، بهطوریکه فقط کاربران احراز هویتشده بتوانند سفارش ایجاد کنند.
همچنین، کاربرانی که مجوزهای مدیریتی دارند باید دسترسی کامل به تمام سفارشها داشته باشند 🔐
بعد از کمی برنامهریزی، متوجه میشوید که این بررسیها باید بهصورت ترتیبی (sequentially) انجام شوند.
هر زمان که برنامه یک request شامل اطلاعات کاربری دریافت میکند، میتواند ابتدا تلاش کند کاربر را احراز هویت کند.
اما اگر اطلاعات نادرست باشند و احراز هویت شکست بخورد ❌، دیگر دلیلی برای ادامهی سایر بررسیها وجود ندارد.

در ماههای بعد، چندین بررسی ترتیبی دیگر نیز به سیستم اضافه کردید ⏳
یکی از همکارانتان پیشنهاد داد که ارسال دادهی خام مستقیماً به سیستم سفارشگیری امن نیست.
بنابراین، یک مرحلهی اعتبارسنجی اضافی برای sanitize کردن دادههای موجود در request اضافه کردید 🧹
مدتی بعد، کسی متوجه شد که سیستم در برابر brute force password cracking آسیبپذیر است.
برای مقابله با این مشکل، فوراً یک بررسی دیگر اضافه کردید که درخواستهای ناموفق تکراری از یک IP مشخص را فیلتر میکند 🚫🌐
سپس شخص دیگری پیشنهاد داد که میتوان با برگرداندن پاسخهای cache شده برای requestهای تکراری با دادهی یکسان، سرعت سیستم را افزایش داد ⚡️
در نتیجه، بررسی دیگری اضافه کردید که فقط در صورتی اجازه میدهد request به سیستم برسد که پاسخ cache مناسبی وجود نداشته باشد.

کد مربوط به این بررسیها که از قبل هم شلوغ به نظر میرسید 😵💫، با اضافه شدن هر قابلیت جدید بیش از پیش متورم و پیچیده شد.
تغییر در یک بررسی، گاهی روی بقیه هم تأثیر میگذاشت.
بدتر از همه این بود که وقتی تلاش کردید این بررسیها را برای محافظت از بخشهای دیگر سیستم reuse کنید، مجبور شدید بخشی از کد را دوبارهنویسی (duplicate) کنید؛
چون آن بخشها به بعضی از بررسیها نیاز داشتند، اما نه همهی آنها.
در نهایت، سیستم بهشدت سختفهم و پرهزینه برای نگهداری شد 💸
مدتی با این کدها دستوپنجه نرم کردید،
تا اینکه یک روز تصمیم گرفتید کل سیستم را refactor کنید 🔧✨
Solution
مانند بسیاری از الگوهای رفتاری دیگر، الگوی Chain of Responsibility بر پایهی تبدیل رفتارهای مشخص به آبجکتهای مستقل به نام handler بنا شده است 🔗
در سناریوی ما، هر بررسی (check) باید به کلاس جداگانهی خودش استخراج شود که تنها یک متد برای انجام آن بررسی دارد.
درخواست (request) بههمراه دادههایش، بهعنوان آرگومان به این متد ارسال میشود 📦
این الگو پیشنهاد میکند که این handlerها را بهصورت یک زنجیره (chain) به هم متصل کنید.
هر handler متصلشده، فیلدی برای نگهداری reference به handler بعدی در زنجیره دارد.
علاوه بر پردازش درخواست، handlerها درخواست را به جلو و در امتداد زنجیره ارسال میکنند ➡️
درخواست در طول زنجیره حرکت میکند تا زمانی که همهی handlerها فرصت پردازش آن را داشته باشند.
و این بهترین بخش ماجراست ✨
یک handler میتواند تصمیم بگیرد که درخواست را به handler بعدی ارسال نکند و عملاً کل پردازشهای بعدی را متوقف کند 🛑
در مثال سیستم سفارشگیری، هر handler پردازش خودش را انجام میدهد و سپس تصمیم میگیرد که آیا درخواست را به مرحلهی بعدی زنجیره منتقل کند یا نه.
در صورتی که request شامل دادههای صحیح باشد، تمام handlerها میتوانند رفتار اصلی خود را اجرا کنند؛ چه بررسی احراز هویت باشد، چه caching یا موارد دیگر ✅

با این حال، یک رویکرد کمی متفاوت وجود دارد (و از نظر تئوریک canonicalتر است) 🤓
در این رویکرد، handler هنگام دریافت request تصمیم میگیرد که آیا میتواند آن را پردازش کند یا نه.
اگر بتواند، دیگر request را به جلو منتقل نمیکند.
در نتیجه، یا فقط یک handler request را پردازش میکند، یا هیچکدام ❌
این رویکرد زمانی بسیار رایج است که با eventها در stack عناصر رابط کاربری گرافیکی (GUI) سروکار داریم.
برای مثال، وقتی کاربر روی یک دکمه کلیک میکند 🖱
این event در زنجیرهای از عناصر GUI منتشر میشود که از خود دکمه شروع شده، از containerهای آن (مثل form یا panel) عبور میکند و در نهایت به پنجرهی اصلی برنامه میرسد 🪟event توسط اولین عنصری در زنجیره که قادر به مدیریت آن است پردازش میشود.
این مثال همچنین قابلتوجه است، چون نشان میدهد که یک chain همیشه میتواند از یک درخت آبجکت (object tree) استخراج شود 🌳

نکتهی بسیار مهم این است که تمام کلاسهای handler باید یک interface مشترک را پیادهسازی کنند 📐
هر handler مشخص (concrete handler) فقط باید بداند که handler بعدی دارای متد execute است.
به این شکل، میتوانید زنجیرهها را در زمان اجرا (runtime) و با handlerهای مختلف بسازید، بدون اینکه کدتان به کلاسهای concrete وابسته شود 🔄
🌍 Real-World Analogy

تازه یک قطعه سختافزاری جدید خریده و روی کامپیوترتان نصب کردهاید 💻
از آنجایی که آدم فنیای هستید، روی سیستمتان چند سیستمعامل مختلف نصب است.
سعی میکنید همهی آنها را بوت کنید تا ببینید آیا سختافزار جدید پشتیبانی میشود یا نه.
ویندوز سختافزار را بهصورت خودکار تشخیص داده و فعال میکند ✅
اما لینوکس محبوبتان حاضر نیست با این سختافزار جدید کار کند 😞
با کورسویی از امید، تصمیم میگیرید با شمارهی پشتیبانی فنی که روی جعبه نوشته شده تماس بگیرید ☎️
اولین چیزی که میشنوید، صدای ربات پاسخگوی خودکار است 🤖
او ۹ راهحل رایج برای مشکلات مختلف پیشنهاد میدهد که هیچکدام به درد شما نمیخورند.
بعد از مدتی، ربات تماس را به یک اپراتور انسانی وصل میکند.
اما افسوس… اپراتور هم قادر نیست پیشنهاد خاصی بدهد 😑
او فقط بخشهایی طولانی از دفترچهی راهنما را تکرار میکند و حاضر نیست به توضیحات شما گوش دهد.
بعد از اینکه برای دهمین بار جملهی
«آیا امتحان کردهاید سیستم را خاموش و روشن کنید؟»
را میشنوید 🔁، درخواست میکنید که به یک مهندس واقعی وصل شوید.
در نهایت، اپراتور تماس شما را به یکی از مهندسان منتقل میکند 👨💻
کسی که احتمالاً ساعتها در اتاق سرور تاریکِ زیرزمین یک ساختمان اداری، منتظر صحبت با یک انسان واقعی بوده است 😄
مهندس به شما میگوید درایور مناسب سختافزار جدید را از کجا دانلود کنید و چطور آن را روی لینوکس نصب کنید.
بالاخره راهحل! 🎉
تماس را با خوشحالی تمام میکنید، در حالی که از شدت شادی در پوست خود نمیگنجید 😄
🧩 ساختار Structure
1_ ءHandler رابط (interface) مشترکی را اعلام میکند که بین تمام handlerهای concrete مشترک است.
این interface معمولاً فقط شامل یک متد برای رسیدگی به requestها است، اما گاهی ممکن است متد دیگری هم برای تنظیم handler بعدی در زنجیره داشته باشد 🔗
2_ ءBase Handler یک کلاس اختیاری است که میتوانید کدهای تکراری و مشترک بین تمام handlerها را در آن قرار دهید ♻️
معمولاً این کلاس شامل یک فیلد برای نگهداری reference به handler بعدی است.
ءClientها میتوانند با ارسال یک handler به constructor یا setter handler قبلی، زنجیره را بسازند.
این کلاس همچنین میتواند رفتار پیشفرض پردازش را پیادهسازی کند:
به این صورت که پس از بررسی وجود handler بعدی، اجرای درخواست را به آن منتقل کند ➡️
3_ ءConcrete Handlers شامل کد واقعی برای پردازش requestها هستند ⚙️
هر handler پس از دریافت request باید تصمیم بگیرد:
آیا خودش request را پردازش کند یا نه
و در صورت نیاز، آیا request را به handler بعدی در زنجیره ارسال کند یا خیر
ءHandlerها معمولاً self-contained و immutable هستند 🔒
و تمام دادههای موردنیاز خود را فقط یکبار و از طریق constructor دریافت میکنند.
ءClient میتواند زنجیرهها را:
فقط یکبار بسازد
یا بهصورت پویا (dynamic) و بسته به منطق برنامه ایجاد کند
توجه داشته باشید که یک request میتواند به هر handlerی در زنجیره ارسال شود؛
لزومی ندارد حتماً از اولین handler شروع شود 🎯
🧪 شبهکد (Pseudocode)
در این مثال، الگوی Chain of Responsibility مسئول نمایش اطلاعات راهنمای زمینهای (contextual help) برای عناصر فعال رابط کاربری گرافیکی (GUI) است 🖥

رابط کاربری برنامه معمولاً بهصورت یک درخت آبجکت (object tree) ساختاربندی میشود 🌳
برای مثال، کلاس Dialog که پنجرهی اصلی برنامه را رندر میکند، ریشهی این درخت است. Dialog شامل Panelها است، و این panelها ممکن است شامل panelهای دیگر یا عناصر سطح پایینتری مانند Button و TextField باشند.
یک کامپوننت ساده میتواند tooltipهای کوتاه و زمینهای نمایش دهد،
به شرطی که متن راهنما (help text) برای آن تعریف شده باشد ℹ️
اما کامپوننتهای پیچیدهتر، روشهای اختصاصی خودشان را برای نمایش راهنما دارند،
مثل:
نمایش بخشی از دفترچه راهنما 📘
یا باز کردن یک صفحه در مرورگر 🌐

وقتی کاربر نشانگر ماوس را روی یک عنصر قرار میدهد و کلید F1 را فشار میدهد ⌨️
برنامه کامپوننتی که زیر نشانگر قرار دارد را تشخیص میدهد
و یک request برای دریافت راهنما به آن ارسال میکند.
این request در طول تمام containerهای عنصر به سمت بالا حرکت میکند (bubble up)
تا زمانی که به عنصری برسد که قادر به نمایش اطلاعات راهنما باشد ✅
// The handler interface declares a method for executing a
// request.
interface ComponentWithContextualHelp is
method showHelp()
// The base class for simple components.
abstract class Component implements ComponentWithContextualHelp is
field tooltipText: string
// The component's container acts as the next link in the
// chain of handlers.
protected field container: Container
// The component shows a tooltip if there's help text
// assigned to it. Otherwise it forwards the call to the
// container, if it exists.
method showHelp() is
if (tooltipText != null)
// Show tooltip.
else
container.showHelp()
// Containers can contain both simple components and other
// containers as children. The chain relationships are
// established here. The class inherits showHelp behavior from
// its parent.
abstract class Container extends Component is
protected field children: array of Component
method add(child) is
children.add(child)
child.container = this
// Primitive components may be fine with default help
// implementation...
class Button extends Component is
// ...
// But complex components may override the default
// implementation. If the help text can't be provided in a new
// way, the component can always call the base implementation
// (see Component class).
class Panel extends Container is
field modalHelpText: string
method showHelp() is
if (modalHelpText != null)
// Show a modal window with the help text.
else
super.showHelp()
// ...same as above...
class Dialog extends Container is
field wikiPageURL: string
method showHelp() is
if (wikiPageURL != null)
// Open the wiki help page.
else
super.showHelp()
// Client code.
class Application is
// Every application configures the chain differently.
method createUI() is
dialog = new Dialog("Budget Reports")
dialog.wikiPageURL = "http://..."
panel = new Panel(0, 0, 400, 800)
panel.modalHelpText = "This panel does..."
ok = new Button(250, 760, 50, 20, "OK")
ok.tooltipText = "This is an OK button that..."
cancel = new Button(320, 760, 50, 20, "Cancel")
// ...
panel.add(ok)
panel.add(cancel)
dialog.add(panel)
// Imagine what happens here.
method onF1KeyPress() is
component = this.getComponentAtMouseCoords()
component.showHelp()
🎯 کاربردپذیری (Applicability)
از الگوی Chain of Responsibility استفاده کنید زمانی که انتظار میرود برنامهی شما انواع مختلفی از requestها را به روشهای متفاوت پردازش کند،
اما نوع دقیق requestها و ترتیب آنها از قبل مشخص نیست 🔍
این الگو به شما اجازه میدهد چندین handler را به یک زنجیره متصل کنید 🔗
و هنگام دریافت یک request، از هر handler «بپرسید» که آیا قادر به پردازش آن هست یا نه.
به این ترتیب، تمام handlerها فرصت پردازش request را خواهند داشت.
از این الگو استفاده کنید زمانی که اجرای چند handler به ترتیب مشخصی ضروری است 🧭
از آنجا که میتوانید handlerها را به هر ترتیبی در زنجیره متصل کنید،
تمام requestها دقیقاً مطابق برنامهریزی شما از زنجیره عبور خواهند کرد.
از الگوی CoR استفاده کنید وقتی که مجموعهی handlerها و ترتیب آنها قرار است در زمان اجرا (runtime) تغییر کند 🔄
اگر برای فیلد reference داخل کلاسهای handler setter تعریف کنید،
میتوانید handlerها را بهصورت پویا:
اضافه کنید
حذف کنید
یا ترتیب آنها را تغییر دهید
🛠 نحوهی پیادهسازی (How to Implement)
1_ ابتدا interface مربوط به handler را تعریف کنید و signature متدی که مسئول پردازش requestها است را مشخص نمایید ✍️
تصمیم بگیرید client چگونه دادههای request را به این متد منتقل کند.
انعطافپذیرترین روش این است که request را به یک object تبدیل کرده
و آن را بهعنوان آرگومان به متد پردازش ارسال کنید 📦
2_ برای حذف کدهای تکراری (boilerplate) در handlerهای concrete،
بهتر است یک کلاس پایهی abstract ایجاد کنید که از interface handler ارثبری میکند ♻️
این کلاس باید یک فیلد برای نگهداری reference به handler بعدی در زنجیره داشته باشد 🔗
بهتر است این کلاس را immutable طراحی کنید.
اما اگر قصد دارید زنجیرهها را در runtime تغییر دهید،
باید setterای برای تغییر مقدار این reference تعریف کنید.
همچنین میتوانید رفتار پیشفرض مناسبی برای متد پردازش پیادهسازی کنید:
به این صورت که request را به handler بعدی ارسال کند،
مگر اینکه handler دیگری باقی نمانده باشد ➡️
3_ ءhandlerهای concrete میتوانند با فراخوانی متد والد از این رفتار استفاده کنند.
سپس بهصورت مرحلهبهمرحله handlerهای concrete را ایجاد کنید
و متد پردازش آنها را پیادهسازی نمایید ⚙️
هر handler هنگام دریافت request باید دو تصمیم بگیرد:
آیا request را پردازش میکند یا نه؟
آیا request را به handler بعدی در زنجیره ارسال میکند یا نه؟
5_ ءClient میتواند:
خودش زنجیرهها را بسازد
یا زنجیرههای آماده را از آبجکتهای دیگر دریافت کند
در حالت دوم، باید کلاسهای factoryای برای ساخت زنجیرهها
بر اساس تنظیمات یا محیط اجرا پیادهسازی کنید 🏗
5_ ءClient میتواند هر handlerی در زنجیره را فعال کند، نه فقط اولین handler 🎯
request در طول زنجیره منتقل میشود تا:
یکی از handlerها از ارسال آن جلوگیری کند
یا request به انتهای زنجیره برسد
6_به دلیل ماهیت پویا (dynamic) زنجیره، Client باید برای سناریوهای زیر آماده باشد ⚠️:
زنجیره ممکن است فقط یک handler داشته باشد
برخی requestها ممکن است به انتهای زنجیره نرسند
برخی دیگر ممکن است بدون پردازش، به انتهای زنجیره برسند
✅ مزایا و ❌ معایب (Pros and Cons)
✅ مزایا
_ میتوانید ترتیب پردازش requestها را کنترل کنید 🧭
_ اصل Single Responsibility: کلاسهایی که عملیات را فراخوانی میکنند از کلاسهایی که عملیات را اجرا میکنند جدا میشوند
_ اصل Open/Closed: میتوانید handlerهای جدید اضافه کنید بدون اینکه کد client موجود را بشکنید
❌ معایب
_ ممکن است برخی requestها بدون پردازش باقی بمانند ⚠️
🔗 ارتباط با سایر الگوها (Relations with Other Patterns)
الگوهای Chain of Responsibility، Command، Mediator و Observer
روشهای مختلف اتصال فرستندهها و گیرندههای request را پوشش میدهند:
ءChain of Responsibility:
request را بهصورت ترتیبی در یک زنجیرهی پویا از گیرندههای بالقوه عبور میدهد تا یکی آن را پردازش کند
Command:
ارتباط یکطرفه بین فرستنده و گیرنده برقرار میکند
Mediator:
ارتباط مستقیم بین فرستنده و گیرنده را حذف میکند
و آنها را مجبور میکند از طریق یک mediator با هم ارتباط داشته باشند
Observer:
به گیرندهها اجازه میدهد بهصورت پویا در دریافت requestها subscribe یا unsubscribe شوند
الگوی Chain of Responsibility اغلب همراه با Composite استفاده میشود 🌳
در این حالت، وقتی یک کامپوننت برگ (leaf) requestی دریافت میکند،
ممکن است آن را از طریق زنجیرهای از کامپوننتهای والد
تا ریشهی درخت آبجکت عبور دهد.
Handlerها در Chain of Responsibility میتوانند بهصورت Command پیادهسازی شوند.
در این حالت، میتوانید عملیات مختلفی را روی یک context مشترک
(که همان request است) اجرا کنید 🔁
اما رویکرد دیگری هم وجود دارد که در آن خود request یک Command است.
در این حالت، میتوانید یک عملیات واحد را
در زنجیرهای از contextهای مختلف اجرا کنید.
الگوهای Chain of Responsibility و Decorator ساختار کلاسی بسیار مشابهی دارند 🧱
هر دو از composition بازگشتی برای عبور execution بین مجموعهای از آبجکتها استفاده میکنند.
اما تفاوتهای مهمی بین آنها وجود دارد:
handlerهای CoR میتوانند عملیات کاملاً مستقل از هم اجرا کنند
آنها میتوانند در هر نقطه، عبور request را متوقف کنند ✋
در مقابل، Decoratorها رفتار آبجکت را گسترش میدهند
بدون اینکه ناسازگار با interface پایه شوند.
Decoratorها اجازه ندارند جریان request را قطع کنند
Code Examples
🔗 Chain of Responsibility در C#
الگوی Chain of Responsibility یک الگوی طراحی رفتاری (Behavioral Design Pattern) است که اجازه میدهد یک request در امتداد زنجیرهای از handlerهای بالقوه عبور داده شود تا زمانی که یکی از آنها request را پردازش کند.
این الگو به چندین object اجازه میدهد که یک request را مدیریت کنند، بدون اینکه کلاس ارسالکننده (sender) به کلاسهای concrete گیرندهها وابسته باشد 🔄
زنجیره میتواند بهصورت پویا (dynamic) در زمان اجرا (runtime) و با هر handlerی که از یک interface استاندارد پیروی کند، ساخته شود.
🧩 مثالهای کاربردی (Usage Example)
الگوی Chain of Responsibility در C# بسیار رایج است.
بیشتر زمانی کاربرد دارد که کد شما با زنجیرهای از objectها کار میکند،
مانند: فیلترها، زنجیرهی eventها
🔍 شناسایی الگو (Identification)
این الگو معمولاً از طریق متدهای رفتاری (behavioral methods) در گروهی از objectها قابل شناسایی است
که بهصورت غیرمستقیم متدهای مشابهی را در objectهای دیگر فراخوانی میکنند،
در حالی که تمام objectها از یک interface مشترک پیروی میکنند.
🧠 مثال مفهومی (Conceptual Example)
این مثال ساختار الگوی طراحی Chain of Responsibility را نشان میدهد.
تمرکز آن روی پاسخ دادن به این سوالهاست:
این الگو از چه کلاسهایی تشکیل شده است؟
هر کدام از این کلاسها چه نقشی دارند؟
اجزای الگو چگونه با یکدیگر در ارتباط هستند؟
📄 Program.cs : مثال مفهومی
using System;
using System.Collections.Generic;
namespace RefactoringGuru.DesignPatterns.ChainOfResponsibility.Conceptual
{
// The Handler interface declares a method for building the chain of
// handlers. It also declares a method for executing a request.
public interface IHandler
{
IHandler SetNext(IHandler handler);
object Handle(object request);
}
// The default chaining behavior can be implemented inside a base handler
// class.
abstract class AbstractHandler : IHandler
{
private IHandler _nextHandler;
public IHandler SetNext(IHandler handler)
{
this._nextHandler = handler;
// Returning a handler from here will let us link handlers in a
// convenient way like this:
// monkey.SetNext(squirrel).SetNext(dog);
return handler;
}
public virtual object Handle(object request)
{
if (this._nextHandler != null)
{
return this._nextHandler.Handle(request);
}
else
{
return null;
}
}
}
class MonkeyHandler : AbstractHandler
{
public override object Handle(object request)
{
if ((request as string) == "Banana")
{
return $"Monkey: I'll eat the {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
class SquirrelHandler : AbstractHandler
{
public override object Handle(object request)
{
if (request.ToString() == "Nut")
{
return $"Squirrel: I'll eat the {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
class DogHandler : AbstractHandler
{
public override object Handle(object request)
{
if (request.ToString() == "MeatBall")
{
return $"Dog: I'll eat the {request.ToString()}.\n";
}
else
{
return base.Handle(request);
}
}
}
class Client
{
// The client code is usually suited to work with a single handler. In
// most cases, it is not even aware that the handler is part of a chain.
public static void ClientCode(AbstractHandler handler)
{
foreach (var food in new List<string> { "Nut", "Banana", "Cup of coffee" })
{
Console.WriteLine($"Client: Who wants a {food}?");
var result = handler.Handle(food);
if (result != null)
{
Console.Write($" {result}");
}
else
{
Console.WriteLine($" {food} was left untouched.");
}
}
}
}
class Program
{
static void Main(string[] args)
{
// The other part of the client code constructs the actual chain.
var monkey = new MonkeyHandler();
var squirrel = new SquirrelHandler();
var dog = new DogHandler();
monkey.SetNext(squirrel).SetNext(dog);
// The client should be able to send a request to any handler, not
// just the first one in the chain.
Console.WriteLine("Chain: Monkey > Squirrel > Dog\n");
Client.ClientCode(monkey);
Console.WriteLine();
Console.WriteLine("Subchain: Squirrel > Dog\n");
Client.ClientCode(squirrel);
}
}
}
Output.txt: Execution result
Chain: Monkey > Squirrel > Dog Client: Who wants a Nut? Squirrel: I'll eat the Nut. Client: Who wants a Banana? Monkey: I'll eat the Banana. Client: Who wants a Cup of coffee? Cup of coffee was left untouched. Subchain: Squirrel > Dog Client: Who wants a Nut? Squirrel: I'll eat the Nut. Client: Who wants a Banana? Banana was left untouched. Client: Who wants a Cup of coffee? Cup of coffee was left untouched.