Az előző két bejegyzésben az MVC architektúrára épülő programfelépítést szemléltettük. Elsőként egy nem grafikus felhasználói felületű alkalmazásban, majd pedig grafikus felhasználói felületet (GUI) kínáló alkalmazásban. Mindkét esetben a megfigyelő programtervezési mintát (observer design pattern) alkalmaztuk a megjelenítést végző objektumok értesítésére, hogy a modell állapota megváltozott, és ennek megfelelően módosuljon a megjelenítés.
Most ugyanezt a mintapéldát fogjuk módosítani úgy, hogy a modell állapotváltozásának GUI-n történő megjelenítésének kezdeményezését nem a megfigyelő programtervezési mintával érjük el, hanem ezt a feladatot a Controller objektum fogja végezni. Látjuk majd, hogy nem kell sok módosítást végezni, legtöbb esetben törölni kell kódokat.
Ahogy az előző bejegyzésben, a könnyebb áttekinthetőség érdekében most is külön modulokba szerveztük az MVC egyes összetevőit (Model, View és Contoller objektumokat) rendre a model.py, view_gui.py és main_controller.py modulokba.
A model.py tartalma meglehetősen leegyszerűsödik, mert minthogy nem alkalmazzuk a megfigyelő mintát, így a Publisher és Subscriber osztályokra nincs szükség, és ennek megfelelően a Car osztály accelerate() metódusában az értesítést végző notify_subscribers() hívás sem kell már. A model.py ezek után megmaradt tartalma látható alább:
|
1 2 3 4 5 6 7 8 9 10 11 |
# modul: model class Car: def __init__(self): self.speed: float = 0 def accelerate(self, delta_speed: float): self.speed += delta_speed if self.speed < 0: self.speed = 0 |
Szintén csak törlést kell végezni a view_gui.py modul kódjaiban. Mivel már nem használjuk a Subscriber osztályt, ezért ennek importálására nincs szükség. Továbbá a megjelenítéseket végző CarSpeedView1 és CarSpeedView2 osztályok sem kell, hogy örököljenek a Subscriber osztályból, és így az update_view() metódusokra sincs szükség. Az így módosított view_gui.py modul teljes kódja tehát ez lesz:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# modul: view_gui import tkinter as tk class CarSpeedView1(tk.Frame): """Az aktuális sebességet vizuálisan jeleníti meg egy értékskálán.""" def __init__(self, master): super().__init__(master, bg='black') self.carspeedview1_var = tk.StringVar(master, '') self.scale = tk.Scale(self, orient=tk.HORIZONTAL, label='Sebesség km/h', variable=self.carspeedview1_var, from_=0, to=150, resolution=1, tickinterval=30, sliderlength=5, width=30, fg='blue', font=('Segoe UI', 10, 'bold')) self.scale.pack(fill=tk.X, padx=1, pady=1) def show(self, speed): self.carspeedview1_var.set(speed) class CarSpeedView2(tk.Frame): """Az aktuális sebességet szövegesen jeleníti meg, és figyelmezet akkor, ha a megadott sebességet túllépjük.""" def __init__(self, master): super().__init__(master, bg='black') self.speed_limit = 130 self.carspeedview2_var = tk.StringVar(master, '\n') self.lbl2 = tk.Label(self, bg='white', textvariable=self.carspeedview2_var, font=('Calibre', 10, 'bold')) self.lbl2.pack(fill=tk.BOTH, padx=1, pady=1) def show(self, speed): txt = f"A sebességed {speed} km/h" warning_text = '' if speed > self.speed_limit: warning_text = f'Túllépted a megengedett maximális {self.speed_limit} km/h sebességet!' self.carspeedview2_var.set('\n'.join((txt, warning_text))) class DataInputGUI(tk.Frame): def __init__(self, master): super().__init__(master) self.controller = None self.accelerate_var = tk.StringVar(master, '15') self.accelerate_entry = tk.Entry(self, textvariable=self.accelerate_var, font=('Consolas', 12, 'bold'), width=5) self.accelerate_entry.pack(side=tk.LEFT) self.accelerate_button = tk.Button(self, text='Accelerate', command=self.process_input, repeatinterval=400, repeatdelay=400) self.accelerate_button.pack(side=tk.LEFT) def set_controller(self, control_obj): self.controller = control_obj def process_input(self): self.controller.change_model_state(self.accelerate_var.get()) class MainWindow(tk.Tk): def __init__(self): super().__init__() self.title('MVC') self.geometry('400x200') self.input_gui = DataInputGUI(self) self.views = [CarSpeedView1(self), CarSpeedView2(self)] self.input_gui.pack() for view in self.views: view.pack(fill=tk.BOTH, padx=5, pady=5) def set_controller(self, controller): self.input_gui.set_controller(controller) def run(self): self.mainloop() |
Az egyetlen, ahol a törlésen kívül kódbeírásra, illetve módosításra van szükség az a main_controller.py modul, azon belül is a Controller osztály definíciója.
Először is törölni kell az __init__ metóduson belül „A megfigyelő View példányok felvétele a modell mint megfigyelt objektum értesítési listájára.” kommenttel jelzett kódsorokat. Helyette itt felveszünk egy privát attribútumot, amely a GUI főobjektumot, annak referenciáját tárolja. Másrészt a change_model_state() metódust kiegészítjük olyan kódsorokkal, amelyek azt biztosítják, hogy a modell állapotváltozását követően (azaz a Car példány sebességének megváltozása után) a megváltozott állapot (sebesség) megjelenjen a GUI-n. Ez lényegében a megjelenítésért felelős view objektumok show() metódusának meghívását jelenti az új sebességgel mint argumentummal. /Az egyszerűség kedvéért a modell állapotát, a sebességet, nem tulajdonságon (property) keresztül, vagy más getter metódussal, hanem közvetlenül kérjük ki./
Ezen elvek alapján a main_controller.py modul módosult kódja a következő:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# modul: main_controller from view_gui import MainWindow from model import Car class Controller: def __init__(self, gui: MainWindow, car: Car): self._model = car self._view = gui def change_model_state(self, delta_speed): """ Kezdeményezi a modell állapotváltozását, és utána intézkedik a megváltozott állapot megjelenítéséről. """ # Az autó gyorsítása a modellben. self._model.accelerate(float(delta_speed)) # A megváltozott sebesség megjelenítése a grafikus # felület minden erre szolgáló objektumával. for view in self._view.views: view.show(self._model.speed) # Alkalmazás indítása. main_window = MainWindow() main_window.set_controller(Controller(main_window, Car())) main_window.run() |
A main_controller.py futtatásával kapott eredmények megegyeznek az előző bejegyzésben szereplő programváltozatáéval, így az ott látható épernyőkép sorozat hasonló módon most is előállítható:

Látva az előző bejegyzésben szereplő programkódokhoz képesti jelentős egyszerűsítéseket, esetleg arra a következtetésre juthatunk, hogy nem érdemes a megfigyelő mintát alkalmazni, hiszen csak bonyolítja a kódot. Egy ilyen konklúzió azonban nem lenne helyes. Ne felejtsük el, hogy a bemutatott program csak egy nagyon egyszerű, illusztrációs célú példa az MVC alapelvének és kialakításának bemutatására. Tényleges alkalmazásfejlesztéskor annak konkrét sajátosságai figyelembevételével (pl. a modell és a GUI összetettsége) kell meghozni a döntést, hogy a megjelenítő objektumok értesítését megfigyelő mintával vagy anélkül valósítjuk meg.
E bejegyzés témájához a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak: az „Osztály vigyázz! – típuslétrehozás osztályokkal” fejezet, a „Panelprogram – modulok” fejezet, valamint a „Grafikus felhasználói felület készítése” fejezet.