Chapterبەش 4 · Lectureوانە 4.2
MVC, MVP and MVVM: Three Ways to Organise the ScreenMVC، MVP و MVVM: سێ ڕێگە بۆ ڕێکخستنی شاشە
Patterns for the UI layer: separated presentation; MVC from Reenskaug's 1979 notes at Xerox PARC to web frameworks, with a runnable router–controller–model–template page in Python; MVP with a presenter, a passive view behind a Protocol interface and a fake view in unittest; an MVVM recap with an observable view model; a side-by-side comparison, one tap animated through all three; where the patterns sit in the layers; how to choose; and common mistakes such as the massive controller.
شێوازەکان بۆ : جیاکردنەوەی پیشاندان؛ لە نووسراوەکانی 1979ـی Reenskaug لە Xerox PARCـەوە تا وێب، لەگەڵ لاپەڕەیەکی کارپێکراوی دابەشکەر، ، و بە Python؛ بە ، ناچالاک لە پشت Protocol و ساختە لە unittestـدا؛ پێداچوونەوەی بە چاودێریکراو؛ بەراوردی تەنیشت یەک، یەک دەستلێدان کە بە ئەنیمەیشن بە ناو هەر سێکیاندا دەڕوات؛ شێوازەکان لە کوێی ؛ چۆن هەڵبژێریت؛ و هەڵە باوەکان وەک قەڵەو.
What you will learnچی فێر دەبیت
- Explain separated presentation and what the model, view, controller, presenter and view model each do.
- Tell the story of MVC (Reenskaug, Xerox PARC, 1979) and follow a web request through router, controller, model and template.
- Write an MVP presenter behind a Protocol interface and test it with a fake view.
- Compare MVC, MVP and MVVM: input, screen updates, knowledge of the view and testability.
- Place the patterns inside the UI layer, choose one for a project and avoid the common mistakes.
- جیاکردنەوەی پیشاندان ڕوون بکەیتەوە و بڵێیت ، ، ، و هەریەکە چی دەکەن.
- چیرۆکی بگێڕیتەوە (Reenskaug، Xerox PARC، 1979) و وێب بە ناو دابەشکەر، ، و بەدوادا بچیت.
- لە پشت Protocolـەوە بنووسیت و بە ساختە تاقیی بکەیتەوە.
- ، و بەراورد بکەیت: ، نوێکردنەوەی شاشە، ناسینی و توانای تاقیکردنەوە.
- شێوازەکان لە ناو دابنێیت، یەکێکیان بۆ پڕۆژەیەک هەڵبژێریت و خۆت لە هەڵە باوەکان بپارێزیت.
The UI layer grows tooچینی نێوبەستەی بەکارهێنەریش گەورە دەبێت
In layers and business logic the UI was a short cli.py. A real cart screen does more: it lists the lines, keeps a running total, shows errors, enables or disables a button and redraws when something changes. That screen code needs a structure too, or it becomes the new tangle.
لە چینەکان و لۆجیکی کاروبار تەنیا cli.pyـێکی کورت بوو. شاشەیەکی ڕاستەقینەی سەبەتە زیاتر دەکات: دێڕەکان پیشان دەدات، کۆی گشتی نوێ دەکاتەوە، هەڵەکان پیشان دەدات، چالاک یان ناچالاک دەکات و کاتێک شتێک دەگۆڕێت دووپاتە دەیکێشێتەوە. ئەم کۆدەی شاشەش ڕێکخستنی دەوێت، دەنا دەبێتە تێکئاڵانێکی نوێ.
Three classic design patterns organise it. Each splits the screen into a view (what the user sees) and a model (the data and rules), and puts a different part in the middle: a controller in MVC, a presenter in MVP and a view model in MVVM.
سێ ناسراو ڕێکی دەخەن. هەریەکەیان شاشە دابەش دەکات بۆ (ئەوەی دەیبینێت) و ( و یاساکان) و بەشێکی جیاواز دەخاتە ناوەڕاستەوە: لە ، لە و لە .
The shared idea: the model knows nothing about screensبیرۆکەی هاوبەش: مۆدێل هیچ لە شاشەکان نازانێت
Martin Fowler's essay GUI Architectures (2006) names the idea behind all three: separated presentation. The model holds the data and the rules and does not know that any screen exists. Screens know the model and call it. So one model can serve a phone app, a web page and a terminal at the same time, and a new screen needs no change in the model.
وتاری GUI Architecturesـی Martin Fowler (2006) ناوی بیرۆکەی پشت هەر سێکیان دەنێت: جیاکردنەوەی پیشاندان. و یاساکان هەڵدەگرێت و نازانێت هیچ شاشەیەک هەیە. شاشەکان دەناسن و بانگی دەکەن. بۆیە یەک دەتوانێت لە یەک کاتدا خزمەتی ئەپێکی مۆبایل، و بکات و شاشەی نوێ هیچ گۆڕانێک لە ناوێت.
In this lesson the model is the business and data layers you built in the last lesson: OrderService with ProductRepository. The patterns differ in the part between the model and the view, and in who updates the screen.
لەم وانەیەدا هەمان و کە لە وانەی پێشوودا دروستت کرد: OrderService لەگەڵ ProductRepository. شێوازەکان لە بەشی نێوان و جیاوازن، هەروەها لەوەی کێ شاشەکە نوێ دەکاتەوە.
MVC: where it came fromMVC: لە کوێوە هات
MVC is older than the web. The Norwegian computer scientist Trygve Reenskaug worked as a visiting scientist in the Learning Research Group at Xerox PARC from 1978 to 1979, the group that built the Smalltalk system. On his own pages he tells the story: his first terms were Model–View–Editor; a note of 12 May 1979 is titled Thing-Model-View-Editor; and after long discussions, especially with Adele Goldberg, the names became Model–View–Controller in a two-page note of 10 December 1979.
لە وێب کۆنترە. زانای کۆمپیوتەری نەرویجی Trygve Reenskaug لە 1978 تا 1979 وەک زانای میوان لە گرووپی Learning Researchـی Xerox PARC کاری کرد، ئەو گرووپەی سیستەمی Smalltalkـی دروست کرد. لە لاپەڕەکانی خۆیدا چیرۆکەکە دەگێڕێتەوە: یەکەم ناوەکانی Model–View–Editor بوون؛ نووسراوێکی 12ی ئایاری 1979 ناونیشانی Thing-Model-View-Editorـە؛ دوای گفتوگۆی درێژ، بەتایبەت لەگەڵ Adele Goldberg، ناوەکان بوون بە Model–View–Controller لە نووسراوێکی دوو لاپەڕەیی 10ی کانوونی یەکەمی 1979ـدا.
He wrote that MVC exists to bridge the gap between the user's mental model and the digital model inside the computer. The MVC classes of the Smalltalk-80 library were written later by Jim Althoff and others, after Reenskaug had left PARC. Fowler calls MVC the most quoted pattern in UI development, and also the most misquoted.
نووسیویەتی کە بۆ ئەوەیە پردێک بێت لە نێوان وێنەی مێشکی و دیجیتاڵی ناو کۆمپیوتەر. کتێبخانەی Smalltalk-80 دواتر لەلایەن Jim Althoff و کەسانی ترەوە نووسران، دوای ئەوەی Reenskaug PARCـی بەجێهێشتبوو. Fowler دەڵێت زۆرترین ئاماژەی پێکراوی لە دروستکردنی ، هەروەها زۆرترین بە هەڵە باسکراویشە.
MVC: where it came fromMVC: لە کوێوە هات
Classic MVC: three jobsMVCـی کلاسیک: سێ کار
Reenskaug's note of December 1979 gives each part a job. The model represents knowledge: here the products and the order. A view is a visual representation of its model, a kind of filter that shows some parts and hides others; it gets its data by asking the model. The controller is the link between the user and the system: it arranges the views, offers menus and turns the user's commands into messages.
نووسراوەکەی کانوونی یەکەمی 1979ـی Reenskaug ئەرکی هەر بەشێک دیاری دەکات. زانیاری نیشان دەدات: لێرەدا بەرهەمەکان و داواکارییەکە. وێنەیەکی بینراوی ، جۆرە فلتەرێکە کە هەندێک بەش پیشان دەدات و هەندێکی تر دەشارێتەوە؛ خۆی بە پرسیار لە وەردەگرێت. پەیوەندیی نێوان و سیستەمەکەیە: ڕێک دەخات، پێشکەش دەکات و دەکات بە پەیام.
In , the part that receives the user's input (a click, a web request), asks the to do the work and chooses which to show.
لە ، ئەو بەشەی ئەوەی دەیکات وەردەگرێت ( یان داوایەکی وێب)، داوا لە دەکات کارەکە بکات و هەڵدەبژێرێت کام پیشان بدرێت.
In Smalltalk-80 each widget had its own view–controller pair, and views observed the model: when the model changed, it told its views and they redrew themselves (publish–subscribe). Fowler notes the cost: this implicit behaviour is hard to follow when you debug.
لە Smalltalk-80ـدا هەر جووتێکی و خۆی هەبوو و چاودێریی دەکرد: کاتێک دەگۆڕا، بە دەگوت و ئەوانیش خۆیان دووپاتە دەکێشایەوە (شێوازی بڵاوکردنەوە و بەشداربوون). Fowler تێچووەکەی دەڵێت: ئەم ڕەفتارە نادیارە کاتی دۆزینەوەی هەڵە بەدواداچوونی قورسە.
MVC on the webMVC لە وێبدا
Web frameworks took MVC's names and adapted them to requests. NeXT's WebObjects (1996) was an early one; Ruby on Rails (2004) and Django (2005) made the idea popular, and today ASP.NET Core MVC and Laravel (PHP) work the same way. One page request takes five steps:
وێب ناوەکانی وەرگرت و بۆ گونجاندیان. WebObjectsـی NeXT (1996) یەکێک لە یەکەمەکان بوو؛ Ruby on Rails (2004) و Django (2005) بیرۆکەکەیان بڵاو کردەوە و ئەمڕۆ ASP.NET Core MVC و Laravel (PHP) بە هەمان شێوە کار دەکەن. داواکاریی یەک لاپەڕە پێنج هەنگاوە:
- Router: maps the URL path to a controller (Laravel's
Route::get('/user/{id}', ...)). - Controller: reads the request, asks the model, chooses the view. Microsoft's guide warns: keep controllers small and push business logic into the model.
- View: a template that turns data into HTML, with as little logic as possible.
- دابەشکەری : دەبەستێتەوە بە (
Route::get('/user/{id}', ...)ـی Laravel). - : دەخوێنێتەوە، لە دەپرسێت، هەڵدەبژێرێت. ڕێنمایی Microsoft ئاگادار دەکاتەوە: بچووک ڕابگرە و بنێرە بۆ .
- : کە دەکات بە ، بە کەمترین لۆجیکی ممکن.
MVC on the webMVC لە وێبدا
A tiny web MVC in PythonMVCـی وێبی بچووک لە Pythonـدا
Real frameworks hide these parts behind a server, but they fit into one Python file. The model reuses the repository from the last lesson; the view is a string.Template; the controller and the router are plain functions. handle() is what a web server would call for every request; here we call it ourselves:
ئەم بەشانە لە پشت دەشارنەوە، بەڵام لە یەک Pythonـدا جێیان دەبێتەوە. وانەی پێشوو دووپاتە بەکار دەهێنێتەوە؛ string.Templateـە؛ و دابەشکەری سادەن. handle() ئەوەیە کە بۆ هەر بانگی دەکات؛ لێرەدا خۆمان بانگی دەکەین:
"""MVC on the web, small enough to read: router -> controller -> model -> view."""
from html import escape
from string import Template
from urllib.parse import parse_qs, urlsplit
from bazaar_cart.repository import ProductRepository, open_demo_db
# MODEL: the products and their rules; knows nothing about HTML or URLs.
class Catalogue(ProductRepository):
def up_to(self, max_price):
return [self.get(code) for (code,) in self.db.execute(
"SELECT code FROM products WHERE price_per_kg <= ? ORDER BY price_per_kg",
(max_price,))]
# VIEW: a template that turns data into HTML; no decisions, no SQL.
PAGE = Template("<h1>Up to $max IQD per kg</h1>\n<ul>\n$items</ul>")
ITEM = Template(" <li>$name: $price IQD/kg</li>\n")
def render_list(products, max_price):
items = "".join(ITEM.substitute(name=escape(p.name), price=f"{p.price_per_kg:,}")
for p in products)
return PAGE.substitute(max=f"{max_price:,}", items=items)
# CONTROLLER: reads the request, asks the model, chooses the view.
def products_controller(query):
text = query.get("max_price", ["100000"])[0]
if not text.isdigit():
return "400 Bad Request: max_price must be a number"
return render_list(catalogue.up_to(int(text)), int(text))
ROUTES = {"/products": products_controller} # the router: path -> controller
def handle(url):
parts = urlsplit(url)
controller = ROUTES.get(parts.path)
if controller is None:
return "404 Not Found"
return controller(parse_qs(parts.query))
catalogue = Catalogue(open_demo_db())
if __name__ == "__main__":
print(handle("/products?max_price=15000"))
print(handle("/products?max_price=cheap"))
print(handle("/basket"))<h1>Up to 15,000 IQD per kg</h1> <ul> <li>Sumac: 8,000 IQD/kg</li> <li>Dried figs: 12,000 IQD/kg</li> <li>Black tea: 14,000 IQD/kg</li> </ul> 400 Bad Request: max_price must be a number 404 Not Found
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 10-14
Model: one query added to the repository. It knows nothing about HTML or URLs.
: یەک زیاد کراوە بۆ . هیچ لەبارەی و نازانێت.
- Lineدێڕی 18-25
View: templates fill in
$placeholders.escape()makes names safe for HTML;Templateitself escapes nothing (XSS defences).: شوێنگرەکانی
$پڕ دەکەنەوە.escape()ناوەکان بۆ سەلامەت دەکات؛Templateخۆی هیچ ناگۆڕێت (بەرگری لە XSS).- Lineدێڕی 29-33
Controller: reads
max_price, refuses a bad value, asks the model and chooses the view.:
max_priceدەخوێنێتەوە، نرخی خراپ ڕەت دەکاتەوە، لە دەپرسێت و هەڵدەبژێرێت.- Lineدێڕی 36-44
Router: a dictionary from path to controller; unknown paths get «404 Not Found».
دابەشکەر: لە بۆ ؛ نەناسراو «404 Not Found» وەردەگرێت.
MVP: a presenter and a passive viewMVP: پێشکەشکەر و دیمەنێکی ناچالاک
In 1996 Mike Potel, chief technology officer of Taligent (by then owned by IBM), described Model–View–Presenter as a generalisation of Smalltalk's MVC for C++ and Java. After Taligent closed, the Dolphin Smalltalk team built their UI framework on it. In MVP the view receives the user's gestures and passes every one to the presenter. The presenter talks to the model and tells the view exactly what to show, through an interface the view promises to offer.
لە 1996ـدا Mike Potel، بەرپرسی تەکنەلۆجیای Taligent (کە ئەو کات هی IBM بوو)، Model–View–Presenterـی وەک گشتاندنێکی Smalltalk بۆ C++ و Java باس کرد. دوای داخرانی Taligent، تیمی Dolphin Smalltalk خۆیان لەسەر دروست کرد. لە جووڵەکانی وەردەگرێت و هەموویان دەنێرێت بۆ . قسە لەگەڵ دەکات و ڕێک بە دەڵێت چی پیشان بدات، لە ڕێی کە بەڵێن دەدات هەیبێت.
In , the part between the and a passive : the view passes every user action to it, and it tells the view exactly what to show through the view's . It can be tested with a fake view.
لە ، ئەو بەشەی لە نێوان و ناچالاکدایە: هەموو ئەوەی دەیکات دەینێرێت بۆی و ئەویش لە ڕێی ڕێک پێی دەڵێت چی پیشان بدات. دەتوانرێت بە ساختە تاقی بکرێتەوە.
Fowler describes two flavours. In a passive view the presenter does every update and the view has no logic at all, which makes the presenter fully testable. In a supervising controller the view handles simple data binding itself and the presenter handles the harder cases.
Fowler دوو جۆری باس دەکات. لە ناچالاکدا هەموو نوێکردنەوەیەک دەکات و هیچ لۆجیکێکی نییە، ئەمەش بە تەواوی تاقیکراو دەکات. لە supervising controllerـدا خۆی سادە دەکات و حاڵەتە قورسەکان.
MVP: a presenter and a passive viewMVP: پێشکەشکەر و دیمەنێکی ناچالاک
MVP in Python: CartPresenterMVP لە Pythonـدا: CartPresenter
The CartView interface is a typing.Protocol: any class with show_lines(), show_total() and show_error() fits, without inheriting from it (duck typing). The presenter checks the format, asks the service from the last lesson and gives the view finished text. The console view only reads a line and prints what it is told.
CartView typing.Protocolـە: هەر کە show_lines()، show_total() و show_error()ـی هەبێت دەگونجێت، بێ ئەوەی لێیەوە وەربگرێت (جۆرداریی مراوی). شێوەکە دەپشکنێت، لە وانەی پێشوو دەپرسێت و دەقی ئامادە دەداتە . کۆنسۆڵ تەنیا دێڕێک دەخوێنێتەوە و ئەوە چاپ دەکات کە پێی دەوترێت.
"""MVP: a passive view behind an interface; the presenter decides what it shows."""
from typing import Protocol
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService
class CartView(Protocol): # the interface every cart view must offer
def show_lines(self, lines: list[str]) -> None: ...
def show_total(self, text: str) -> None: ...
def show_error(self, message: str) -> None: ...
class CartPresenter:
def __init__(self, service, view: CartView):
self.service = service
self.view = view
self.items = []
def on_add(self, code, grams_text): # the view calls this for every "Add"
if not grams_text.isdigit():
self.view.show_error("Type the grams as a whole number.")
return
try:
order = self.service.price_order(self.items + [(code, int(grams_text))])
except ValueError as error: # a business rule said no
self.view.show_error(str(error))
return
self.items.append((code, int(grams_text)))
self.view.show_lines([f"{line.name} {line.grams} g: {line.price:,} IQD"
for line in order.lines])
self.view.show_total(f"Total {order.total:,} IQD (delivery {order.delivery:,})")
class ConsoleCartView: # passive: it reads keys and prints what it is told
def show_lines(self, lines):
for line in lines:
print(" " + line)
def show_total(self, text):
print(" " + text)
def show_error(self, message):
print(" ! " + message)
def run(self, presenter):
while command := input("Add (code grams), empty to stop: ").strip():
code, _, grams = command.partition(" ")
presenter.on_add(code, grams)
if __name__ == "__main__":
view = ConsoleCartView()
view.run(CartPresenter(OrderService(ProductRepository(open_demo_db())), view))Add (code grams), empty to stop: walnuts 500 Walnuts 500 g: 9,000 IQD Total 12,000 IQD (delivery 3,000) Add (code grams), empty to stop: walnuts half ! Type the grams as a whole number. Add (code grams), empty to stop: honey 5000 ! Honey: only 3,000 g in stock Add (code grams), empty to stop: sumac 350 Walnuts 500 g: 9,000 IQD Sumac 350 g: 3,000 IQD Total 15,000 IQD (delivery 3,000) Add (code grams), empty to stop:
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 8-11
The interface: three methods every cart view must have. Python checks it only with a type checker, not at run time.
: سێ کە هەموو سەبەتە دەبێت هەیبێت. Python تەنیا بە پشکنەری جۆر دەیپشکنێت، نەک لە کاتی کارکردندا.
- Lineدێڕی 20-32
The presenter decides everything the screen shows: the error text, the lines and the total. It never prints.
بڕیار لەسەر هەموو ئەوە دەدات کە شاشە پیشانی دەدات: دەقی هەڵە، دێڕەکان و کۆی گشتی. هەرگیز چاپ ناکات.
- Lineدێڕی 35-49
The view is passive: it reads a command, forwards it with
on_add()and prints what it is told.ناچالاکە: دەخوێنێتەوە، بە
on_add()دەینێرێت و ئەوە چاپ دەکات کە پێی دەوترێت.
MVP: a presenter and a passive viewMVP: پێشکەشکەر و دیمەنێکی ناچالاک
Testing a presenter with a fake viewتاقیکردنەوەی پێشکەشکەر بە دیمەنێکی ساختە
This is the point of MVP: because the presenter talks only to the CartView interface, a test can hand it a fake view that remembers what it was told. No terminal, no window, no clicking:
ئەمە مەبەستی : چونکە تەنیا قسە لەگەڵ CartView دەکات، دەتوانێت ساختەی بداتێ کە ئەوەی پێی وتراوە لەبیری دەمێنێت. نە ، نە پەنجەرە، نە :
import unittest
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService
from mvp_cart import CartPresenter
class FakeCartView:
"""Keeps what the presenter asked it to show; no screen at all."""
def __init__(self):
self.lines, self.total, self.errors = [], None, []
def show_lines(self, lines):
self.lines = lines
def show_total(self, text):
self.total = text
def show_error(self, message):
self.errors.append(message)
class CartPresenterTest(unittest.TestCase):
def setUp(self):
self.view = FakeCartView()
service = OrderService(ProductRepository(open_demo_db()))
self.presenter = CartPresenter(service, self.view)
def test_adding_walnuts_shows_the_line_and_the_total(self):
self.presenter.on_add("walnuts", "500")
self.assertEqual(self.view.lines, ["Walnuts 500 g: 9,000 IQD"])
self.assertEqual(self.view.total, "Total 12,000 IQD (delivery 3,000)")
def test_letters_for_grams_show_an_error(self):
self.presenter.on_add("walnuts", "half")
self.assertEqual(self.view.errors, ["Type the grams as a whole number."])
self.assertIsNone(self.view.total)
def test_a_broken_rule_shows_the_business_message(self):
self.presenter.on_add("honey", "5000")
self.assertEqual(self.view.errors, ["Honey: only 3,000 g in stock"])
if __name__ == "__main__":
unittest.main()test_a_broken_rule_shows_the_business_message (test_presenter.CartPresenterTest.test_a_broken_rule_shows_the_business_message) ... ok test_adding_walnuts_shows_the_line_and_the_total (test_presenter.CartPresenterTest.test_adding_walnuts_shows_the_line_and_the_total) ... ok test_letters_for_grams_show_an_error (test_presenter.CartPresenterTest.test_letters_for_grams_show_an_error) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 8-21
The fake view offers the same three methods and stores what it receives.
ساختەکە هەمان سێ پێشکەش دەکات و ئەوەی وەریدەگرێت هەڵی دەگرێت.
MVVM in one screenMVVM لە یەک شاشەدا
You met MVVM in the mobile course: MVC, MVP and MVVM, the view model does not know the view and, in practice, MVVM in Flutter with Provider. In short: the view model holds the screen's state, offers commands, and never holds a reference to a view. Views observe it and redraw when it says «I changed». It is close to what Fowler calls a Presentation Model, and Microsoft's .NET guide says the view model is unaware of the view.
لە خولی مۆبایلدا ناسی: MVC، MVP و MVVM، مۆدێلی دیمەن دیمەنەکەی ناناسێت و بە کرداریش MVVM لە Flutterـدا بە Provider. بە کورتی: شاشەکە هەڵدەگرێت، پێشکەش دەکات و هەرگیز ئاماژەیەک بۆ هەڵناگرێت. چاودێریی دەکەن و کاتێک دەڵێت «گۆڕام» دووپاتە دەیکێشنەوە. نزیکە لەوەی Fowler پێی دەڵێت Presentation Model و ڕێنمایی .NETـی Microsoft دەڵێت ناناسێت.
"""MVVM: the view model holds the screen's state and announces changes."""
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService
class CartViewModel:
def __init__(self, service):
self.service = service
self.items, self.lines = [], []
self.total_text, self.error = "Total 0 IQD", ""
self._listeners = []
def subscribe(self, listener): # a view asks to hear about changes
self._listeners.append(listener)
def add(self, code, grams_text): # a command the view can run
try:
order = self.service.price_order(self.items + [(code, int(grams_text))])
self.items.append((code, int(grams_text)))
self.lines = [f"{line.name} {line.grams} g" for line in order.lines]
self.total_text, self.error = f"Total {order.total:,} IQD", ""
except ValueError as error: # int("half") is a ValueError too
self.error = str(error)
for listener in self._listeners:
listener() # "I changed" - whoever listens redraws
def main():
vm = CartViewModel(OrderService(ProductRepository(open_demo_db())))
def cart_screen(): # view 1: draws everything from the state
warning = f" ! {vm.error}" if vm.error else ""
print("Cart:", ", ".join(vm.lines), "|", vm.total_text + warning)
vm.subscribe(cart_screen)
vm.subscribe(lambda: print("Badge:", vm.total_text)) # view 2: only the total
vm.add("walnuts", "500")
vm.add("honey", "5000")
if __name__ == "__main__":
main()Cart: Walnuts 500 g | Total 12,000 IQD Badge: Total 12,000 IQD Cart: Walnuts 500 g | Total 12,000 IQD ! Honey: only 3,000 g in stock Badge: Total 12,000 IQD
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6-25
The view model: state (
lines,total_text,error), one command (add) and a list of listeners. It never prints and never names a view.: (
lines،total_text،error)، یەک (add) و لیستێکی . هەرگیز چاپ ناکات و هەرگیز ناوی نابات.- Lineدێڕی 31-36
Two views subscribe. Each reads the state it needs when it hears «I changed».
دوو بەشدار دەبن. هەریەکەیان کاتێک «گۆڕام» دەبیستێت، ئەو پێویستیەتی دەخوێنێتەوە.
MVC, MVP and MVVM side by sideMVC، MVP و MVVM لە تەنیشت یەکەوە
| Questionپرسیار | MVC | MVP | MVVM |
|---|---|---|---|
| The part in the middleبەشی ناوەڕاست | |||
| Who receives the input first?یەکەمجار کێ وەردەگرێت؟ | The controller (on the web: the request) (لە وێبدا: ) | The view, which forwards it، کە دەینێرێت | The view, which runs a command، کە جێبەجێ دەکات |
| Who updates the screen?کێ شاشەکە نوێ دەکاتەوە؟ | The view, after the model says «I changed» (web: a new page)، دوای ئەوەی دەڵێت «گۆڕام» (وێب: لاپەڕەیەکی نوێ) | The presenter, through the view interface، لە ڕێی | The view, observing the view model، بە چاودێریکردنی |
| Does the middle part know the view?ئایا بەشی ناوەڕاست دەناسێت؟ | Yes, it chooses itبەڵێ، هەڵیدەبژێرێت | Yes, through an interfaceبەڵێ، لە ڕێی | Noنەخێر |
| Testing the middle part needs…تاقیکردنەوەی بەشی ناوەڕاست پێویستی بە… | a request and a model و | a fake view ساختە | nothing but its stateهیچ جگە لە |
| Where you meet itلە کوێ دەیبینیت | Laravel, Django, ASP.NET Core MVC, RailsLaravel، Django، ASP.NET Core MVC، Rails | Windows Forms and Java desktop code (Swing), test-heavy UIsکۆدی Windows Forms و دێسکتۆپی Java (Swing)، پڕ لە | Flutter, Android (Compose), .NET MAUI and WPFFlutter، Android (Compose)، .NET MAUI و WPF |
One tap, three journeysیەک دەستلێدان، سێ گەشت
Watch the same action, «Add 500 g walnuts», travel through each pattern. Walnuts cost 18,000 IQD per kg, so the line is 9,000 IQD and delivery 3,000 IQD: the screen must end at «Total 12,000 IQD».
سەیری هەمان کار بکە، «500 گرام گوێز زیاد بکە»، کە بە ناو هەر شێوازێکدا دەڕوات. گوێز کیلۆی 18,000 دینارە، بۆیە دێڕەکە 9,000 دینارە و گەیاندن 3,000 دینار: شاشەکە دەبێت بە «Total 12,000 IQD» کۆتایی بێت.


- MVC: the controller acts on the model; the model announces the change; the view pulls the new data.
- MVP: everything passes through the presenter, which pushes finished text into the view.
- MVVM: the view model changes its state and announces it; the view redraws itself from that state.
- : کار لەسەر دەکات؛ گۆڕانەکە ڕادەگەیەنێت؛ نوێ وەردەگرێت.
- : هەموو شت بە تێدەپەڕێت، کە دەقی ئامادە دەخاتە ناو .
- : دەگۆڕێت و ڕایدەگەیەنێت؛ خۆی لەو دووپاتە دەکێشێتەوە.
The patterns live in the UI layerشێوازەکان لە چینی نێوبەستەی بەکارهێنەردا دەژین
How do these patterns fit the layers of the last lesson? The view and the controller, presenter or view model are all part of the UI layer. What the patterns call the «model» is the business logic layer with the data access layer below it. So a pattern never replaces the business layer: a presenter that calculates the delivery fee has taken a job that belongs below it.
ئەم شێوازانە چۆن لەگەڵ وانەی پێشوو دەگونجێن؟ و ، یان هەموویان بەشێکن لە . ئەوەی شێوازەکان پێی دەڵێن «» لەگەڵ لە ژێریەوە. بۆیە شێوازێک هەرگیز جێگەی ناگرێتەوە: کە کرێی گەیاندن دەژمێرێت، کارێکی گرتووە کە هی خوارەوەیەتی.
Which one should I choose?کامیان هەڵبژێرم؟
In practice your framework usually decides, so learn the pattern it expects. When you do choose, ask what kind of screen you are building:
لە کرداردا زۆرجار بڕیار دەدات، بۆیە ئەو شێوازە فێربە کە چاوەڕێی دەکات. کاتێک خۆت هەڵدەبژێریت، بپرسە چ جۆرە شاشەیەک دروست دەکەیت:
- Web pages rendered on the server: MVC, the shape of Laravel, Django and ASP.NET Core MVC.
- Rich mobile or desktop screens: MVVM, which today's Android, Flutter and .NET guides recommend.
- UI code without binding that must be tested screen by screen: MVP with a passive view.
- A tiny script: none of them; one function that prints is fine.
- لاپەڕەی وێب کە لەسەر دروست دەبن: ، شێوەی Laravel، Django و ASP.NET Core MVC.
- شاشەی دەوڵەمەندی مۆبایل یان دێسکتۆپ: ، کە ڕێنمایییەکانی ئەمڕۆی Android، Flutter و .NET پێشنیاری دەکەن.
- کۆدی بێ بەستنەوە کە دەبێت شاشە بە شاشە تاقی بکرێتەوە: بە ناچالاک.
- بچووک: هیچیان؛ یەک کە چاپ دەکات بەسە.
Common mistakesهەڵە باوەکان
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService
from mvvm_cart import CartViewModel
class ForgetfulCartViewModel(CartViewModel):
def add(self, code, grams_text):
try:
order = self.service.price_order(self.items + [(code, int(grams_text))])
self.items.append((code, int(grams_text)))
self.total_text, self.error = f"Total {order.total:,} IQD", ""
for listener in self._listeners:
listener()
except ValueError as error:
self.error = str(error) # the state changed, but nobody is told
vm = ForgetfulCartViewModel(OrderService(ProductRepository(open_demo_db())))
vm.subscribe(lambda: print("Screen:", vm.total_text, vm.error))
vm.add("walnuts", "500")
vm.add("honey", "5000")
print("(the error is in vm.error:", repr(vm.error) + ", but the screen never showed it)")Screen: Total 12,000 IQD (the error is in vm.error: 'Honey: only 3,000 g in stock', but the screen never showed it)
The listeners are told only on success, inside the try. The honey error is stored but never shown, so the customer thinks nothing happened. Fix: notify once after the try/except, as CartViewModel.add() does on lines 24–25: every change of state, success or error, ends with «I changed».
تەنیا لە کاتی سەرکەوتندا ئاگادار دەکرێنەوە، لە ناو tryـدا. هەڵەی هەنگوین هەڵدەگیرێت بەڵام هەرگیز پیشان نادرێت، بۆیە کڕیار وا دەزانێت هیچ ڕووی نەداوە. چارەسەر: یەک جار دوای try/except ئاگادار بکەرەوە، وەک CartViewModel.add() لە دێڕی 24–25ـدا دەیکات: هەموو گۆڕانێکی ، سەرکەوتن بێت یان هەڵە، بە «گۆڕام» کۆتایی دێت.
Exercisesڕاهێنانەکان
Name the part (model, view, controller, presenter or view model) for each: (a) a function that reads ?max_price= from the URL and picks a template; (b) a class with show_total(text) that prints; (c) a class with total_text and subscribe() that never prints; (d) OrderService.price_order(); (e) a class that calls view.show_error().
بۆ هەریەکە ناوی بەشەکە بڵێ (، ، ، یان ): (a) کە ?max_price= لە دەخوێنێتەوە و هەڵدەبژێرێت؛ (b) بە show_total(text) کە چاپ دەکات؛ (c) بە total_text و subscribe() کە هەرگیز چاپ ناکات؛ (d) OrderService.price_order()؛ (e) کە view.show_error() بانگ دەکات.
Show answerوەڵامەکە پیشان بدە
(a) A web MVC controller. (b) A view (a passive MVP view). (c) A view model. (d) The model: business logic. (e) A presenter.
(a) وێب. (b) ( ناچالاکی ). (c) . (d) : . (e) .
Add a second controller to mvc_web.py for /api/products?max_price=… that returns JSON instead of HTML. Do not change Catalogue.
دووەم بۆ mvc_web.py زیاد بکە بۆ /api/products?max_price=… کە لە جیاتی بگەڕێنێتەوە. Catalogue مەگۆڕە.
Show answerوەڵامەکە پیشان بدە
import json
import mvc_web # the model, the router and the HTML controller from before
def api_products_controller(query): # same model, another view: JSON
text = query.get("max_price", ["100000"])[0]
if not text.isdigit():
return json.dumps({"error": "max_price must be a number"})
products = mvc_web.catalogue.up_to(int(text))
return json.dumps([{"name": p.name, "price_per_kg": p.price_per_kg} for p in products])
mvc_web.ROUTES["/api/products"] = api_products_controller
print(mvc_web.handle("/api/products?max_price=12000"))[{"name": "Sumac", "price_per_kg": 8000}, {"name": "Dried figs", "price_per_kg": 12000}]A new view (JSON) and a new controller; the model is reused untouched. The if __name__ == "__main__": guard in mvc_web.py keeps its demo from running on import.
نوێ () و نوێ؛ بێ دەستکاری دووپاتە بەکار دەهێنرێتەوە. پاسەوانی if __name__ == "__main__": لە mvc_web.pyـدا ناهێڵێت نموونەکەی لە کاتی import کار بکات.
Turn CartPresenter into a view model: what disappears, what is added, and how does the console view change?
CartPresenter بکە بە : چی نامێنێت، چی زیاد دەکرێت و کۆنسۆڵ چۆن دەگۆڕێت؟
Show answerوەڵامەکە پیشان بدە
The view parameter and every self.view.show_…() call disappear. Instead the class keeps state (lines, total_text, error), a subscribe() method and a list of listeners, and calls them at the end of add(). The console view subscribes and, when notified, reads the state and prints it. mvvm_cart.py above is exactly this result.
view و هەموو بانگکردنێکی self.view.show_…() نامێنن. لە جیاتیان هەڵدەگرێت (lines، total_text، error)، لەگەڵ subscribe() و لیستێکی . لە کۆتایی add()ـیشدا بانگیان دەکات. کۆنسۆڵ بەشدار دەبێت و کاتێک ئاگادار دەکرێتەوە، دەخوێنێتەوە و چاپی دەکات. mvvm_cart.pyـی سەرەوە ڕێک ئەم ئەنجامەیە.
Choose a pattern and give one reason: (a) Lana's school-timetable website, built with Laravel; (b) Hawler Ride's Flutter app for drivers; (c) a 15-line script that prints today's prices once.
شێوازێک هەڵبژێرە و یەک هۆ بڵێ: (a) خشتەی وانەکانی قوتابخانەی لانا، بە Laravel دروستکراو؛ (b) ئەپی Flutterـی Hawler Ride بۆ شۆفێرەکان؛ (c) 15 دێڕی کە یەک جار نرخەکانی ئەمڕۆ چاپ دەکات.
Show answerوەڵامەکە پیشان بدە
(a) MVC: Laravel routes each request to a controller that fills a view. (b) MVVM: a rich screen with changing data, and Flutter's guide uses views and view models. (c) None: there is no screen state to organise.
(a) : Laravel هەر دەنێرێت بۆ کە پڕ دەکاتەوە. (b) : شاشەیەکی دەوڵەمەند بە گۆڕاو و ڕێنمایی Flutter و بەکار دەهێنێت. (c) هیچیان: هیچ شاشە نییە بۆ ڕێکخستن.
Summaryپوختە
- All three patterns use separated presentation: the model (business + data layers) knows nothing about screens.
- MVC (Reenskaug, Xerox PARC, 1978–79): the controller takes input and changes the model; views observe the model. On the web: router → controller → model → template.
- MVP (Taligent, 1996): a passive view forwards gestures; the presenter tells it what to show through an interface, so a fake view can test it.
- MVVM (Microsoft, 2005): the view model holds state and announces changes; views observe it; it never holds a view.
- All of them live in the UI layer; rules stay in the business layer. Web pages → MVC; rich apps → MVVM; test-heavy UIs without binding → MVP; tiny scripts → none.
- هەر سێ شێوازەکە جیاکردنەوەی پیشاندان بەکار دەهێنن: ( و ) هیچ لەبارەی شاشەکان نازانێت.
- (Reenskaug، Xerox PARC، 1978–79): وەردەگرێت و دەگۆڕێت؛ چاودێریی دەکەن. لە وێبدا: دابەشکەر ← ← ← .
- (Taligent، 1996): ناچالاک جووڵەکان دەنێرێت؛ لە ڕێی پێی دەڵێت چی پیشان بدات، بۆیە ساختە دەتوانێت تاقیی بکاتەوە.
- (Microsoft، 2005): هەڵدەگرێت و گۆڕانەکان ڕادەگەیەنێت؛ چاودێریی دەکەن؛ هەرگیز هەڵناگرێت.
- هەموویان لە دەژین؛ یاساکان لە دەمێننەوە. لاپەڕەی وێب ← ؛ ئەپی دەوڵەمەند ← ؛ پڕ لە تاقیکردنەوە بێ بەستنەوە ← ؛ بچووک ← هیچیان.
Next: the presenter, the service and the repository were all handed what they need from outside. That idea has a name and its own lesson: dependency injection.
داهاتوو: ، و هەموویان ئەوەی پێویستیان بوو لە دەرەوە پێیان درا. ئەم بیرۆکەیە ناوێکی هەیە و وانەیەکی تایبەت بە خۆی: تێخستنی وابەستەیی.
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
Dependency Injection: Handing Objects What They Needتێخستنی وابەستەیی: دانی ئەوەی ئۆبجێکتەکان پێویستیانە
Related lecturesوانە پەیوەندیدارەکان
- App Architecture: the MVVM Patternتەلارسازیی نەرمەواڵە: شێوازی MVVM
- MVVM in Flutter with ProviderMVVM لە Flutterـدا بە Provider
- Organising an MVVM Project: Folders, Services and get_itڕێکخستنی پڕۆژەیەکی MVVM: فۆڵدەرەکان، خزمەتگوزارییەکان و get_it
- Dependency Injection: Handing Objects What They Needتێخستنی وابەستەیی: دانی ئەوەی ئۆبجێکتەکان پێویستیانە
- Building a CRUD Application with PHP and MySQLدروستکردنی بەرنامەیەکی CRUD بە PHP و MySQL
- Inheritance and Polymorphismبۆماوە و فرەشێوەیی
- Injection Attacks: SQL Injection and Cross-Site Scriptingهێرشەکانی تێخزاندن: تێخزاندنی SQL و XSS
- Automated Testing: Code That Checks Your Codeتاقیکردنەوەی خۆکار: کۆدێک کە کۆدەکەت دەپشکنێت
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- Martin Fowler: GUI Architectures (18 July 2006): Forms and Controls, MVC, Application Model, MVP, Humble View, Presentation Model
- Trygve Reenskaug: MVC, Xerox PARC 1978-79 (his own page, archived copy; the live university page returned 404 on 2026-11-10)
- Trygve Reenskaug: Models - Views - Controllers, note of 10 December 1979 (archived copy)
- Zenodo record: Reenskaug, Models–Views–Controllers (1979)
- Wikipedia: Model–view–controller (history, Smalltalk-80, WebObjects 1996, Rails 2004, Django 2005)
- Mike Potel: MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java (1996)
- Wikipedia: Model–view–presenter (Taligent, Dolphin Smalltalk, variants)
- Microsoft Learn: Overview of ASP.NET Core MVC (model, view and controller responsibilities; keep controllers small)
- Microsoft Learn: Model-View-ViewModel (.NET MAUI enterprise patterns)
- Django documentation 5.2: FAQ General (“Django appears to be a MVC framework…”, MTV)
- Laravel documentation: Controllers (routes to controller methods; Laravel 13.x is current as of November 2026)
- Android Developers: Guide to app architecture (UI layer, state holders, unidirectional data flow)
- The Elm Guide: The Elm Architecture (Model, View, Update)
- objc.io issue 13 (June 2014): Architecting iOS Apps with VIPER
- Python 3.13 documentation: typing.Protocol
- Python 3.13 documentation: string.Template
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.