পাঠ ২৩ · ৫৮-এর মধ্যে · মডিউল ৫
Home / Courses / Software Engineering Principles & Git / বিহেভিয়ারাল প্যাটার্ন

বিহেভিয়ারাল প্যাটার্ন — Observer ও Strategy

Behavioral patterns — Observer & Strategy
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • Observer কীভাবে subject ও observer-দের মধ্যে ONE-TO-MANY ডিপেন্ডেন্সি তৈরি করে
  • Subscribe/unsubscribe করে observer-দের রানটাইমে যোগ-বিয়োগ করা
  • Strategy কীভাবে if/elif চেইনের বদলে polymorphic অ্যালগরিদম নির্বাচন সম্ভব করে
  • দুটো ভিন্ন sort strategy-এর ইন্টারচেঞ্জেবিলিটি বাস্তব কোডে যাচাই করা

১ · Observer প্যাটার্ন — state-change notification

Observer প্যাটার্নObserver Patternএকটি ONE-TO-MANY ডিপেন্ডেন্সি — subject-এর state বদলালে এটি তার registered সব observer-কে স্বয়ংক্রিয়ভাবে notify করে। একটি সাবজেক্ট (aka "observable") ও একাধিক observer-এর মধ্যে ONE-TO-MANY ডিপেন্ডেন্সি ডিফাইন করে — সাবজেক্টের state বদলালে এটি স্বয়ংক্রিয়ভাবে তার সব রেজিস্টার্ড observer-কে notify করে, তাদের concrete টাইপ আগে থেকে না জেনেই। Observer-রা runtime-এ SUBSCRIBE/UNSUBSCRIBE করতে পারে — একটি genuinely useful মেকানিজম state-বদলানো কম্পোনেন্টকে সেই কম্পোনেন্টগুলো থেকে DECOUPLE করতে যাদের প্রতিক্রিয়া জানানো দরকার (এই কম্পোনেন্টগুলোর সংখ্যা বদলাতেও পারে)। হার্ডওয়্যার লেভেলে এর একটি অ্যানালগাস ধারণাInterrupt Handlingহার্ডওয়্যার ইন্টারাপ্ট হলো একটি ডিভাইসের "কিছু ঘটেছে" জানানোর মেকানিজম — CPU সেই ইভেন্টে "রিঅ্যাক্ট" করে, অনেকটা observer-এর মতো — computer-architecture কোর্সের L48-এ বিস্তারিত। আছে — হার্ডওয়্যার ইন্টারাপ্ট, যেখানে একটি ডিভাইস CPU-কে "কিছু ঘটেছে" জানিয়ে দেয়।

Subject.notify_all(event) EmailNotifier.update() LogObserver.update()
Subject তার observer লিস্টের প্রতিটিকে সরাসরি রেফারেন্স করে রাখে — কিন্তু তাদের concrete টাইপ জানে না, শুধু জানে প্রতিটির একটি update(event) মেথড আছে।
Python
# Observer pattern -- Subject-এর state বদলালে এটি তার registered সব observer-কে
# notify করে -- subject তাদের concrete টাইপ জানে না, শুধু জানে তাদের update(event)
# মেথড আছে -- subscribe/unsubscribe রানটাইমে করা যায়।

class Observer:
    def update(self, event):
        raise NotImplementedError

class EmailNotifier(Observer):
    def update(self, event):
        print(f"[EmailNotifier] ইমেইল পাঠানো হলো: {event}")

class LogObserver(Observer):
    def update(self, event):
        print(f"[LogObserver] লগ করা হলো: {event}")

class Subject:
    def __init__(self):
        self._observers = []

    def subscribe(self, observer):
        self._observers.append(observer)

    def unsubscribe(self, observer):
        self._observers.remove(observer)

    def notify_all(self, event):
        for observer in self._observers:
            observer.update(event)

subject = Subject()
email_notifier = EmailNotifier()
log_observer = LogObserver()

subject.subscribe(email_notifier)
subject.subscribe(log_observer)

print("-- প্রথম state change (দুইজনই subscribed) --")
subject.notify_all("অর্ডার #101 প্লেস হয়েছে")

subject.unsubscribe(email_notifier)

print("\n-- দ্বিতীয় state change (EmailNotifier unsubscribed) --")
subject.notify_all("অর্ডার #101 শিপড হয়েছে")

    
লক্ষ্য করুন দ্বিতীয় notify_all() কলে শুধু LogObserver প্রিন্ট করেছে — EmailNotifier unsubscribe করার পর সে আর _observers লিস্টে নেই, তাই সে আর কোনো notification পায়নি। এটিই runtime-এ subscription dynamically বদলানোর প্রমাণ।

২ · Strategy প্যাটার্ন — ইন্টারচেঞ্জেবল অ্যালগরিদম

Strategy প্যাটার্নStrategy Patternএকটি ফ্যামিলি অ্যালগরিদমকে পৃথক, ইন্টারচেঞ্জেবল ক্লাসে encapsulate করে, client কোড রানটাইমে যেকোনো একটি বেছে নিতে পারে। ইন্টারচেঞ্জেবল অ্যালগরিদমের একটি ফ্যামিলি ডিফাইন করে, প্রতিটিকে একটি common ইন্টারফেস ইমপ্লিমেন্ট করা নিজস্ব ক্লাসে encapsulate করে, এবং client কোডকে রানটাইমে যেকোনো একটি strategy সিলেক্ট/সোয়াপ করতে দেয় — এটি সরাসরি M4/L16-এর OCP-এর একটি concrete বাস্তবায়ন: if/elif চেইন দিয়ে আচরণ বেছে নেওয়ার বদলে, একটি strategy অবজেক্ট polymorphic-ভাবে ব্যবহার করা হয় — নতুন strategy যোগ করতে শুধু একটি নতুন ক্লাস লাগে, বিদ্যমান কোডে কোনো পরিবর্তন লাগে না।

Python
# Strategy pattern -- bubble_sort ও python_native_sort দুটোই একই "strategy(data)"
# ইন্টারফেস মেনে চলে -- Sorter ক্লাস রানটাইমে যেকোনো একটি strategy বেছে নিতে পারে,
# ফলাফল identical হওয়া উচিত।

def bubble_sort(data):
    result = list(data)
    n = len(result)
    for i in range(n):
        for j in range(0, n - i - 1):
            if result[j] > result[j + 1]:
                result[j], result[j + 1] = result[j + 1], result[j]
    return result

def python_native_sort(data):
    return sorted(data)

class Sorter:
    def __init__(self, strategy):
        self.strategy = strategy

    def sort(self, data):
        return self.strategy(data)

numbers = [5, 2, 9, 1, 5, 6]

sorter_bubble = Sorter(bubble_sort)
sorter_native = Sorter(python_native_sort)

result_bubble = sorter_bubble.sort(numbers)
result_native = sorter_native.sort(numbers)

print("Bubble sort ফলাফল :", result_bubble)
print("Native sort ফলাফল :", result_native)
print("দুটো strategy identical ফলাফল দিলো?", result_bubble == result_native)

    
[5, 2, 9, 1, 5, 6] সাজালে হবে [1, 2, 5, 5, 6, 9] — দুটো সম্পূর্ণ ভিন্ন algorithm (একটি হাতে লেখা bubble sort, একটি Python-এর নিজস্ব sorted()) হুবহু একই ফলাফল দেয়, কারণ দুটোই একই কন্ট্র্যাক্ট মেনে চলে — এটিই Strategy-এর মূল প্রতিশ্রুতি: বাস্তবায়ন যাই হোক, ইন্টারফেস একই থাকলে strategy সোয়াপ করা নিরাপদ।
মূল কথা · Key takeaway

Observer ও Strategy দুটোই "কম্পোজিশন-ওভার-ইনহেরিটেন্স" চিন্তাধারার উদাহরণ — Observer একটি অবজেক্টের রিঅ্যাকশন-লজিক বাইরে থেকে ইনজেক্ট করা observer-দের মধ্যে রাখে; Strategy একটি অবজেক্টের কোন-অ্যালগরিদম-চলবে সিদ্ধান্তটি বাইরে থেকে ইনজেক্ট করা strategy অবজেক্টের মধ্যে রাখে। দুটোই M4/L18-এর dependency injection ধারণার সরাসরি প্রয়োগ।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ Observer প্যাটার্নে Subject-কে কেন observer-দের CONCRETE টাইপ (যেমন EmailNotifier) জানতে হয় না?

কারণ Subject শুধু Observer ইন্টারফেসের বিরুদ্ধে কোড করে — এটি শুধু জানে প্রতিটি রেজিস্টার্ড অবজেক্টের একটি update(event) মেথড আছে, সেই অবজেক্টটি ঠিক কীভাবে সেই ইভেন্টে প্রতিক্রিয়া দেখাবে তা Subject-এর জানার দরকার নেই। এই কারণেই নতুন ধরনের observer (যেমন একটি SMSNotifier) যোগ করতে Subject ক্লাসের একটি লাইনও বদলাতে হয় না — সরাসরি M4/L16-এর OCP-এর প্রয়োগ।

প্র ০২ Strategy প্যাটার্ন ব্যবহার না করে যদি Sorter-এর ভেতরেই if/elif দিয়ে "bubble" বা "native" স্ট্রিং চেক করে সাজানো হতো, কী সমস্যা হতো?

একটি নতুন sort algorithm (যেমন quicksort) যোগ করতে হলে Sorter ক্লাসের ভেতরেই if/elif চেইন এডিট করতে হতো — বিদ্যমান, ইতিমধ্যে টেস্ট করা কোড মডিফাই করা, সরাসরি M4/L16-এর OCP লঙ্ঘন। Strategy প্যাটার্নে নতুন অ্যালগরিদম যোগ করতে শুধু একটি নতুন ফাংশন/ক্লাস লেখা লাগে — Sorter-এর কোনো কোড বদলাতে হয় না।

প্র ০৩ একটি সিস্টেমে Observer ব্যবহার করলে সম্ভাব্য কোন বাস্তবিক ঝুঁকি থাকতে পারে, যদি কোনো observer কখনো unsubscribe না করে?

"memory leak"-এর ঝুঁকি থাকে — Subject observer-দের রেফারেন্স ধরে রাখে, তাই একটি observer আর দরকার না হলেও (যেমন একটি বন্ধ হয়ে যাওয়া UI window) unsubscribe না করলে সেটি মেমোরিতে থেকে যায় এবং প্রতিটি notification-এ অপ্রয়োজনীয়ভাবে কল হতে থাকে। বাস্তব সিস্টেমে এই কারণেই observer-দের জীবনচক্র সাবধানে ম্যানেজ করা (subscribe করলে অবশ্যই কোথাও unsubscribe করা) একটি গুরুত্বপূর্ণ practical discipline।

অনুশীলন

  1. চিন্তা করুন: একটি ই-কমার্স সাইটে "প্রোডাক্ট স্টক আউট হলে" ইভেন্টের জন্য কী কী observer থাকতে পারে তার একটি তালিকা করুন, এবং প্রতিটি কেন Subject-এর concrete টাইপ জানার দরকার নেই তা ব্যাখ্যা করুন।

    সম্ভাব্য observer: একটি RestockNotifier (সাপ্লায়ারকে ইমেইল পাঠায়), একটি WishlistNotifier (যেসব ইউজার প্রোডাক্টটি wishlist-এ রেখেছে তাদের জানায়), একটি AnalyticsLogger (স্টক-আউট ইভেন্ট ট্র্যাক করে রাখে)। Subject (প্রোডাক্ট ইনভেন্টরি সিস্টেম) শুধু "স্টক আউট হয়েছে" ইভেন্ট emit করে — কে বা কতজন এই ইভেন্টে রিঅ্যাক্ট করছে তা তার জানার দরকার নেই, প্রতিটি observer নিজেই তার নিজের প্রতিক্রিয়া লজিক ঠিক করে।

  2. পরীক্ষা করুন: Strategy কোড সেলে একটি তৃতীয় reverse_sort(data) স্ট্র্যাটেজি (বড় থেকে ছোট ক্রমে) যোগ করে Sorter-এ ব্যবহার করে Run করে দেখুন।

    def reverse_sort(data): return sorted(data, reverse=True) — এই ফাংশনটি bubble_sort/python_native_sort-এর মতোই একই সিগনেচার (data → list) মেনে চলে, তাই Sorter(reverse_sort) দিয়ে সরাসরি ব্যবহার করা যাবে — [5, 2, 9, 1, 5, 6]-এর জন্য ফলাফল হবে [9, 6, 5, 5, 2, 1], এবং Sorter ক্লাসের একটি লাইনও বদলাতে হয়নি।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
স্ট্রাকচারাল প্যাটার্ন — Adapter, Decorator, Facade