Eida.cz - Opravdové MVC ve Swingu - sny a realita

Opravdové MVC ve Swingu - sny a realita

Eida

Občas mám místo Javy statické okno do duše. Bývala doba, na jednu stranu opravdu úžasná doba, kdy jsme trávili dlouhé kouzelné večery ve společnosti Javy a na našich prvních pevných připojeních bezstarostně prosmáli u ICQ mnohdy i celé noci, osudově připoutáni chapadly k obrazovkám. Vzhledem k podobnému období a navýšené intenzity, co ponovala venku i teď v prosinci, to byla ta doba pokřivených Vánoc, doba okamžiků plných neomluvitelných chyb, výběru falešných hrdinů, nebo třeba i hřejivých chvilek s Karkulkou. Tehdá jsme tušili, co dnes ví už jen Bůh, uvěřili jsme mezi jiným i Sidově propagandě, že Java je free a že je možné v ní tvořit i poezii, přijde-li se v optimálním stavu odpoledne z potížemi zatíženého oběda. Když ale bylo po konečné bitvě a z bludů přišlo prozření, naše názory se rozešly a víra se rozstříštila - ani Medúzčin element není totiž nositelem univerzální pravdy. Vida genialitu čistého C a dechberoucích optimalizací na úrovni assembleru, Java se ve své prázdnotě propadla ku kamsi do hlubin věčného zatracení. A přesto ani od té doby nijak pořádně, významně, nebo otevřeně nekóduji, a to rozhodně nenaplňuje tu dříve vznešenou myšlenku Nočního kódu. Vědět už při jejím zakládání, že to jednou stejně skončí degradovaným stavem, asi bych všechno vyměnil za jediný den a čokoládový croissant.

Každopádně teď, jak se zdá, se všechno začalo nechtěně vracet a v rámci výuky přišlo už v minulém roce pár požadavků na znovuzhmotnění Javy jako přímočarého demonstračního prostředku OOP. To už samo o sobě zní jako ohromná katastrofa. V tuto chvíli se jedná o jednoduchý projekt s velmi volným zadáním libovolného 1:N modelu s podmínkou návrhu a realizace vlastního GUI prostřednictvím knihovny Swing. Výuka samotná je sama o sobě velmi dobře organizovaná a podporovaná operačními programy ESF, přesto je tak neuvěřitelně obsáhlá, že skoro není možné uvěřit, že by se skutečně dalo během jednoho jediného semestru přetavit naprosté near-BFU začátačníky na armádu špičkových programátorů znalých všech neprůstřelných návrhových vzorů a schopných samostatného kreativního uvažování při zachování čistoty a oku lahodící elegance. Bohužel času je nechutně málo a v ukázkách se právě z důvodů úspor matlá všechno, tedy i vykreslovací a logický kód, dohromady, takže lze očekávat, že odevzdané práce studenty ve výsledku vlastně nic nenaučí a jen vlastně dále přispějí k rozvoji jejich vnitřního zmatku.

Z toho důvodu jsem si hned na začátku položil velmi jednoduchou otázku. Je při tvorbě MVC aplikace ve Swingu vůbec možné nějak snadno, rychle, důsledně a důstojně oddělit vrstvu View od Controlleru? Základním předpokladem pro uspokojivou odpověď je si nejprve jasně vymezit, co má která vrstva na svědomí a za jaké morální hranice by neměla zajít.

Model
Základní objektový model. Udržuje ER modelovaného aplikačního problému a poskytuje jen black-box rozhraní pro operace se svými daty. Je zcela nezávislý na způsobu zobrazení dat. Můžeme si ho představit jako sadu jakýchkoliv obecných potomků třídy Object.

View - Pohled
Je prvkem uživatelského rozhraní a grafickou reprezentací modelovaného problému - sadou tabulek, obrázků, grafů, tlačítek, nabídek, dialogů, oken - tedy tím hmatatelným na GUI aplikaci samotné. Má v zásadě tři hlavní úkoly.

  • Vytvoří, rozloží a vykreslí všechny požadované Swing komponenty.
  • Reaguje na uživatelem vyvolané události a odesílá zprávy o nich do příslušného Controlleru.
  • Přijímá a reaguje překreslováním na odpovědi od Controllerů.

Controller - Řadič
Tvoří operační vrstvu mezi Modelem a View, je zcela nezávislý na použitých mechanismech výsledného zobrazení ve View. Má rovněž tři základní úkoly.

  • Přijímá zprávy s požadavky od View.
  • Provádí logické operace s Modelem pomocí jeho veřejného rozhraní.
  • Odesílá zprávy s odpovědí do příslušných View.

Je to rozhodně výzva, protože v Javě jsem to nedělal hrozně dlouhou dobu a přecejen dny strávené v přítulném Objective-C donutí člověka think different. Přijít k čisté Javě je pak jako cesta hodně dlouho zpátky časem, nehledě na naprosté zděšení ze všech těch konstrukcí, co nevypadají kolikrát ani trochu objektově. Oproti moderním prostředím je navíc Swing tak neuvěřitelně low-tech, že o MVC navíc nemůže být bez nějakého hlubšího zamyšlení vůbec řeč.

Základním kamenem takové nějaké (slušné) desktopové grafické aplikace ve Swingu je JFrame, který představuje hlavní okno. To se tvoří většinou hned zkraje z event dispatch vlákna pomocí SwingUtilities a zůstává po celou dobu běhu. Jedno hlavní okno už může obsahovat svoje vlastní komponenty, nebo se třeba v případě MDI ještě dál členit na množinu samostatných JInternalFrame. Ať už je to jakkoliv, hlavní okno se vyznačuje především svojí všudypřítomností v rámci celé aplikace, čehož bude velmi rozumné využít právě pro účely MVC.

Toto zamyšlení směřuje k vytvoření abstraktního předka takového obecného okna, co bude mít za úkol především schraňovat informace o všech svých View a Controllerech v aplikaci a bude poskytovat i globální ukazatel na samotné okno. Tomu se bude muset podřídit i návrh abstraktního View a Controlleru, což budou třídy beroucí si v konstruktoru ukazatel na svoje okno, budou poskytovat metodu pro získání tohoto okna; v případě View též ukazatel na svoji vlastní kreslící plochu (tzv. contentPane) - View totiž bude zcela obecným grafickým prvkem, třídou v každém případě dědící od JComponent.

Zajištění singletonů pro View a Controllery v okně bude potřeba provést jejich jednorázovou registrací, ideální strukturou pro takovéto informace je HashMap, držící dvojice názvu třídy a její konkrétní instance.

public abstract class AbstractFrame {
	
  /** Okno samo o sobě */
  protected final JFrame frame;

  /** Mapa View - views */
  protected final Map<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>> views = new HashMap<Class<? extends AbstractView<? extends JComponent>>, AbstractView<? extends JComponent>>();
    
  /** Mapa Controllerů - controllers */
  protected final Map<Class<? extends AbstractController>, AbstractController> controllers = new HashMap<Class<? extends AbstractController>, AbstractController>();

  /**
   * Konstruktor
   *
   */
  public AbstractFrame() {
    regControllers();
    regViews();

    // ... + vykreslení komponent
  }

  protected abstract void regViews();

  protected abstract void regControllers();

  /**
   * Získání konkrétního Pohledu z mapy všech zaregistrovaných na základě názvu třídy.
   * 
   * @param viewClass
   * @return Instance požadovaného Pohledu
   */
  @SuppressWarnings("unchecked")
  public <View extends AbstractView<? extends JComponent>> View getView(Class<View> viewClass) {
    return (View) views.get(viewClass);
  }
    
  /**
   * Získání konkrétního Controlleru z mapy všech zaregistrovaných na základě názvu třídy.
   * 
   * @param controllerClass
   * @return Instance požadovaného Controlleru
   */
  @SuppressWarnings("unchecked")
  public <Controller extends AbstractController> Controller getController(Class<Controller> controllerClass) {
    return (Controller) controllers.get(controllerClass);
  }

}
AbstractFrame - abstraktní předek okna

Konkrétní implementace by pak v takovém okně mohla pro registraci dvou View a jejich Controllerů, tabulky a detailu, mohla vypadat například takto. 

public class MainWindowFrame extends AbstractFrame {

  /**
   * Konstruktor
   *
   */
  public void MainWindowFrame() {
    super();
  }

  /**
   * Implementace abstraktní metody pro registraci Pohledů
   * 
   */
  @Override
  protected void regViews() {
    // Pohled Tabulky na celý model
    views.put(TableView.class, new TableView(this));
		
    // Detailní pohled na jeden prvek
    views.put(DetailView.class, new DetailView(this));
  }

  /**
   * Implememtace abstraktní metody pro registraci Controllerů.
   *
   */
  @Override
  protected void regControllers() {

    // controller Pohledu tabulky
    controllers.put(TableController.class, new TableController(this));
		
    // controller Pohledu detailu položky
    controllers.put(DetailController.class, new DetailController(this));
  }

}
MainWindowFrame - konkrétní implementace okna

Každý View si pak pohodlně bude moci poslat zprávu Controlleru přehledným zavoláním getMainFrame().getController(XYZController.class).metoda(zpráva) a naopak, každý Controller bude moci reagovat pomocí getMainFrame().getView(XYZView.class).metoda(odpověď).

V případě tabulky a detailu ukázkového příkladu bylo potřeba ještě zajistit změnu pohledu v rámci jednoho okna na stejném místě. To se dalo pohodlně zařídit tím, že byla vyčleněna plocha - JPanel - v hlavním okně, na kterou byl vyveden ukazatel (dostupný z abstraktních get/setContent( )). Přepnutí se pak dalo v každém okamžiku vyvolat přes getMainFrame().setConten(getMainFrame().getView(ABCView.class).getContentPane()).

Přestože byla prvotní úvaha v tom původním swingovém zmatku skutečně velká, nakonec se ukázalo, že při troše štěstí a ochotě vynaložit nějaké úsilí není zas až tak komplikované zavést velmi přehledné MVC v něčem tak zdánlivě primitivním, jako je čistá Java se Swingem. Je to pohoda.

Zdroje původní inspirace

 

Tento článek přečetlo již 375 čtenářů (1 dnes).

Komentáře

Nový komentář