পাঠ ০৬ · ৫৮-এর মধ্যে · মডিউল ২
Home / Courses / Full-Stack Web Frameworks / MVVM ও MVP প্যাটার্ন

MVVM ও MVP প্যাটার্ন

MVVM and MVP patterns
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • MVVM-এর ViewModel কীভাবে বাইন্ডেবল স্টেট এক্সপোজ করে এবং View তা কীভাবে অবজার্ভ করে
  • MVP-এর Presenter কীভাবে সরাসরি একটি View ইন্টারফেস ম্যানিপুলেট করে
  • MVC, MVVM, MVP — তিনটির মধ্যে মূল পার্থক্য কোথায় (Controller/ViewModel/Presenter-এর ভূমিকা)
  • Python দিয়ে একটি সত্যিকারের কার্যকর Observable প্রপার্টি ও একটি Protocol-ভিত্তিক View ইন্টারফেস

১ · MVC থেকে MVVM ও MVP কেন আলাদা

L05-এ আমরা দেখেছি MVC-তে Controller রিকোয়েস্ট গ্রহণ করে Model থেকে ডেটা এনে View-কে দিয়ে রেন্ডার করায় — প্রতিটি রিকোয়েস্টে Controller-ই সিদ্ধান্ত নেয় View কী দেখাবে। MVVM ও MVP একই "ডেটা ও প্রেজেন্টেশন আলাদা রাখা" লক্ষ্য অর্জন করে, কিন্তু মাঝের অংশ ও View-এর সাথে তার সম্পর্ক ভিন্নভাবে সংগঠিত করে — বিশেষত যেখানে View-এর অবস্থা বারবার, স্বয়ংক্রিয়ভাবে বদলাতে হয় (যেমন একটি ডেস্কটপ বা মোবাইল UI)।

MVC
Controller রিকোয়েস্ট প্রতি একবার Model → View সমন্বয় করে, রেসপন্স ফেরত দেয়। View সাধারণত "প্যাসিভ" — যা দেওয়া হয় তাই রেন্ডার করে।
MVVM
ViewModel বাইন্ডেবল স্টেট এক্সপোজ করে; View সেই স্টেট অবজার্ভ করে ও স্টেট বদলালেই নিজে থেকে re-render হয় — ViewModel View-এর অস্তিত্ব সম্পর্কে জানে না।
MVP
Presenter একটি View ইন্টারফেস সরাসরি ধরে রাখে ও তার মেথড সরাসরি কল করে ("এখন এটা দেখাও") — View সম্পূর্ণ প্যাসিভ, নিজে কিছু সিদ্ধান্ত নেয় না।

২ · MVVM — বাইন্ডেবল স্টেট ও অবজার্ভিং View

নিচের কোড সেলে একটি Observable ক্লাস তৈরি করা হয়েছে যা একটি মান ধারণ করে ও মান বদলালে সব সাবস্ক্রাইব করা লিসেনারকে জানায়। CounterViewModel একটি বাইন্ডেবল count প্রপার্টি এক্সপোজ করে, আর CounterView সেই প্রপার্টি "অবজার্ভ" করে — count বদলালেই স্বয়ংক্রিয়ভাবে নিজেকে re-render করে, কোথাও সরাসরি view.render() কল না করেই।

Python
# ---------- একটি বাইন্ডেবল (observable) প্রপার্টি ----------
class Observable:
    def __init__(self, initial_value):
        self._value = initial_value
        self._listeners = []

    def get(self):
        return self._value

    def set(self, new_value):
        old_value = self._value
        self._value = new_value
        if old_value != new_value:
            for listener in self._listeners:
                listener(new_value)

    def subscribe(self, listener):
        self._listeners.append(listener)


# ---------- VIEWMODEL -- বাইন্ডেবল স্টেট এক্সপোজ করে, View সম্পর্কে কিছুই জানে না ----------
class CounterViewModel:
    def __init__(self):
        self.count = Observable(0)

    def increment(self):
        self.count.set(self.count.get() + 1)

    def reset(self):
        self.count.set(0)


# ---------- VIEW -- ViewModel-এর স্টেট অবজার্ভ করে, স্টেট বদলালেই re-render হয় ----------
class CounterView:
    def __init__(self, view_model):
        self.view_model = view_model
        self.render_count = 0
        self.view_model.count.subscribe(self._on_count_changed)   # অবজার্ভ করা শুরু
        self._render(self.view_model.count.get())                # প্রাথমিক রেন্ডার

    def _on_count_changed(self, new_count):
        self._render(new_count)

    def _render(self, count_value):
        self.render_count += 1
        print(f"[View re-render #{self.render_count}] বর্তমান কাউন্ট: {count_value}")


print("== MVVM ডেমো ==")
vm = CounterViewModel()
view = CounterView(vm)   # প্রাথমিক রেন্ডার -- re-render #1

vm.increment()            # count: 0 -> 1, re-render #2
vm.increment()            # count: 1 -> 2, re-render #3
vm.reset()                 # count: 2 -> 0, re-render #4
vm.reset()                 # count: 0 -> 0, মান অপরিবর্তিত -- re-render হয় না

print(f"\nমোট re-render হয়েছে: {view.render_count} বার")

    
লক্ষ্য করুন CounterViewModel-এর ভেতরে কোথাও CounterView-এর কোনো উল্লেখ নেই — ViewModel শুধু Observable স্টেট এক্সপোজ করে, কে সেটা অবজার্ভ করছে তা নিয়ে মাথা ঘামায় না। শেষ reset() কলে মান 0-ই থেকে যায় (আগে থেকেই 0), তাই Observable.set()-এর old_value != new_value চেক ব্যর্থ হয় ও কোনো নোটিফিকেশন যায় না — সেজন্যই মোট re-render সংখ্যা ৫ নয়, ৪।

৩ · MVP — Presenter সরাসরি View ইন্টারফেস ম্যানিপুলেট করে

MVVM-এর বিপরীতে MVP-তে View কোনো স্টেট "অবজার্ভ" করে না — বরং Presenter একটি View ইন্টারফেসের রেফারেন্স সরাসরি ধরে রাখে এবং তার মেথড সরাসরি কল করে "কী দেখাতে হবে" নির্দেশ দেয়। নিচের কোডে TodoViewInterface একটি Protocol (একটি চুক্তি — কোন মেথড থাকতে হবে তার সংজ্ঞা), ConsoleTodoView তার একটি concrete বাস্তবায়ন, ও TodoPresenter সেই ইন্টারফেস ধরে সরাসরি মেথড কল করে।

Python
from typing import Protocol

# ---------- View ইন্টারফেস -- Presenter এই "চুক্তি"-র বিরুদ্ধে কোড লেখে ----------
class TodoViewInterface(Protocol):
    def show_todos(self, todos: list) -> None: ...
    def show_error(self, message: str) -> None: ...


# ---------- VIEW -- ইন্টারফেসের একটি concrete বাস্তবায়ন ----------
class ConsoleTodoView:
    def __init__(self):
        self.last_shown = None

    def show_todos(self, todos):
        self.last_shown = todos
        if not todos:
            print("View আউটপুট: (কোনো টুডু নেই)")
        else:
            for t in todos:
                print(f"View আউটপুট: - {t}")

    def show_error(self, message):
        self.last_shown = None
        print(f"View আউটপুট (এরর): {message}")


# ---------- PRESENTER -- View-এর রেফারেন্স সরাসরি ধরে রাখে ও সরাসরি মেথড কল করে ----------
class TodoPresenter:
    def __init__(self, view: TodoViewInterface):
        self.view = view
        self._todos = []

    def load_todos(self, raw_titles):
        if not raw_titles:
            self.view.show_error("কোনো টুডু পাওয়া যায়নি")   # Presenter সরাসরি View-কে নির্দেশ দেয়
            return
        self._todos = list(raw_titles)
        self.view.show_todos(self._todos)                     # Presenter সরাসরি View-কে নির্দেশ দেয়


print("== MVP ডেমো ==")
console_view = ConsoleTodoView()
presenter = TodoPresenter(console_view)

presenter.load_todos(["দুধ কেনা", "রিপোর্ট জমা দেওয়া"])
presenter.load_todos([])

print(f"\nView-এ সর্বশেষ যা দেখানো হয়েছে: {console_view.last_shown}")

    
TodoPresenter.load_todos([]) কল করলে raw_titles খালি লিস্ট হওয়ায় if not raw_titles সত্য হয়, তাই Presenter সরাসরি self.view.show_error(...) কল করে — ফলে console_view.last_shown শেষ পর্যন্ত None-ই থাকে (এরর দেখানোর সময় সেট করা হয়)। MVVM-এর মতো এখানে কোনো "অবজার্ভ" বা স্বয়ংক্রিয় নোটিফিকেশন নেই — Presenter নিজেই সিদ্ধান্ত নিয়ে সরাসরি View-এর মেথড কল করে।
মূল কথা · Key takeaway

MVC, MVVM, MVP তিনটিই ডেটা ও প্রেজেন্টেশন আলাদা রাখে, কিন্তু মাঝের স্তরটি View-এর সাথে ভিন্নভাবে সম্পর্কিত হয় — MVC-তে Controller একবারের জন্য Model → View সমন্বয় করে; MVVM-এ ViewModel বাইন্ডেবল স্টেট এক্সপোজ করে যা View নিজে অবজার্ভ করে; MVP-তে Presenter সরাসরি View ইন্টারফেসের মেথড কল করে নির্দেশ দেয়। কোন প্যাটার্ন কোন ফ্রেমওয়ার্কে বেশি দেখা যায় তা মূলত সেই ফ্রেমওয়ার্কের ডেটা-বাইন্ডিং সক্ষমতার উপর নির্ভর করে।

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

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

প্র ০১ MVVM ডেমোতে মোট চারবার set() কল করা হলেও কেন re-render হয়েছে মাত্র ৪ বার নয় (initial render সহ মোট ৫ বার আশা করলে) — আসলে ঠিক কতবার ও কেন?

মোট re-render হয়েছে ৪ বার: প্রাথমিক রেন্ডার (View তৈরির সময়) + increment()-এর দুটো কল + প্রথম reset()। শেষ reset() কলে মান আগে থেকেই 0 ছিল, তাই Observable.set()-এর ভেতরের old_value != new_value শর্ত মিথ্যা হয়, কোনো লিসেনার কল হয় না, এবং re-render সংখ্যা বাড়ে না — এটাই দেখায় Observable শুধু প্রকৃত পরিবর্তনে নোটিফাই করে, প্রতিটি set() কলে নয়।

প্র ০২ MVP ডেমোতে TodoViewInterface-কে সরাসরি ইনস্ট্যান্স না করে শুধু Protocol হিসেবে সংজ্ঞায়িত করার লাভ কী?

Protocol শুধু একটি চুক্তি (contract) সংজ্ঞায়িত করে — কোন মেথড থাকতে হবে তা বলে, কিন্তু বাস্তবায়ন চাপায় না। ফলে TodoPresenter যেকোনো ক্লাসের সাথে কাজ করতে পারে যতক্ষণ সেটি show_todos ও show_error মেথড রাখে — টেস্টের জন্য একটি ভুয়া/মক View, বা বাস্তবে একটি GUI-ভিত্তিক View, দুটোই একই Presenter কোড দিয়ে চালানো যায়, Presenter-এর কোনো পরিবর্তন ছাড়াই।

প্র ০৩ MVC-এর Controller ও MVP-এর Presenter — দুটোই "মাঝের" অংশ যা Model ও View সমন্বয় করে। তাহলে এদের মধ্যে মূল পার্থক্য কী?

MVC-এর Controller সাধারণত একটি রিকোয়েস্ট-প্রতি একবার চলে, ফলাফল View-কে "দিয়ে দেয়" এবং একটি রেসপন্স ফেরত দেয় — View সাধারণত সেই ডেটা প্যাসিভভাবে রেন্ডার করে একবারই। MVP-এর Presenter সাধারণত একটি দীর্ঘস্থায়ী (long-lived) View অবজেক্টের রেফারেন্স ধরে রাখে ও প্রয়োজনে বারবার সরাসরি তার মেথড কল করে (যেমন ইউজার ইন্টারঅ্যাকশনের প্রতিক্রিয়ায়) — এটি একবারের "রেন্ডার ও শেষ" এর বদলে চলমান, ইন্টারঅ্যাক্টিভ UI-এর জন্য বেশি উপযোগী।

অনুশীলন

  1. চিন্তা করুন: যদি CounterView ViewModel-এর count.subscribe(...) কখনো কল না করত, তাহলে vm.increment() কল করার পর View-তে কী হতো (বা হতো না), এবং কেন?

    increment() তখনও count.set(...) কল করত ও ViewModel-এর ভেতরের মান বদলে যেত, কিন্তু Observable._listeners লিস্ট খালি থাকায় কোনো লিসেনার কল হতো না — অর্থাৎ View স্ক্রিনে কিছুই বদলাত না, যদিও ভেতরের ডেটা ঠিকই বদলেছে। এটাই দেখায় MVVM-এ "স্বয়ংক্রিয় re-render" আসলে সাবস্ক্রিপশনের উপর নির্ভরশীল — জাদু নয়, বরং একটি সত্যিকারের রেজিস্টার করা কলব্যাক।

  2. পরীক্ষা করুন: MVP কোড সেলে ConsoleTodoView-এর একটি বিকল্প SilentTestView ক্লাস লিখুন যা print না করে শুধু self.last_shown সেট করে, তারপর সেটি TodoPresenter-এ পাস করে দেখুন একই Presenter কোড কোনো পরিবর্তন ছাড়াই কাজ করে কি না।

    class SilentTestView: ক্লাসে show_todos(self, todos): self.last_shown = todos ও show_error(self, message): self.last_shown = None লিখে TodoPresenter(SilentTestView()) দিয়ে চালালে একই load_todos(...) কলগুলো ঠিকঠাক কাজ করে, শুধু কনসোলে কিছু প্রিন্ট হয় না — এটাই Protocol-ভিত্তিক View ইন্টারফেসের মূল সুবিধা, Presenter-এর কোড একদম অপরিবর্তিত থাকে।

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

  • পরবর্তী পাঠ L07 REST আর্কিটেকচারাল প্রিন্সিপল — স্টেটলেসনেস, ইউনিফর্ম ইন্টারফেস ও ক্যাশেবিলিটি দেখুন।
  • আগের পাঠ L05 MVC আর্কিটেকচার প্যাটার্ন — এই পাঠের তুলনার ভিত্তি।
  • Python Programming কোর্স সহোদর কোর্স উপরের কোডে ব্যবহৃত typing.Protocol ও ক্লাসের ভাষাগত ভিত্তি সেই কোর্সে শেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।
আগের পাঠ
MVC আর্কিটেকচার প্যাটার্ন