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ـدا؛ پێداچوونەوەی بە چاودێریکراو؛ بەراوردی تەنیشت یەک، یەک دەستلێدان کە بە ئەنیمەیشن بە ناو هەر سێکیاندا دەڕوات؛ شێوازەکان لە کوێی ؛ چۆن هەڵبژێریت؛ و هەڵە باوەکان وەک قەڵەو.

18 min read17 خولەک خوێندنەوە · 17 stepsهەنگاو

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.

سێ ناسراو ڕێکی دەخەن. هەریەکەیان شاشە دابەش دەکات بۆ (ئەوەی دەیبینێت) و ( و یاساکان) و بەشێکی جیاواز دەخاتە ناوەڕاستەوە: لە ، لە و لە .

Three panels. MVC: input goes to the controller; the controller updates the model and chooses the view; the model notifies the view with a dashed arrow. MVP: the view sends actions to the presenter, the presenter tells the view what to show and calls the model. MVVM: the view sends actions to the view model, the view model calls the model, and the view observes the view model (dashed arrow). Legend: a solid arrow means knows and calls; a dashed arrow only says I changed.سێ بەش. MVC: نێوخستە دەچێت بۆ کۆنترۆڵکەر؛ کۆنترۆڵکەر مۆدێل نوێ دەکاتەوە و دیمەن هەڵدەبژێرێت؛ مۆدێل بە تیرێکی خاڵخاڵ دیمەن ئاگادار دەکات. MVP: دیمەن کردارەکان دەنێرێت بۆ پێشکەشکەر، پێشکەشکەر بە دیمەن دەڵێت چی پیشان بدات و مۆدێل بانگ دەکات. MVVM: دیمەن کردارەکان دەنێرێت بۆ مۆدێلی دیمەن، مۆدێلی دیمەن مۆدێل بانگ دەکات و دیمەن چاودێریی مۆدێلی دیمەن دەکات (تیری خاڵخاڵ). ڕوونکردنەوە: تیری پڕ واتە دەیناسێت و بانگی دەکات؛ تیری خاڵخاڵ تەنیا دەڵێت «گۆڕام».
MVC, MVP and MVVM at a glance، و بە یەک نیگاCêIT

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)⁩ ناوی بیرۆکەی پشت هەر سێکیان دەنێت: جیاکردنەوەی پیشاندان. و یاساکان هەڵدەگرێت و نازانێت هیچ شاشەیەک هەیە. شاشەکان دەناسن و بانگی دەکەن. بۆیە یەک دەتوانێت لە یەک کاتدا خزمەتی ئەپێکی مۆبایل، و بکات و شاشەی نوێ هیچ گۆڕانێک لە ناوێت.

Three blue screens at the top (phone app, web page, terminal), each showing Total 12,000 IQD. Solid arrows go down from each screen to one model box (the business and data layers: OrderService and ProductRepository). Dashed arrows go back up. Bottom: the model never names a screen; a new screen needs no change in the model.سێ شاشەی شین لە سەرەوە (ئەپی مۆبایل، لاپەڕەتۆڕ، تێرمیناڵ)، هەریەکەیان Total 12,000 IQD پیشان دەدات. تیری پڕ لە هەر شاشەیەکەوە بەرەو خوار دەچێت بۆ یەک چوارگۆشەی مۆدێل (چینی لۆجیکی کاروبار و چینی دەستڕاگەیشتن بە دراوە: OrderService و ProductRepository). تیری خاڵخاڵ دەگەڕێنەوە سەرەوە. خوارەوە: مۆدێل هەرگیز ناوی شاشەیەک نابات؛ شاشەی نوێ هیچ گۆڕانێک لە مۆدێلدا ناوێت.
Many screens, one modelچەندین شاشە، یەک CêIT

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 دەڵێت زۆرترین ئاماژەی پێکراوی لە دروستکردنی ، هەروەها زۆرترین بە هەڵە باسکراویشە.

A timeline. 1978–79: Reenskaug at Xerox PARC writes the MVC notes. 1980s: the Smalltalk-80 class library uses MVC. 1996: MVP at Taligent (Mike Potel). 2004–05: MVC on the web with Rails and Django. 2005: MVVM at Microsoft (WPF). 2006: Fowler writes GUI Architectures. Bottom: names change; the idea stays: keep the screen apart from the rules.هێڵێکی کات. 1978–79: Reenskaug لە Xerox PARC نووسراوەکانی MVC دەنووسێت. 1980s: کتێبخانەی Smalltalk-80 MVC بەکار دەهێنێت. 1996: MVP لە Taligent (Mike Potel). 2004–05: MVC لە وێبدا بە Rails و Django. 2005: MVVM لە Microsoft (WPF). 2006: Fowler وتاری GUI Architectures دەنووسێت. خوارەوە: ناوەکان دەگۆڕێن؛ بیرۆکەکە دەمێنێت: شاشە لە یاساکان جیا بکەرەوە.
Four decades of one ideaچوار دەیە لە یەک بیرۆکەCêIT

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 ئەرکی هەر بەشێک دیاری دەکات. زانیاری نیشان دەدات: لێرەدا بەرهەمەکان و داواکارییەکە. وێنەیەکی بینراوی ، جۆرە فلتەرێکە کە هەندێک بەش پیشان دەدات و هەندێکی تر دەشارێتەوە؛ خۆی بە پرسیار لە وەردەگرێت. پەیوەندیی نێوان و سیستەمەکەیە: ڕێک دەخات، پێشکەش دەکات و دەکات بە پەیام.

Definitionپێناسە

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.

لە ، ئەو بەشەی ئەوەی دەیکات وەردەگرێت ( یان داوایەکی وێب)، داوا لە دەکات کارەکە بکات و هەڵدەبژێرێت کام پیشان بدرێت.

A person with mouse and keys sends input to the controller, the link between the user and the system. The controller tells the model to change it. The model, which represents knowledge (products, the order), sends a dashed I changed message down to the view. The view asks the model for data and draws the screen, Total 12,000 IQD; it is a presentation filter that draws part of the model. Bottom: each widget had its own controller and view; views observe the model.کەسێک بە ماوس و تەختەکلیل نێوخستە دەنێرێت بۆ کۆنترۆڵکەر، پەیوەندیی نێوان بەکارهێنەر و سیستەمەکە. کۆنترۆڵکەر بە مۆدێل دەڵێت بگۆڕێت. مۆدێل، کە زانیاری نیشان دەدات (بەرهەمەکان، داواکارییەکە)، پەیامی خاڵخاڵی «گۆڕام» دەنێرێت بۆ دیمەن. دیمەن داوای دراوە لە مۆدێل دەکات و شاشەکە دەکێشێت، Total 12,000 IQD؛ «فلتەرێکی پیشاندان»ـە کە بەشێک لە مۆدێل دەکێشێت. خوارەوە: هەر ویجێتێک کۆنترۆڵکەر و دیمەنی خۆی هەبوو؛ دیمەنەکان چاودێریی مۆدێل دەکەن.
Classic MVC in Smalltalk کلاسیک لە SmalltalkـداCêIT

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)⁩ بە هەمان شێوە کار دەکەن. داواکاریی یەک لاپەڕە پێنج هەنگاوە:

A browser sends GET /products?max_price=15000. Step 1: the request reaches the router (ROUTES). Step 2: the router calls the controller, products_controller. Step 3: the controller asks the model, Catalogue.up_to(), which reads SQLite and returns the products. Step 4: the controller passes them to the view, a template (PAGE and ITEM). Step 5: the HTML goes back to the browser: Up to 15,000 IQD per kg, Sumac: 8,000 IQD/kg, and so on.تۆڕگەڕێک GET /products?max_price=15000 دەنێرێت. هەنگاوی 1: داواکارییەکە دەگاتە دابەشکەری URL (ROUTES). هەنگاوی 2: دابەشکەرەکە کۆنترۆڵکەر بانگ دەکات، products_controller. هەنگاوی 3: کۆنترۆڵکەر لە مۆدێل دەپرسێت، Catalogue.up_to()، کە SQLite دەخوێنێتەوە و بەرهەمەکان دەگەڕێنێتەوە. هەنگاوی 4: کۆنترۆڵکەر دەیاندات بە دیمەن، قاڵبێک (PAGE و ITEM). هەنگاوی 5: HTMLـەکە دەگەڕێتەوە بۆ تۆڕگەڕ: Up to 15,000 IQD per kg، Sumac: 8,000 IQD/kg و هتد.
One web request through MVCیەک وێب بە ناو CêIT
  • 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() ئەوەیە کە بۆ هەر بانگی دەکات؛ لێرەدا خۆمان بانگی دەکەین:

Router, controller, model and view in 52 linesدابەشکەر، کۆنترۆڵکەر، مۆدێل و دیمەن لە ⁦52⁩ دێڕداmvc_web.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""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"))
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; Template itself 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 خۆیان لەسەر دروست کرد. لە جووڵەکانی وەردەگرێت و هەموویان دەنێرێت بۆ . قسە لەگەڵ دەکات و ڕێک بە دەڵێت چی پیشان بدات، لە ڕێی کە بەڵێن دەدات هەیبێت.

Definitionپێناسە

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.

لە ، ئەو بەشەی لە نێوان و ناچالاکدایە: هەموو ئەوەی دەیکات دەینێرێت بۆی و ئەویش لە ڕێی ڕێک پێی دەڵێت چی پیشان بدات. دەتوانرێت بە ساختە تاقی بکرێتەوە.

Left: a passive view, ConsoleCartView, showing Add (code grams): walnuts 500, Walnuts 500 g: 9,000 IQD, Total 12,000 IQD; it reads keys and shows only what it is told. Between the view and the presenter stands the CartView interface. An arrow on_add goes from the view to the presenter, CartPresenter with on_add(code, grams_text); an arrow show_total goes back to the view. The presenter calls the model, OrderService. Bottom: what the CartView interface promises: show_lines(lines), show_total(text), show_error(message).چەپ: دیمەنێکی ناچالاک، ConsoleCartView، کە پیشان دەدات Add (code grams): walnuts 500، Walnuts 500 g: 9,000 IQD، Total 12,000 IQD؛ دوگمەکان دەخوێنێتەوە و تەنیا ئەوە پیشان دەدات کە پێی دەوترێت. لە نێوان دیمەن و پێشکەشکەردا نێوبەستەی CartView هەیە. تیری on_add لە دیمەنەوە دەچێت بۆ پێشکەشکەر، واتە CartPresenter بە on_add(code, grams_text)؛ تیری show_total دەگەڕێتەوە بۆ دیمەن. پێشکەشکەر مۆدێل بانگ دەکات، OrderService. خوارەوە: ئەوەی نێوبەستەی CartView بەڵێنی دەدات: show_lines(lines)، show_total(text)، show_error(message).
MVP: the presenter speaks to the view through an interface: لە ڕێی قسە لەگەڵ دەکاتCêIT

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()ـی هەبێت دەگونجێت، بێ ئەوەی لێیەوە وەربگرێت (جۆرداریی مراوی). شێوەکە دەپشکنێت، لە وانەی پێشوو دەپرسێت و دەقی ئامادە دەداتە . کۆنسۆڵ تەنیا دێڕێک دەخوێنێتەوە و ئەوە چاپ دەکات کە پێی دەوترێت.

A presenter, an interface and a passive console viewپێشکەشکەر، نێوبەستە و دیمەنێکی ناچالاکی کۆنسۆڵmvp_cart.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""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))
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 دەکات، دەتوانێت ساختەی بداتێ کە ئەوەی پێی وتراوە لەبیری دەمێنێت. نە ، نە پەنجەرە، نە :

Three presenter tests with FakeCartViewسێ حاڵەتی تاقیکردنەوە بۆ پێشکەشکەر بە FakeCartViewtest_presenter.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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()
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8-21

The fake view offers the same three methods and stores what it receives.

ساختەکە هەمان سێ پێشکەش دەکات و ئەوەی وەریدەگرێت هەڵی دەگرێت.

CartPresenter at the top talks only to a CartView. A solid arrow goes to ConsoleCartView (the real program, prints on the screen) and a dashed arrow to FakeCartView (in unittest, remembers in lists, no screen). Bottom: the test then checks what the presenter told the view: view.total == "Total 12,000 IQD (delivery 3,000)".CartPresenter لە سەرەوە تەنیا قسە لەگەڵ CartView دەکات. تیرێکی پڕ دەچێت بۆ ConsoleCartView (بەرنامەی ڕاستەقینە، لەسەر شاشە چاپ دەکات) و تیرێکی خاڵخاڵ بۆ FakeCartView (لە unittestـدا، لە لیستدا هەڵی دەگرێت، بێ شاشە). خوارەوە: پاشان حاڵەتی تاقیکردنەوە دەپشکنێت پێشکەشکەر چی بە دیمەن گوتووە: view.total == "Total 12,000 IQD (delivery 3,000)".
A fake view makes the presenter testable ساختە تاقیکراو دەکاتCêIT

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 دەڵێت ناناسێت.

An observable view model in plain Pythonمۆدێلی دیمەنێکی چاودێریکراو بە Pythonـی سادەmvvm_cart.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""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()
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».

دوو بەشدار دەبن. هەریەکەیان کاتێک «گۆڕام» دەبیستێت، ئەو پێویستیەتی دەخوێنێتەوە.

Two views at the top, the cart screen (Walnuts 500 g, Total 12,000 IQD) and a badge (Total 12,000 IQD). The cart screen calls add on the view model, CartViewModel, which holds the state lines, total_text and error and the command add(code, grams_text). Dashed arrows labelled I changed go to every listener. The view model calls OrderService below. Note: the view model never holds a view.دوو دیمەن لە سەرەوە، شاشەی سەبەتە (Walnuts 500 g، Total 12,000 IQD) و نیشانەیەک (Total 12,000 IQD). شاشەی سەبەتە add لە مۆدێلی دیمەن بانگ دەکات، CartViewModel، کە دۆخی lines، total_text و error و فەرمانی add(code, grams_text) هەڵدەگرێت. تیری خاڵخاڵ بە ناوی «گۆڕام» دەچن بۆ هەموو گوێگرێک. مۆدێلی دیمەن OrderService لە خوارەوە بانگ دەکات. تێبینی: مۆدێلی دیمەن هەرگیز دیمەنێک هەڵناگرێت.
Views watch one view model چاودێریی یەک دەکەنCêIT

MVC, MVP and MVVM side by sideMVC، MVP و MVVM لە تەنیشت یەکەوە

The three patterns comparedبەراوردی سێ شێوازەکە
QuestionپرسیارMVCMVPMVVM
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⁩، RailsWindows 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⁩» کۆتایی بێت.

An animation in five frames with three columns. MVC column: controller, model, view. MVP and MVVM columns: view, presenter or view model, model. Frame 1: in MVC the controller gets the tap; in MVP and MVVM the view gets it. Frame 2: the controller tells the model to add a line; the MVP view calls on_add on the presenter; the MVVM view runs vm.add on the view model. Frame 3: the model applies the shop's rules (9,000 + 3,000); the presenter and the view model call price_order on the model. Frame 4: the MVC model says I changed to the view; the presenter calls show_total on the view; the view model updates its state and says I changed. Frame 5: the MVC view reads the model and draws, the MVP view shows exactly what it was told, the MVVM view reads total_text and redraws; every screen shows Total 12,000 IQD.
An animation in five frames with three columns. MVC column: controller, model, view. MVP and MVVM columns: view, presenter or view model, model. Frame 1: in MVC the controller gets the tap; in MVP and MVVM the view gets it. Frame 2: the controller tells the model to add a line; the MVP view calls on_add on the presenter; the MVVM view runs vm.add on the view model. Frame 3: the model applies the shop's rules (9,000 + 3,000); the presenter and the view model call price_order on the model. Frame 4: the MVC model says I changed to the view; the presenter calls show_total on the view; the view model updates its state and says I changed. Frame 5: the MVC view reads the model and draws, the MVP view shows exactly what it was told, the MVVM view reads total_text and redraws; every screen shows Total 12,000 IQD.
“Add 500 g walnuts” in MVC, MVP and MVVM«⁦500⁩ گرام گوێز زیاد بکە» لە ، و CêIT
  • 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.

ئەم شێوازانە چۆن لەگەڵ وانەی پێشوو دەگونجێن؟ و ، یان هەموویان بەشێکن لە . ئەوەی شێوازەکان پێی دەڵێن «» لەگەڵ لە ژێریەوە. بۆیە شێوازێک هەرگیز جێگەی ناگرێتەوە: کە کرێی گەیاندن دەژمێرێت، کارێکی گرتووە کە هی خوارەوەیەتی.

The user interface layer at the top contains three options: MVC (view and controller), MVP (view and presenter) and MVVM (view and view model). Below it are the business logic layer (services.py: OrderService) and the data access layer (repository.py: ProductRepository); a bracket labels these two the Model of all three patterns. Bottom: MVC, MVP and MVVM organise the screen; the rules stay in the business layer.چینی نێوبەستەی بەکارهێنەر لە سەرەوە سێ هەڵبژاردەی تێدایە: MVC (دیمەن و کۆنترۆڵکەر)، MVP (دیمەن و پێشکەشکەر) و MVVM (دیمەن و مۆدێلی دیمەن). لە ژێریەوە چینی لۆجیکی کاروبار (services.py: OrderService) و چینی دەستڕاگەیشتن بە دراوە (repository.py: ProductRepository) هەن؛ کەوانەیەک ئەم دووانە ناو دەنێت «مۆدێل»ـی هەر سێ شێوازەکە. خوارەوە: MVC، MVP و MVVM شاشە ڕێک دەخەن؛ یاساکان لە چینی لۆجیکی کاروباردا دەمێننەوە.
Patterns inside the UI layer; the model belowشێوازەکان لە ناو ؛ لە خوارەوەCê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:

لە کرداردا زۆرجار بڕیار دەدات، بۆیە ئەو شێوازە فێربە کە چاوەڕێی دەکات. کاتێک خۆت هەڵدەبژێریت، بپرسە چ جۆرە شاشەیەک دروست دەکەیت:

A decision chart with four questions. A tiny script or a one-off tool? Yes: no pattern needed, functions are enough. Pages built on a server, request by request? Yes: MVC (Laravel, Django, ASP.NET Core). A rich screen whose data changes, with observing? Yes: MVVM (Flutter, Android, .NET MAUI). Older UI code, no binding, every screen rule tested? Yes: MVP, a presenter and a passive view.نەخشەیەکی بڕیاردان بە چوار پرسیار. دەسنووسێکی بچووک یان ئامرازێکی یەکجاری؟ بەڵێ: شێواز پێویست نییە، کردارەکان بەسن. لاپەڕەکان لەسەر ڕاژەکار دروست دەبن، بۆ هەر داواکارییەک جارێک؟ بەڵێ: MVC (Laravel، Django، ASP.NET Core). شاشەیەکی دەوڵەمەند کە دراوەکەی دەگۆڕێت، بە چاودێری؟ بەڵێ: MVVM (Flutter، Android، .NET MAUI). کۆدی کۆنی نێوبەستەی بەکارهێنەر، بێ بەستنەوە، هەموو یاسایەکی شاشە تاقیکراو؟ بەڵێ: MVP، پێشکەشکەر و دیمەنی ناچالاک.
Choosing a patternهەڵبژاردنی شێوازێکCêIT
  • 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هەڵە باوەکان

Before: one products_controller box holds six jobs: read the request, SELECT (SQL), R1 round up to 250, R2 and R3 grams and stock, R4 delivery fee, build the HTML; it is marked with a cross. After: a thin products_controller only reads the request, calls the service and chooses the view; OrderService holds R1 to R4 and calls ProductRepository; a separate view uses a template; it is marked with a tick.پێش: یەک چوارگۆشەی products_controller شەش کار هەڵدەگرێت: خوێندنەوەی داواکاری، SELECT (SQL)، R1 نزیککردنەوە بۆ 250، R2 و R3 گرام و کۆگا، R4 کرێی گەیاندن، دروستکردنی HTML؛ نیشانەی × هەیە. دوای: products_controllerـێکی باریک تەنیا داواکاری دەخوێنێتەوە، خزمەتگوزاری بانگ دەکات و دیمەن هەڵدەبژێرێت؛ OrderService R1 تا R4 هەڵدەگرێت و ProductRepository بانگ دەکات؛ دیمەنێکی جیا قاڵب بەکار دەهێنێت؛ نیشانەی ✓ هەیە.
From a massive controller to a thin oneلە قەڵەوەوە بۆ یەکێکی باریکCêIT
Common mistake: the view model forgets to notify on errorsهەڵەی باو: مۆدێلی دیمەن لە کاتی هەڵەدا ئاگادارکردنەوە لەبیر دەکاتmvvm_forgot.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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)")

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ڕاهێنانەکان

ExerciseڕاهێنانWhich part is this?ئەمە کام بەشە؟

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)⁩ .

ExerciseڕاهێنانA JSON API with the same modelAPIـیەکی JSON بە هەمان مۆدێل

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وەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوmvc_api.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
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"))

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 کار بکات.

ExerciseڕاهێنانFrom presenter to view modelلە پێشکەشکەرەوە بۆ مۆدێلی دیمەن

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ـی سەرەوە ڕێک ئەم ئەنجامەیە.

ExerciseڕاهێنانChoose for three projectsبۆ سێ پڕۆژە هەڵبژێرە

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زاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

Dependency Injection: Handing Objects What They Needتێخستنی وابەستەیی: دانی ئەوەی ئۆبجێکتەکان پێویستیانە

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.

Appearanceڕووکار

Modeدۆخ
Styleشێواز
Coloursڕەنگەکان

Saved on this device only.تەنیا لەسەر ئەم ئامێرە پاشەکەوت دەکرێت.