সফটওয়্যার ইঞ্জিনিয়ারিংয়ের জটিল কনসেপ্টগুলো অনেক সময় আমাদের দৈনন্দিন জীবনের সাধারণ ঘটনার সাথেই মিলে যায়। Chain of Responsibility ডিজাইন প্যাটার্নটি ঠিক তেমনই একটি কনসেপ্ট। বিষয়টি সহজে বোঝার জন্য চলো রহিম চাচার গ্রামের একটি গল্প দিয়ে শুরু করি।
রহিম চাচা এবং গ্রামের দায়িত্ব বণ্টন ব্যবস্থা
এক গ্রামে রহিম চাচা নামে এক অত্যন্ত বুদ্ধিমান ও প্রবীণ ব্যক্তি ছিলেন। তিনি ছিলেন ওই গ্রামের প্রধান। প্রতিদিন গ্রামের মানুষেরা নানা সমস্যা নিয়ে তার কাছে আসত—কেউ রাস্তা মেরামতের দাবি নিয়ে, কেউ বাজারে নতুন দোকান খোলার অনুমতি নিতে, আবার কেউবা ব্যক্তিগত জমিজমার ঝামেলা নিয়ে।
একসময় রহিম চাচা বুঝতে পারলেন, গ্রামের আকার বাড়ার সাথে সাথে প্রতিদিনের এই সব অভিযোগ একা সামলানো তার পক্ষে আর সম্ভব হচ্ছে না। তাই তিনি তার কাজের চাপ কমানোর জন্য চারজন বিশ্বস্ত লোককে নিয়ে একটি চেইন বা ধারাবাহিক ফিল্টারিং ব্যবস্থা তৈরি করলেন:
১. আব্দুল চাচা (লিপিকার বা লগার): তার কাজ হলো কেউ কোনো সমস্যা নিয়ে এলে প্রথমেই সেটি খাতায় লিখে রাখা (Log করা) এবং পরবর্তী ধাপে পাঠানো।
২. কামরুল ভাই (পরিচয় যাচাইকারী): তিনি চেক করেন অভিযোগকারী ব্যক্তিটি আসলেই এই গ্রামের বাসিন্দা কি না। যদি বহিরাগত কেউ হয়, তবে তাকে ওখান থেকেই বিদায় করে দেওয়া হয়।
৩. সালাম মিয়া (যাচাইকরণকারী): তিনি দেখেন সমস্যাটি আসলেই সত্যি এবং যৌক্তিক কি না। ভুল বা অসম্পূর্ণ তথ্য থাকলে তিনি সেটি বাতিল করে দেন।
৪. আলম দা (সমাধানকারী): উপরের তিনটি ধাপ পেরিয়ে যখন কোনো অভিযোগ আলম দার কাছে আসে, তিনি সেটি সমাধান করার চেষ্টা করেন। যদি সমস্যাটি তার আওতার বাইরে বা খুব গুরুতর হয়, কেবল তখনই সেটি রহিম চাচার (গ্রাম প্রধান) কাছে পাঠানো হয়।
এই চেইন কীভাবে কাজ করে? ধরো, হাসান চাচা গ্রামের রাস্তা মেরামতের জন্য আসলেন। আব্দুল চাচা সেটি লিখলেন, কামরুল ভাই দেখলেন তিনি গ্রামের লোক, সালাম মিয়া যাচাই করলেন রাস্তা আসলেই খারাপ, এবং সবশেষে আলম দা গ্রামের লোকদের নিয়ে রাস্তা ঠিক করে দিলেন। এখানে রহিম চাচার কাছে যাওয়ার কোনো প্রয়োজনই পড়ল না! কিন্তু কিছুক্ষণ পর এক অচেনা লোক এসে গ্রামের জমি দখলের চেষ্টা করল। আব্দুল চাচা খাতায় লিখলেও, কামরুল ভাই চেক করে দেখলেন সে বহিরাগত। তাই রিকোয়েস্টটি ওখানেই বাতিল হয়ে গেল।
সফটওয়্যার ইঞ্জিনিয়ারিংয়ে ঠিক এই ফিল্টারিং বা ধারাবাহিক দায়িত্ব হস্তান্তরের প্রক্রিয়াটিকেই বলা হয় Chain of Responsibility (CoR)।
Chain of Responsibility ডিজাইন প্যাটার্ন কী?
Chain of Responsibility (CoR) হলো একটি Behavioral Design Pattern। এই প্যাটার্ন একাধিক অবজেক্ট বা হ্যান্ডলারকে (Handler) ধারাবাহিকভাবে একটি রিকোয়েস্ট (Request) প্রসেস করার সুযোগ দেয়।
এর মূল ধারণা হলো, একটি রিকোয়েস্ট অনেকগুলো হ্যান্ডলারের মধ্য দিয়ে চেইন বা সিকোয়েন্স অনুযায়ী যেতে থাকবে। যখনই কোনো হ্যান্ডলার রিকোয়েস্টটি রিসিভ করবে, সে প্রথমে সিদ্ধান্ত নেবে যে সে নিজে এটি প্রসেস করতে সক্ষম কি না। যদি সক্ষম হয়, তবে সে সমাধান করে দেবে; আর যদি না হয়, তবে সে রিকোয়েস্টটিকে চেইনের পরবর্তী হ্যান্ডলারের কাছে পাস করে দেবে।
কখন এবং কেন এটি ব্যবহার করবেন? (Deep Dive)
১. জটিল if-else বা switch-case এড়াতে: যখন একটি রিকোয়েস্ট প্রসেস করার জন্য অনেকগুলো কন্ডিশন চেক করতে হয় এবং কন্ডিশনগুলো প্রতিনিয়ত বাড়তে থাকে, তখন if-else ব্যবহার করলে কোড আনমেইনটেইনেবল হয়ে যায়। CoR এই সমস্যা দূর করে।
২. Sender এবং Receiver-কে আলাদা (Decouple) করতে: যে রিকোয়েস্ট পাঠাচ্ছে (Client), তার জানার দরকার নেই কোন হ্যান্ডলার রিকোয়েস্টটি প্রসেস করবে। সে শুধু চেইনের প্রথম হ্যান্ডলারকে রিকোয়েস্টটি দেবে।
৩. Dynamic Workflow তৈরি করতে: রানটাইমে (Runtime) তুমি চাইলে চেইনের সিকোয়েন্স পরিবর্তন করতে পারো অথবা নতুন কোনো হ্যান্ডলার যুক্ত করতে পারো।
৪. Single Responsibility Principle (SRP): প্রতিটি হ্যান্ডলার কেবল তার নিজের নির্দিষ্ট লজিক নিয়ে কাজ করে, ফলে কোড অত্যন্ত ক্লিন থাকে।
Python Code Example: Customer Support System
ধরা যাক, আমরা একটি কাস্টমার সাপোর্ট সিস্টেম তৈরি করছি। এখানে ইউজাররা টিকিট সাবমিট করে। টিকিটগুলো একটি নির্দিষ্ট চেইন ফলো করে প্রসেস হয়: Logging -> Spam Check -> Technical Support -> Billing Support -> Manager Escalation।
নিচে পাইথন (Python) কোডের মাধ্যমে এটি ইমপ্লিমেন্ট করা হলো:
from abc import ABC, abstractmethod
import json
# Step 1: Abstract Handler Class
class SupportHandler(ABC):
def __init__(self):
self._next_handler = None
def set_next(self, handler):
self._next_handler = handler
# Returning handler allows method chaining: a.set_next(b).set_next(c)
return handler
@abstractmethod
def handle(self, request: dict):
if self._next_handler:
return self._next_handler.handle(request)
# If no handler can process the request
print(f"No solution available for: {request.get('issue')}")
return False
# Step 2: Concrete Handlers
class LoggingHandler(SupportHandler):
def handle(self, request: dict):
print(f"[LOG] Received Ticket: {json.dumps(request)}")
# Pass the request to the next handler in the chain
return super().handle(request)
class SpamFilterHandler(SupportHandler):
def handle(self, request: dict):
issue_text = request.get('issue', '').lower()
if 'win money' in issue_text or 'lottery' in issue_text:
print("[SPAM] Rejected ticket due to spam content.")
return False # Chain stops here
return super().handle(request)
class TechnicalSupportHandler(SupportHandler):
def handle(self, request: dict):
if request.get('type') == 'technical':
print(f"[TECH SUPPORT] Issue resolved: {request.get('issue')}")
return True # Successfully handled, chain stops
return super().handle(request)
class BillingSupportHandler(SupportHandler):
def handle(self, request: dict):
if request.get('type') == 'billing':
print(f"[BILLING SUPPORT] Billing issue resolved: {request.get('issue')}")
return True # Successfully handled, chain stops
return super().handle(request)
class ManagerHandler(SupportHandler):
def handle(self, request: dict):
if request.get('priority') == 'high':
print(f"[MANAGER] Escalating to manager: {request.get('issue')}")
return True # Successfully handled, chain stops
return super().handle(request)
# Step 3: Build the Chain and Client Code
if __name__ == "__main__":
# Initialize handlers
logger = LoggingHandler()
spam_filter = SpamFilterHandler()
tech_support = TechnicalSupportHandler()
billing_support = BillingSupportHandler()
manager = ManagerHandler()
# Build the chain
logger.set_next(spam_filter).set_next(tech_support).set_next(billing_support).set_next(manager)
# Step 4: Test Different Cases
tickets = [
{'type': 'technical', 'priority': 'low', 'issue': 'My internet is not working.'},
{'type': 'billing', 'priority': 'medium', 'issue': 'I was charged twice for my subscription.'},
{'type': 'other', 'priority': 'high', 'issue': 'Complaint about service quality.'},
{'type': 'other', 'priority': 'low', 'issue': 'Where can I find the FAQ section?'},
{'type': 'spam', 'priority': 'low', 'issue': 'Win Money Fast! Click here!'},
]
for index, ticket in enumerate(tickets, start=1):
print(f"\n=== Processing Ticket {index} ===")
# Client only interacts with the first handler (logger)
logger.handle(ticket)জনপ্রিয় ফ্রেমওয়ার্কগুলোতে Chain of Responsibility-এর ব্যবহার
আধুনিক ওয়েব ডেভেলপমেন্টে এই প্যাটার্নটি প্রতিনিয়ত ব্যবহার করা হয়। তুমি হয়তো এটি ব্যবহার করছ, কিন্তু জানো না যে এটিই CoR প্যাটার্ন! নিচে কিছু বাস্তব উদাহরণ দেওয়া হলো:
Middleware (Laravel, Django, Express.js): তুমি যখন ওয়েবসাইটে লগইন করার রিকোয়েস্ট পাঠাও, তখন রিকোয়েস্টটি একাধিক মিডলওয়্যারের চেইন পেরিয়ে কন্ট্রোলারে যায়। যেমন: প্রথমে
Logging Middleware, তারপরAuthentication Middleware(ইউজার লগইন করেছে কি না), এবং শেষেAuthorization Middleware(তার অ্যাডমিন প্যানেলে যাওয়ার পারমিশন আছে কি না)। কোনো একটি ধাপে ফেইল করলে রিকোয়েস্ট ওখানেই ব্লক হয়ে যায়।Exception Handling (Try-Catch Blocks): অনেকগুলো
catchবাexceptব্লক পর পর লেখা থাকলে সিস্টেম চেক করে কোন ব্লকটি ওই নির্দিষ্ট এররটি (Error) হ্যান্ডেল করতে সক্ষম। এটিও একটি CoR-এর রূপ।UI Event Bubbling (JavaScript): ওয়েব পেজে তুমি যখন কোনো বাটনে ক্লিক করো, সেই ক্লিক ইভেন্টটি চাইল্ড এলিমেন্ট থেকে শুরু করে প্যারেন্ট এলিমেন্টের দিকে বাবল (Bubble) হতে থাকে, যতক্ষণ না কেউ সেই ইভেন্টটিকে লিসেন বা হ্যান্ডেল করে।
Chain of Responsibility প্যাটার্ন মূলত একটি সিস্টেমকে করে তোলে হাইলি স্কেলেবল, ফ্লেক্সিবল এবং মেইনটেইনেবল। একটি ফাংশনে শত শত লাইনের if-else লেখার চেয়ে দায়িত্বগুলোকে ছোট ছোট অংশে ভাগ করে দেওয়াই হলো প্রফেশনাল সফটওয়্যার ডিজাইনের আসল সৌন্দর্য!