মূল বিষয়বস্তুতে যান
Clean Code Mastery

Chain of Responsibility Pattern: যে যেটা পারে সে সামলাও, নইলে পাস করো

Chain of Responsibility pattern শেখো একটা school leave application-এর গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, table, আর practice task সহ।

22 মিনিট আপডেট: June 11, 2026beginner
design-patternsbehavioral-patternschain-of-responsibilitytypescriptcsharpmiddlewareoop

রুবেলের ছুটির আবেদন

ধরো রুবেল পড়ে ক্লাস সেভেনে, ঢাকার একটা school-এ। তার বড় ভাই তারিকের বিয়ে পরের সপ্তাহে — গ্রামের বাড়িতে। পুরো পরিবার যাবে। রুবেলকে তিন দিনের ছুটি নিতে হবে।

সোমবার সকালে রুবেল মনোযোগ দিয়ে একটা leave application লিখলো। প্রথম ক্লাস শুরুর আগেই গিয়ে দিলো তার class teacher স্যারকে।

এখন দেখো সেই কাগজটার কী হয়।

স্যার টিফিনের সময় আবেদনটা পড়লেন। school-এর নিয়ম হলো class teacher সর্বোচ্চ এক দিনের ছুটি দিতে পারেন। তিন দিন তার এখতিয়ারের বাইরে। তো কী করলেন? reject করলেন না। রুবেলকে ডেকে বকলেনও না। শুধু কাগজের কোণে "Forwarded" লিখে পাঠিয়ে দিলেন vice principal ম্যামের কাছে।

vice principal ম্যাম দুই দিন পর্যন্ত ছুটি দিতে পারেন। তিন দিন তার পক্ষেও সম্ভব না। তিনিও "Forwarded" লিখে পাঠালেন principal স্যারের কাছে।

principal স্যার যেকোনো যুক্তিসংগত ছুটি দিতে পারেন। তিনি আবেদনটা পড়লেন, ভাইয়ের বিয়ের কথা মাথায় নিলেন, আর সই করে দিলেন। Approved! মঙ্গলবার রুবেল জানলো সে গ্রামে যেতে পারবে।

এই ছোট গল্পে কিছু গুরুত্বপূর্ণ জিনিস লক্ষ্য করো:

  • রুবেল আবেদনটা দিয়েছিল শুধু একজনকে — class teacher স্যারকে।
  • রুবেল জানতও না, জানার দরকারও ছিল না, কে শেষ পর্যন্ত approve করবে।
  • প্রতিটা মানুষের সামনে একটাই সিদ্ধান্ত ছিল: নিজে সামলাবো, নাকি পরেরজনের কাছে পাঠাবো।
  • আবেদনটা একটা chain ধরে এগিয়েছে: class teacher → vice principal → principal।
  • chain-এর কেউই পুরো school-এর নিয়ম জানার দরকার ছিল না। প্রত্যেকে শুধু নিজের সীমা জানতো।

এটাই হলো Chain of Responsibility design pattern। তুমি একটা request দাও chain-এর প্রথম link-এ। সেটা এগিয়ে চলে link থেকে link — যতক্ষণ না কেউ দায়িত্ব নেয়। রুবেলের চিঠিটা মাথায় রাখো — এই পুরো lesson সেই চিঠিকে ঘিরেই।

চিঠির যাত্রাটা flow হিসেবে দেখো:

চিত্র ১: রুবেলের ছুটির আবেদন school chain ধরে উপরে উঠছে

আর এই যাত্রাটা একটা journey হিসেবে — প্রতিটা স্টেশনে কে কেমন অনুভব করছে:

চিত্র ২: রুবেলের টেবিল থেকে principal-এর office পর্যন্ত চিঠির সফর

Chain of Responsibility pattern আসলে কী?

Chain of Responsibility (সংক্ষেপে CoR বা Chain of Command) একটা behavioral design pattern। Behavioral pattern মানে হলো object-গুলো কীভাবে একে অপরের সাথে কথা বলে আর কাজ ভাগ করে নেয় — সেটা নিয়ে।

exam-এ মনে রাখার মতো একটা সংজ্ঞা:

Chain of Responsibility একটা request-কে handler object-এর একটা লাইন ধরে পাঠায়। প্রতিটা handler নিজে নিজে সিদ্ধান্ত নেয় — সে request টা process করবে নাকি পরেরজনের কাছে পাঠাবে। যে পাঠিয়েছে সে কখনো জানে না কে শেষ পর্যন্ত handle করবে।

এই pattern সবার আগে describe করা হয়েছিল বিখ্যাত Gang of Four (GoF) বইয়ে — Design Patterns: Elements of Reusable Object-Oriented Software (1994)। ওই বইয়ের এগারোটা behavioral pattern-এর একটা এটা।

তিনটা ছোট idea এই pattern-কে কাজ করায়:

  1. একটা common interface। সব handler বাইরে থেকে একই রকম দেখায়। একটাই main method, সাধারণত handle(request) নামে। class teacher, vice principal, principal — চিঠির কাছে তিনজনকেই একই রকম লাগে। তিনজনই পড়ে আর সিদ্ধান্ত নেয়।
  2. পরের handler-এর দিকে pointer। প্রতিটা handler জানে পরের link কে। class teacher জানে পরে যাবে vice principal-এর কাছে। এই pointer-টাই তিনটা আলাদা মানুষকে একটা chain বানায়।
  3. প্রতিটা link-এ একটা choice। Handle করে থামাবো, নাকি পাস করবো। কখনো কখনো handler একটু কাজ করে তারপরও পাস করে দেয় — যেমন class teacher "attendance is good" লিখে forward করার আগে।

pattern-এর role-গুলো আর school-এর গল্প মেলাও:

Pattern roleকী করেSchool-এ কে
Handler interfacesetNext() আর handle() declare করে"যে কেউ leave letter পেতে পারে" — এই ধারণাটা
Base handlerপরের link store করে, default-এ forward করে"Forwarded" লেখার school-এর অভ্যাস
Concrete handlersআসল checking logic, একটা rule per handlerclass teacher, vice principal, principal
Clientchain জোড়া লাগায় আর request পাঠায়রুবেল (আর যে office chain-এর order ঠিক করেছে)
Requestchain ধরে যেটা এগিয়ে যাচ্ছেleave application নিজেই
💡

মনে রাখার সহজ trick: school-এর লাইনের মতো ভাবো — "teacher পারছে না? উপরে পাঠাও!" যে পাঠাচ্ছে সে শুধু প্রথম link-এর সাথে কথা বলে। বাকিটা chain নিজেই সামলায়। রুবেলের গল্পটা যদি আবার বলতে পারো, তাহলে এই pattern তুমি বুঝেই ফেলেছো।

পুরো pattern-টা একটা mind map হিসেবে — exam-এর আগে notebook-এ এঁকে নিতে পারো:

চিত্র ৩: Chain of Responsibility এক নজরে

সমস্যাটা কোথায়?

এই pattern দরকার হয় কেন? আগে ব্যথাটা দেখো।

ধরো school তোমাকে দিয়ে leave approval software বানাচ্ছে, আর তুমি দ্রুত একটা বড় function লিখে ফেললে:

// ❌ The "one giant function" approach — works today, hurts tomorrow
function approveLeave(days: number, studentName: string): string {
  if (days <= 1) {
    return `Class Teacher approved ${days} day leave for ${studentName}`;
  } else if (days <= 2) {
    return `Vice Principal approved ${days} day leave for ${studentName}`;
  } else if (days <= 5) {
    return `Principal approved ${days} day leave for ${studentName}`;
  } else {
    return `Leave rejected. Too many days!`;
  }
}

ছোট case-এ ঠিকঠাক লাগছে। কিন্তু real software বাড়ে — real school যেভাবে বাড়ে। এক term-এর মধ্যে school নতুন rule যোগ করলো:

  • Sports leave-এর জন্য আগে Sports Teacher-এর কাছে যেতে হবে।
  • পাঁচ দিনের বেশি medical leave হলে School Board দেখবে।
  • exam week-এ সব ছুটির আবেদনে extra "exam check" লাগবে।
  • day scholar আর hostel student-এর জন্য প্রথম ধাপ আলাদা।

এখন তোমার একটা function if, else if, আর nested condition-এর জঙ্গল হয়ে যাবে। নতুন rule মানেই এই একই function edit করা। edit করলে পুরনো rule ভাঙতে পারে। একটা rule test করতে গেলে বাকি সব rule মাথায় রাখতে হয়। সবচেয়ে বড় সমস্যা হলো, check-এর order function-এর ভেতরে আটকে আছে। surgery ছাড়া order বদলানো বা একটা check reuse করা সম্ভব না।

আসল সমস্যার একটা নাম আছে: sender আর receiver-এর মধ্যে tight coupling। যে code request পাঠাচ্ছে সে সরাসরি জানে approver-দের পুরো list আর তাদের exact order। রুবেলকে কিন্তু এই list জানতে হয়নি — সে শুধু class teacher স্যারের হাতে চিঠিটা দিয়েছিল। তোমার code-ও সেই আরামে থাকুক।

Chain of Responsibility এটা ঠিক করে প্রতিটা rule-কে আলাদা handler class দিয়ে। প্রতিটা handler একটাই কাজ করে। তুমি যেকোনো order-এ, runtime-এ, এগুলো জোড়া লাগাতে পারো — ট্রেনের কামরার মতো। নতুন rule? নতুন কামরা। নতুন order? কামরা সাজাও নতুন করে। engine (client) কখনো বদলায় না।

কীভাবে কাজ করে, ধাপে ধাপে

চলো pattern-টা ধীরে ধীরে তৈরি করি, চিঠির গল্প ধরে।

ধাপ ১: একটা common Handler interface বানাও। এটা declare করে setNext(handler) — chain জোড়া লাগানোর জন্য, আর handle(request) — request process করার জন্য।

ধাপ ২: একটা base handler class বানাও (optional কিন্তু অনেক helpful)। এটা next handler store করে আর default behavior দেয়: "next থাকলে ওর কাছে পাঠাও, না থাকলে থামো।" Concrete handler-গুলো তখন শুধু নিজের special logic লেখে। এই base class হলো সেই "Forwarded" লেখার school-এর অভ্যাস — প্রত্যেককে নতুন করে শিখতে হয় না।

ধাপ ৩: প্রতিটা rule-এর জন্য একটা concrete handler লেখো। ClassTeacher, VicePrincipal, Principal। প্রতিটা handler request দেখে। পারলে সামলায়, chain থামে। না পারলে পরেরজনকে call করে।

ধাপ ৪: client-এ chain জোড়া লাগাও। classTeacher.setNext(vicePrincipal).setNext(principal) — এটুকুই। order বদলাতে চাও? শুধু wiring বদলাও, handler-গুলো না।

ধাপ ৫: chain-এর মাথায় request পাঠাও। Client classTeacher.handle(request) call করে আর result-এর অপেক্ষায় থাকে।

রুবেলের তিন দিনের আবেদন chain ধরে কীভাবে যায় — message by message:

চিত্র ৪: রুবেলের আবেদন handler-এর chain ধরে এগিয়ে যাচ্ছে

দেখো, রুবেল শুধু class teacher স্যারের সাথে কথা বলেছে। বাকিটা chain-এর ভেতরে হয়েছে। কাল যদি vice principal আর principal-এর মাঝে একজন Head of Department যোগ হয়, রুবেলের কাজ একটুও বদলাবে না। শুধু wiring বদলাবে।

আবেদনটা যে state-গুলোর মধ্যে দিয়ে যায় সেটাও দেখা যাক। যেকোনো মুহূর্তে চিঠিটা ঠিক একটা desk-এ আছে:

চিত্র ৫: একটা ছুটির আবেদনের জীবন state হিসেবে

একটা গুরুত্বপূর্ণ কথা: একটা request তিনভাবে শেষ হতে পারে।

  1. Chain-এর মাঝে handle হয়। কেউ approve বা reject করে। Chain থামে।
  2. Chain-এর শেষে পড়ে যায়। কেউ handle করেনি। Client-কে এটার জন্য আগে থেকে plan করতে হবে (null return, default answer, বা error)।
  3. অনেক link মিলে process করে। কিছু version-এ প্রতিটা handler একটু কাজ করে আর pass করে। Middleware এভাবেই কাজ করে — নিচে আরো বিস্তারিত আছে।

একটু advanced: এই তৃতীয় style-টাই আসলে web framework-এর middleware pipeline। ASP.NET Core বা Express.js-এ একটা HTTP request একটা chain-এর ভেতর দিয়ে যায় যেখানে প্রতিটা link সাধারণত কাজ করে: logging middleware request note করে pass করে, authentication middleware user attach করে pass করে, আর final handler response তৈরি করে। কিছু ভুল হলেই chain short-circuit হয় — একটা auth middleware 401 Unauthorized return করে পরেরজনকে আর call করে না। তুমি Program.cs-এ বা app.use(...) দিয়ে middleware-এর order ঠিক করো — এটাই আসলে chain wiring।

Real code-এ দেখো

চলো TypeScript-এ রুবেলের school-টা বানাই। comment-গুলো পড়ো — সেগুলোই গল্পটা বলছে।

// The request object — the leave application itself
interface LeaveRequest {
  studentName: string;
  days: number;
  reason: string;
}
 
// Step 1: Common interface for every approver in the chain
interface LeaveHandler {
  setNext(handler: LeaveHandler): LeaveHandler;
  handle(request: LeaveRequest): string | null;
}
 
// Step 2: Base class — stores "next" and forwards by default
abstract class BaseApprover implements LeaveHandler {
  private next: LeaveHandler | null = null;
 
  setNext(handler: LeaveHandler): LeaveHandler {
    this.next = handler;
    return handler; // returning next lets us chain calls fluently
  }
 
  handle(request: LeaveRequest): string | null {
    if (this.next) {
      return this.next.handle(request); // forward to the next link
    }
    return null; // end of chain — nobody handled it
  }
}
 
// Step 3: Concrete handlers — one small class per approver
class ClassTeacher extends BaseApprover {
  handle(request: LeaveRequest): string | null {
    if (request.days <= 1) {
      return `Mrs. Sen approved ${request.days}-day leave for ${request.studentName}.`;
    }
    console.log("Mrs. Sen: above my limit, passing up...");
    return super.handle(request); // pass to the next approver
  }
}
 
class VicePrincipal extends BaseApprover {
  handle(request: LeaveRequest): string | null {
    if (request.days <= 2) {
      return `Mr. Iyer approved ${request.days}-day leave for ${request.studentName}.`;
    }
    console.log("Mr. Iyer: above my limit, passing up...");
    return super.handle(request);
  }
}
 
class Principal extends BaseApprover {
  handle(request: LeaveRequest): string | null {
    if (request.days <= 5) {
      return `Mrs. Rao approved ${request.days}-day leave for ${request.studentName}.`;
    }
    console.log("Mrs. Rao: even I cannot approve this much!");
    return super.handle(request);
  }
}
 
// Step 4: Client wires the chain — order is just the wiring order
const classTeacher = new ClassTeacher();
const vicePrincipal = new VicePrincipal();
const principal = new Principal();
 
classTeacher.setNext(vicePrincipal).setNext(principal);
 
// Step 5: Fire requests at the head of the chain
const requests: LeaveRequest[] = [
  { studentName: "Riya", days: 1, reason: "Doctor visit" },
  { studentName: "Priya", days: 3, reason: "Cousin Anushka's wedding" },
  { studentName: "Meera", days: 10, reason: "Family trip" },
];
 
for (const req of requests) {
  console.log(`\n--- ${req.studentName} asks for ${req.days} day(s) ---`);
  const result = classTeacher.handle(req);
  console.log(result ?? "Nobody could approve. Please meet the school board.");
}

Output:

--- Riya asks for 1 day(s) ---
Mrs. Sen approved 1-day leave for Riya.
 
--- Priya asks for 3 day(s) ---
Mrs. Sen: above my limit, passing up...
Mr. Iyer: above my limit, passing up...
Mrs. Rao approved 3-day leave for Priya.
 
--- Meera asks for 10 day(s) ---
Mrs. Sen: above my limit, passing up...
Mr. Iyer: above my limit, passing up...
Mrs. Rao: even I cannot approve this much!
Nobody could approve. Please meet the school board.

Meera-র case দেখো। তার request chain-এর শেষে পড়ে গেছে, আর client একটা default message দিয়ে সেটা handle করেছে। সবসময় "কেউ handle করলো না" case-এর জন্য plan রাখো।

আর পরিবর্তন করা কত সহজ দেখো। Sports Teacher-কে sports leave-এর জন্য সবার আগে check করতে হবে? একটা নতুন class লেখো আর wiring-এর এক লাইন বদলাও:

sportsTeacher.setNext(classTeacher).setNext(vicePrincipal).setNext(principal);

পুরনো কোনো handler ছোঁওয়া হয়নি। এটাই Open/Closed Principle কাজে: extension-এর জন্য open, modification-এর জন্য closed।

তুমি যে class structure তৈরি করলে সেটা দেখো:

চিত্র ৬: স্কুলের ছুটি চেইনের class structure

C#-এ একই জিনিস

C#-এ pattern-টা প্রায় একই রকম দেখায়। এই ছোট version-টা একই school-এর গল্পে:

public record LeaveRequest(string StudentName, int Days);
 
public abstract class Approver
{
    private Approver? _next;
 
    public Approver SetNext(Approver next)
    {
        _next = next;
        return next;
    }
 
    public virtual string? Handle(LeaveRequest request)
        => _next?.Handle(request); // default: forward, or null at chain end
}
 
public class ClassTeacher : Approver
{
    public override string? Handle(LeaveRequest request)
        => request.Days <= 1
            ? $"Class Teacher approved {request.Days}-day leave."
            : base.Handle(request);
}
 
public class VicePrincipal : Approver
{
    public override string? Handle(LeaveRequest request)
        => request.Days <= 2
            ? $"Vice Principal approved {request.Days}-day leave."
            : base.Handle(request);
}
 
public class Principal : Approver
{
    public override string? Handle(LeaveRequest request)
        => request.Days <= 5
            ? $"Principal approved {request.Days}-day leave."
            : base.Handle(request);
}
 
// Client
var teacher = new ClassTeacher();
teacher.SetNext(new VicePrincipal()).SetNext(new Principal());
 
Console.WriteLine(teacher.Handle(new LeaveRequest("Priya", 3)));
// Output: Principal approved 3-day leave.

Python-ও দেখো — একই গল্প, interface ছাড়া duck typing দিয়ে:

class Approver:
    def __init__(self, limit: int, title: str):
        self.limit = limit
        self.title = title
        self.next: "Approver | None" = None
 
    def set_next(self, handler: "Approver") -> "Approver":
        self.next = handler
        return handler
 
    def handle(self, days: int) -> str:
        if days <= self.limit:
            return f"{self.title} approved {days}-day leave."
        if self.next:
            return self.next.handle(days)
        return "Nobody could approve. Meet the school board."
 
 
teacher = Approver(1, "Mrs. Sen")
teacher.set_next(Approver(2, "Mr. Iyer")).set_next(Approver(5, "Mrs. Rao"))
 
print(teacher.handle(3))   # Mrs. Rao approved 3-day leave.
print(teacher.handle(10))  # Nobody could approve. Meet the school board.

একটু advanced: C# developer-রা এই pattern প্রতিদিন দেখে — বুঝতে পারে না শুধু। কারণ ASP.NET Core middleware pipeline এই একই idea-র উপর দাঁড়িয়ে। প্রতিটা middleware একটা RequestDelegate পায় যার নাম nextawait next(context) call করলে request এগিয়ে যায়, না করলে pipeline short-circuit হয়। UseAuthentication আগে, UseAuthorization পরে — middleware-এর এই order-ই আমাদের pattern-এর wiring step। আর এই order ভুল করা real-world ASP.NET bug-এর একটা বড় কারণ। একবার middleware-কে handler-এর chain হিসেবে দেখতে শুরু করলে official docs-গুলো এই lesson-এর মতোই পড়াবে।

Real software-এ কোথায় দেখা যায়

Chain of Responsibility শুধু textbook-এর pattern না। Software দুনিয়ার বড় বড় জায়গায় চুপচাপ কাজ করছে।

১. ASP.NET Core middleware। একটা ASP.NET Core app-এ আসা প্রতিটা HTTP request middleware component-এর একটা pipeline-এর মধ্যে দিয়ে যায়। প্রতিটা middleware next call করার আগে-পরে কাজ করতে পারে, অথবা next না ডেকে pipeline short-circuit করতে পারে — যেমন authentication middleware 401 return করা।

২. Express.js middleware। Node.js-এ Express প্রতিটা request (req, res, next) shape-এর function-গুলোর মধ্যে দিয়ে পাঠায়। next() call করলে chain এগোয়। next() না ডেকে response পাঠালে chain থামে। Logging, body parsing, session, routing — সব এই chain-এর link।

৩. Java servlet filter। Java Servlet-এ Filter object-এ doFilter(request, response, chain) method থাকে। প্রতিটা filter সিদ্ধান্ত নেয় chain.doFilter(...) call করবে কিনা। এই design দশকের পুরনো, কিন্তু Java web app-এ এখনো সর্বত্র।

৪. DOM event bubbling। একটা div-এর ভেতরে body-র মধ্যে একটা button-এ click করলে event "bubble" করে উপরে: button → div → body → document। পথে প্রতিটা element event handle করার সুযোগ পায়, যেকেউ stopPropagation() call করে chain ভাঙতে পারে। Browser নিজেই তোমার জন্য Chain of Responsibility চালাচ্ছে।

৫. Logging framework। Logger-রা একটা log record-কে handler-এর মধ্যে দিয়ে পাঠায় (console handler, file handler, email-on-error handler)। প্রতিটা handler log level দেখে সিদ্ধান্ত নেয়।

৬. Support escalation। Helpdesk software ticket escalate করে: bot → level-1 agent → level-2 engineer → manager। চেনা লাগছে? এটা হলো রুবেলের চিঠি — headset পরে।

Real softwareRequest কী?Handler-রা কে?Link chain থামাতে পারে?
ASP.NET Core pipelineHTTP requestMiddleware componentহ্যাঁ — next skip করলে
Express.jsHTTP requestMiddleware functionহ্যাঁ — next() ছাড়া respond করলে
Java servlet filterHTTP requestFilter objectহ্যাঁ — chain.doFilter skip করলে
DOM event bubblingUI event (click, key)Path-এর elementsহ্যাঁ — stopPropagation()
Logging frameworkLog recordLog handler/appenderহ্যাঁ — level দেখে filter করলে
Helpdesk escalationSupport ticketLevel অনুযায়ী agentহ্যাঁ — যেকোনো level-এ resolve করলে

একটা মজার ব্যাপার: ভালো chain-এ বেশিরভাগ request শুরুতেই থামে। রুবেলের school-এ বেশিরভাগ চিঠি এক দিনের — class teacher স্যারই approve করেন। principal-এর কাছে বেশিরভাগ চিঠি যাওয়া মানে নিয়মটাই বাজে। Software-এও একই কথা — প্রথম handler-গুলো common case-গুলো নিজেই absorb করুক।

চিত্র ৭: স্কুলের চেইনে ছুটির চিঠি সাধারণত কোথায় থামে

আর request যত বড়, chain-এ তত গভীরে যায়:

চিত্র ৮: leave size অনুযায়ী একটা রিকোয়েস্ট চেইনে কতটা গভীরে যায়

১০ দিনের bar দেখো: তিনজনের কাছেই গেছে, কিন্তু শেষে unhandled। পুরো chain ঘুরলেও উত্তর guarantee নেই — তাই fallback message-টা জরুরি।

কখন use করবে, কখন করবে না

প্রতিটা pattern-এর মতো CoR-ও একটা tool, নিয়ম না। এই table দেখে সিদ্ধান্ত নাও:

পরিস্থিতিUse করবে?কারণ
অনেক object request handle করতে পারে, আগে থেকে জানা যায় না কেহ্যাঁChain runtime-এ সঠিক handler খুঁজে নেবে
পুরনো code edit না করে processing step যোগ, সরানো বা সাজানো যাবেহ্যাঁWiring আর handler logic আলাদা
একটা লম্বা if / else if ladder ঠিক করতে হবেহ্যাঁপ্রতিটা branch একটা clean handler হবে
পরপর কয়েকটা check চালাতে হবে (auth, validation, rate limit)হ্যাঁClassic middleware-style chain
সবসময় একটাই fixed object request handle করেনাDirect method call সহজ আর পরিষ্কার
মাত্র দুই-তিনটা stable condition যা কখনো বদলায় নানাছোট if/else honest আর readable
প্রতিটা receiver সবসময় request পাবেই, থামানো যাবে নানাএটা Observer / event-এর কাজ
প্রতিটা request-এর জন্য guaranteed উত্তর দরকারসাবধানেসম্ভব, কিন্তু chain-এর শেষে default handler যোগ করতে হবে

ছবি দেখতে ভালো লাগলে, তোমার situation এই map-এ বসাও। "অনেক possible handler" আর "flow বারবার বদলায়" — এই দিকে যত বেশি যাবে, chain তত বেশি মানানসই:

চিত্র ৯: chain তোমার সমস্যায় fit হয় কিনা সিদ্ধান্ত নেওয়ার একটা quick map

সাধারণ ভুলগুলো

⚠️

ভুল ১: unhandled case ভুলে যাওয়া। কোনো handler request না নিলে সেটা চুপচাপ chain-এর শেষে পড়ে যায়। নতুনরা এটা ভুলে যায় আর program undefined বা null return করে কোনো message ছাড়া। সবসময় ঠিক করো "কেউ handle করলো না" মানে কী: chain-এর শেষে একটা default handler, একটা clear error, অথবা Meera-র জন্য আমাদের school-board message-এর মতো কিছু।

⚠️

ভুল ২: next handler-কে call করতে ভুলে যাওয়া। concrete handler-এর ভেতরে return super.handle(request) (বা next.handle(request)) ভুলে গেলে chain সেখানেই চুপচাপ থামে। Request হারিয়ে যায়, আর তুমি ঘণ্টার পর ঘণ্টা খোঁজো কেন। Base class-কে দিয়ে forwarding করাও — তাহলে concrete class শুধু নিজের দরকারী অংশ override করবে।

আরো কিছু সাবধানতা:

  • Circular chain বানিও না। A-র next যদি B হয়, আর B-র next যদি A হয়, request চিরকাল ঘুরতে থাকবে — দুটো office-এর মধ্যে চিঠি bouncing করার মতো। Wiring এক জায়গায় রাখো যাতে পুরো chain এক নজরে দেখা যায়।
  • এক handler-এ অনেক বেশি logic রেখো না। একটা handler যদি পাঁচটা আলাদা জিনিস check করে, সেটাকে পাঁচটা handler-এ ভাগ করো। এক link, এক কাজ — এটাই পুরো pattern-এর মূল কথা। class teacher ছুটির দিন দেখেন — medical certificate আর exam schedule দেখেন না।
  • Chain order-এর উপর গোপনে নির্ভর করো না। ValidationHandler যদি শুধু তখনই কাজ করে যখন AuthHandler আগে চলেছে — সেটা কোথাও লিখে রাখো, অথবা আরো ভালো হলো handler-গুলো independent রাখো।
  • ছোট সমস্যায় অনেক লম্বা chain বানিও না। তিনটা if statement-এর জায়গায় দশটা class over-engineering। Pattern ব্যথা কমাতে — ceremony বাড়াতে নয়।
  • Chain-এর মাঝে request carelessly mutate করো না। একটা handler request edit করে pass করলে পরের handler-গুলো বদলানো data দেখে। কখনো কখনো এটাই design (middleware-এ user attach করা)। সেটা না হলে request read-only রাখো।

একটু advanced: production middleware pipeline-এ এই ভুলগুলোর debugging version বিখ্যাত। next call না করা middleware-এ প্রতিটা request hang করে বা empty response আসে। Router-এর পরে register করা middleware কখনো run-ই করে না। সবার শেষে register করা exception-handling middleware upstream-এ কিছুই catch করতে পারে না। Framework "mysteriously" কোনো handler ignore করলে আগে chain-টা কাগজে এঁকো — bug প্রায় সবসময় handler-এ না, wiring-এ।

কাছের pattern-গুলোর সাথে তুলনা

Chain of Responsibility-র কয়েকটা look-alike cousin আছে behavioral family-তে। পার্থক্যটা জানা exam আর interview-তে favourite প্রশ্ন।

PatternSender-receiver কীভাবে connectedকে request পায়?মাঝপথে থামানো যায়?
Chain of ResponsibilitySender → first link → next → nextপ্রথম handler যে দাবি করে (সাধারণত একজন)হ্যাঁ, যেকোনো link থামাতে পারে
CommandSender request-কে object-এ wrap করেCommand-এর মাধ্যমে একজন known receiverনা — থামানো নিয়ে না, store/queue/undo নিয়ে
ObserverPublisher subscriber-দের list রাখেসব subscriber প্রতিটা event পায়না, সবাই notify হয়
Mediatorসবাই একটা central hub-এর সাথে কথা বলেHub ঠিক করে কে কাজ করবেHub routing control করে
Decoratorএকটা object-এর চারপাশে wrapper, chain-এর মতোপ্রতিটা wrapper সবসময় run করেনা, Decorator-কে পাস করতেই হবে

সবচেয়ে tricky cousin হলো Decorator, কারণ structure-টা (object একটার পর একটা জুড়ে) হুবহু একই দেখায়। পার্থক্যটা উদ্দেশ্যে: Decorator যোগ করে আর সবসময় call forward করে; CoR handler সিদ্ধান্ত নেয় আর forward না-ও করতে পারে। Decorator chain ভাঙতে পারে না। CoR handler পারে। School-এর ভাষায়: Decorator হলো প্রতিটা office চিঠিতে stamp মেরে সবসময় পাঠিয়ে দেওয়া; CoR হলো প্রতিটা office-এর ক্ষমতা আছে চিঠি নিজেই file করে দেওয়ার।

Observer নিয়ে ভাবো: সকালের assembly-র announcement — সবাই শুনলো (Observer)। রুবেলের চিঠি গেল শুধু একজন final approver-এর কাছে (CoR)। Mediator নিয়ে ভাবো: সব চিঠি যদি প্রথমে school office-এ যায়, আর office ঠিক করে কে handle করবে — সেই central desk হলো Mediator, chain না।

দ্রুত revision

+=====================================================================+
|        CHAIN OF RESPONSIBILITY — QUICK REVISION                     |
+=====================================================================+
| WHAT     : Pass a request along a line of handlers; each one        |
|            handles it OR passes it to the next.                     |
| STORY    : Priya's leave letter: Mrs. Sen -> Mr. Iyer -> Mrs. Rao.  |
|            Student talks only to the first link.                    |
| ROLES    : Handler (interface) | BaseHandler (stores next)          |
|            ConcreteHandlers (real logic) | Client (wires chain)     |
| KEY CODE : setNext(h)  +  handle(request)                           |
| ENDINGS  : handled mid-chain | falls off the end | many links work  |
| WINS     : single responsibility, easy reorder, open/closed,        |
|            reusable handlers                                        |
| RISKS    : unhandled requests, harder debugging, long chains slow   |
| SEEN IN  : ASP.NET Core / Express middleware, servlet filters,      |
|            DOM event bubbling, logging frameworks                   |
| COUSIN   : Decorator looks same but ALWAYS forwards; CoR may STOP.  |
+=====================================================================+

নিজে practice করো

তিনটা task নিজে করো। শুধু পড়লে হবে না — code টাইপ করো।

Task ১: Bank complaint escalation। ধরো একজন customer একটা complaint করছে একটা টাকার পরিমাণ দিয়ে। একটা chain বানাও: CustomerCareExecutive (১,০০০ টাকা পর্যন্ত handle করে) → BranchManager (৫০,০০০ পর্যন্ত) → RegionalOfficer (১০,০০,০০০ পর্যন্ত)। Regional officer-ও না পারলে print করো "Complaint forwarded to the banking ombudsman।" ৫০০, ২০,০০০, আর ৫০,০০,০০০ টাকার amount দিয়ে test করো।

Task ২: Exam-week rule। রুবেলের school-এর chain-এর সামনে একটা ExamWeekHandler যোগ করো। Exam week-এ (isExamWeek = true request-এ থাকলে) এটা medical ছাড়া সব leave request reject করবে — পরেরজনের কাছে পাঠাবে না। দেখাও যে তুমি শুধু wiring line বদলে আর একটা নতুন class যোগ করে এটা করেছো — পুরনো কোনো class edit করোনি। এটাই Open/Closed Principle।

Task ৩ (challenge): Mini middleware। Express-এর মতো একটা ছোট request pipeline বানাও: একটা LoggerHandler যে request print করে সবসময় pass করে, একটা AuthHandler যে request.token না থাকলে "401 Unauthorized" দিয়ে chain থামায়, আর একটা BusinessHandler যে "200 OK" return করে। এটা pattern-এর "সবাই একটু কাজ করে" style দেখাবে। Bonus: LoggerHandler-কে দিয়ে বাকি chain-এর result-এর পরে "request finished" print করাও — এই before/after sandwich-টাই real middleware-এ response time measure করার পদ্ধতি।

Task ৩ শেষ করতে পারলে তুমি মূলত প্রতিটা web framework-এর middleware system-এর হৃদয়টা নতুন করে বানিয়ে ফেলেছো। এবার এই lesson-টা chain-এর পরের জনকে পাস করে দাও। সে না পারলে জানে কী করতে হবে — উপরে পাঠাও!

সচরাচর জিজ্ঞাসা

Chain of Responsibility pattern সহজ কথায় কী?
মানে হলো একটা request-কে একের পর এক handler-এর কাছে পাঠানো। প্রতিটা handler দেখে সে এটা সামলাতে পারবে কিনা। পারলে সামলায়, না পারলে পরেরজনের কাছে পাঠিয়ে দেয়। যে পাঠাচ্ছে সে জানেও না কে শেষ পর্যন্ত কাজটা করবে।
chain-এর কেউ যদি request handle না করে তাহলে কী হয়?
request chain-এর শেষ পর্যন্ত গিয়ে হারিয়ে যায়, কেউ ধরে না। ভালো design-এ এটার জন্য আলাদা ব্যবস্থা থাকে — null return করা, default result দেওয়া, বা একটা clear error throw করা। যে পাঠিয়েছিল সে যেন বুঝতে পারে কেউ দায়িত্ব নেয়নি।
Chain of Responsibility আর Decorator-এর পার্থক্য কী?
দুটোই object-এর একটা লাইন বানায়, কিন্তু উদ্দেশ্য আলাদা। Decorator সবসময় পরের জনকে call করে আর কিছু extra কাজ যোগ করে। কিন্তু Chain of Responsibility-তে handler চাইলে request-কে থামিয়ে দিতে পারে — পরেরজনের কাছে না-ও পাঠাতে পারে।
real software-এ Chain of Responsibility কোথায় দেখা যায়?
Web framework middleware সবচেয়ে বড় উদাহরণ। Express.js আর ASP.NET Core দুটোই প্রতিটা HTTP request-কে একটা middleware handler-এর chain-এর মধ্য দিয়ে পাঠায়। Java servlet filter আর browser-এর DOM event bubbling-ও এভাবেই কাজ করে।
chain-এর প্রতিটা handler-কে কি request handle করতেই হবে?
না। প্রতিটা handler নিজেই সিদ্ধান্ত নেয়। সে request handle করে chain থামিয়ে দিতে পারে, কিছু কাজ করে পরেরজনের কাছে পাঠাতে পারে, অথবা সরাসরি পাস করে দিতে পারে। এই স্বাধীনতাটাই এই pattern-এর আসল শক্তি।

আরো দেখো

সম্পর্কিত পাঠ

Command Pattern: প্রতিটি কাজকে একটি অর্ডার স্লিপে বদলে দাও

রেস্তোরাঁর অর্ডার স্লিপের গল্প দিয়ে Command pattern শেখো। TypeScript আর C#-এ undo ও redo সহ পুরো কোড, diagram, table, আর practice task।

আরও পড়ুন

Iterator Pattern: একে একে প্রতিটা element-এ যাও

Iterator pattern শেখো ট্রেনের টিকেট চেকারের গল্প দিয়ে। TypeScript-এ custom iterator বানাও, for...of আর generator ব্যবহার করো, আর C#-এ IEnumerable দেখো।

আরও পড়ুন

Decorator Pattern: Object-এ একটা একটা করে Layer চাপাও

চায়ের দোকানের এক মজার গল্প দিয়ে Decorator pattern শেখো। নতুন Subclass না বানিয়েই Runtime-এ Object-এ নতুন Behaviour যোগ করো — একটার পর একটা Layer Wrap করে।

আরও পড়ুন

Mediator Pattern: Control Tower যেটা ক্যাওস থামায়

বিমানবন্দরের control tower-এর গল্প দিয়ে Mediator pattern শেখো — সহজ TypeScript আর C# কোড, MediatR উদাহরণ, diagram, table আর practice task সহ।

আরও পড়ুন