{ "title": "MVC Pattern", "exercisesLink": "opgaver/opgaver.htm" }

Dette kapitel, de tilhørende opgaver og vejledende løsninger er under opdatering fra Java Swing til JavaFX.

Ikke opdateret (endnu): Figur 8, opgaver 2, og den vejledende løsning til opgave 2.

Man bruger ofte "control" eller "widget" som betegnelse for enkelte grafiske kompo­nenter, der indgår i en bruger­grænse­flade. Det kan f.eks. være knapper, tekst­felter osv. Da vi i forbindelse med MVC Pattern anvender betegnelsen: "controller", i en helt bestemt betydning, der kun delvist dækker det vi normalt vil kalde controls, kan disse to beteg­nelser nemt skabe forvirring. I dette kapitel vil vi derfor kun anvende beteg­nelsen: "widget" for grafiske kompo­nenter, selvom beteg­nelsen "control" er noget mere udbredt.
Model-View-Controller (MVC) Pattern er "The Grand Old Man" når talen falder på design patterns. Det stammer oprindelig fra programmeringssproget Smalltalk og bruges i [GoF94] (p.4-6) som et introducerende eksempel på, hvad et design pattern er.
MVC Pattern beskæftiger sig med en større sammenhæng, og vi vil derfor anvende en anden afsnits-inddeling end når vi beskriver design patterns i almindelighed.
1. Design idé
Tre dele MVC Pattern opdeler en interaktiv del af en applikation i tre dele. Modellen har de relevante data, samt den dertil hørende funktionalitet. View viser en hel eller delvis repræsentation af de adata der er i modellen. Controlleren tager sig af input fra brugeren, og påvirker modellens data.
View og controller udgør til sammen den grafiske brugergrænseflade (GUI):
Figur 1:
View og controller udgør den grafiske bruger­grænse­flade (GUI)
Det centrale i dette design er at ændringer i modellen automatisk fører til opdatering af view, og evt. også opdatering af controlleren.
Forbindelsen mellem view og controller anvendes, hvis brugeren skal have mulighed for at configurere, hvordan view skal vise modellen. Det kunne f.eks. være om data skal vises i et lagkagediagram eller et søjlediagram.
View er abstraktion Den repræsentation af modellen som view viser, er en abstraktion. View kan basere sig på større eller mindre dele af modellens data, alt efter hvilken abstraktion man ønsker.
Controlleren giver brugeren mulighed for at ændre modellens data.
2. Eksempel
Betragt følgende GUI, der er en save dialog:
Figur 2:
Save dialog
Den model der ligger bag, og som view viser en repræsentation af, er filesystemet.
Widgets Save dialogen er en GUI som så mange andre, der indeholder en række mindre grafiske elementer eller widgets, som enten er views eller controllere. Lad os identificere nogen af dem.
Oversigten der viser en del af de directories og filer der findes i det aktuelle directory er GUI-ens view.
Der er en række knapper, med tekst eller ikoner, der gør det muligt for brugeren at påvirke hvad der vises og hvad der sker. Mere interessant er scrollbar'en, der bestemmer hvilke directories og filer der vises i oversigten. Alle disse er controllere, fordi de enten giver brugeren mulighed for at påvirke view'et mht. hvad der vises (f.eks. 'gå et directory op', 'gå til home directory' og scrollbar'en) eller at ændre det begved liggende filesystem (f.eks. 'opret directory').
View viser del af modellen Hele GUI-en er et slags vindue, der giver os mulighed for at se ind i filesystemet. Vi kan kun se en lille del af det, men ved at "skrue og dreje" kan vi bestemme hvilken del vi ser — som når man bevæger en kikkert.
3. Klassediagram
Figur 3:
MVC klasse-diagram
View kender den model som den viser en repræsentation af. Modellen kender både den og evt. andre views, og meddeler dem løbende ændringer af modellen, så views kan opdatere, gvad de viser.
Controller kender én model, som den kan påvirke. Modellen kender de controllere der har behov for at kende modellens tilstand. F.eks. kan man ønske at controllere bliver disabled, hvis modellen er i en bestemt tilstand.
Controller kan kende et view; hvis den har behov for at ændre den måde hvorpå view viser modellen.
4. Objektdiagram
I følgende eksempel er der to views. Den ene har ingen controller tilknyttet, men viser hele tiden modellen på samme måde (Det den viser kan ændre sig efterhånden som modellen måtte ændre sig, men ikke måden den viser det på). Det andet view har en controller, der kan ændre den måde hvorpå den viser modellen, samt påvirke data i modellen.
Figur 4:
Eksempel på MVC objekt-system
5. Interaktionsdiagram
I det følgende viser to sekvensdiagrammer henholdsvis initialisering af objekt-systemet og et eksempel på håndtering af en event.
5.1 Initialisering
Figur 5:
Initialisering af objekt-systemet
Her melder de to views sig selv til som observere på modellen. Bemærk, at controlleren ikke er observer på modellen.
5.2 Input event
Her indkommer der en event til systemet. Event'en sendes til controlleren, der skal sørge for at slette det markerede/valgte element i view2.
Figur 6:
"Delete selected item" event-håndtering
Man bemærker at controlleren først henter oplysning fra view2 om hvilket element der er markeret/valgt, før den sender besked til modellen om at slette det. Dernæst sender modellen besked til både view1 og view2 om, at der er sket en ændring i modellen.
6. Virkninger
6.1 Positive
6.1.1 Data er uafhængig af hvordan det vises
Container-egenskaber De data der vises bliver uafhængige af den måde de vises på. Modellen kan derfor designes ud fra de container-egenskaber man iøvrigt ønsker. Dvs. at modellen ikke (ensidigt) vil være rettet mod views behov, men også være hensigtsmæssig i behandling af data i anden sammenhæng.
6.1.2 Views er ikke aggregeret Modellen
Uafhængige Modellen har kun en løs kobling til view. Man kan tilmelde/framelde views dynamisk, uden at det påvirker modellen. Det giver en frihed i den mangfoldighed af views man kan arbejde med, uden at det betyder noget for modellens implementation.
6.2 Negative
6.2.1 Ineffektivitet i opdatering af View
Lang række kald Hvis controlleren påvirker modellen ved en række kald, bliver det til en tilsvarende række kald af update på samtlige observere. Det kan være umådelig ineffektivt.
Stærkere kobling Man kan til dels løse det ved at få modellen til at vente med at sende update-kaldene til controlleren er færdig. Det forringer desværre designet, da modellen nu skal understøtte akkumulerede/forsinkede kald af update. Modellen bliver stærkere koblet til view og controller.
get-kald Der kan også forekomme ineffektivitet i views get-kald på modellen. Det sker hvis der foretages en lang række kald, for at view kan få de data den skal præsentere. At view gemmer sin tilstand, kombineret med en grad af pushI Observer Pattern vil push sende oplysninger med update-kaldet, om hvilken ændring der er sket. Dette er i modsætning til pull, hvor der kun gives besked om, at der er sket en ændring, ikke hvad der er sket., kan afhjælpe det, men alene push vil forringe designet af modellen. Det er nemlig et skridt væk fra at modellen kun er en container, i retning af at den er målrettet mod de behov som view har.
7. Implementation
Figur 7:
Implemen­tation med Observer pattern
7.1 View-Model
Observer pattern View skal opdatere når der sker ændringer i modellen. Det er oplagt at implementere dette med Observer Pattern, hvor View er Observer, og Model er Subject.
Reference til modellen Det vil være naturligt at lade view tilmelde sig selv som Observer i sin konstruktor. View skal alligevel senere bruge en reference til modellen, hvis den vel at mærke pull'er tilstandsændringen. Hvis modellen push'er tilstandsændringen vil en sådan reference fra view til modellen ikke være nødvendig. Observer Pattern vil normalt være implementeret så update-metoden modtager en reference til Subject, og man kan derfor også vælge at bruge denne reference, der i givet fald vil referere til modellen.
7.2 Controller-Model
Der er to relationer mellem controlleren og modellen.
Mulig observer-rolle Først er der som for View-Model-forholdet en mulig Observer-rolle for controlleren ifht. modellen som Subject. Implementationen af dette er analog til View-Model, dog vil det altid være naturligt at controlleren selv melder sig som Observer ved modellen, da den som tilstands-ændrer alligevel skal bruge en reference til modellen.
Tilstands-ændrer Dernæst er der controllerens rolle som tilstands-ændrer af modellen. Modellen skal have metoder, der understøtter de behov controlleren har for at ændre modellens tilstand.
7.3 View-Controller
Controllerens mulige behov for at indstille hvordan view viser modellen implementeres blot med passende metoder i view.
View kan instantiere controller Hvad skal give controlleren en reference til view? Den der instantierer controlleren ville være et naturligt valg, men hvem er det? Det kunne være view! Det er specielt anvendeligt hvis view ikke er en widget, men et vindue der indeholder widgets. I den situation vil det være naturligt at vinduet også instantierer controlleren, da disse ligeledes er widgets indeholdt i vinduet.
Visse controllere fungerer udelukkende som configuratorer af view og har ikke noget forhold til modellen. Disse vil typisk være aggregeret view[[De vil være lavet som en del af view.]].
7.4 View
Er view et vindue eller en widget? Man kan mere eller mindre vælge efter behag. Der er dog visse ting man skal være opmærksom på.
Figur 8:
Samlet view med Whole-Part
Whole-Part Hvis view er et vindue bliver den et ekstra led mellem widgets og modellen. Vinduet tilmelder sig som Observer ved modellen og den skal så delegere data rundt til de forskellige widgets. På sin vis giver det et enklere design, da alle widgets alligevel er indeholdt i vinduet. Dette design følger et Whole-Part Pattern.
Figur 9:
Individuelle Views
Widgets er views Hvis view er en widget er den direkte Observer på modellen[[Se afsnit 7.6, hvor det er beskrevet hvordan disse alternativt kan være indirekte observere på modellen.]]. Man laver det med en subklasse til den pågældende widget og implementerer Observer interfacet. Det er et renere design fordi det mere direkte er widgets der vises og dermed er views. Hvis man har mange views bliver det til mange klasser; hvilket er en ulempe. Alternativt udvikler vinduets update-metode sig dog let til en Blob-metode (Blob Anti-Pattern[[Blob Anti-Pattern går i sin enkelthed ud på, at man f.eks. ved eventhåndtering laver én eventhandler for mange forskellige events, med en stor samlet if-else-sætning, der vokser med antallet af events man ønsker at håndtere, og bliver mere og mere voldsom i sit omfang — den blobber!]]), hvis den skal delegere til et utal af widgets.
Blob-metode Hvad man konkret vælger er op til én selv. Begge designs er sunde i det små, men bliver antallet af widgets stort, bør man nok vælge de mange klasser frem for Blob-metoden — den er ikke sund!
7.5 Controller
Configurere view direkte Controllere er reelt widgets i et vindue, så diskussionen er den samme som ovenfor. Skal vinduet være controller eller skal det være de enkelte widgets? Svaret er umiddelbart det samme. Der er dog et men, for hvad med controllers muligheder for at indtille view? Hvis vinduet er controller kan den gøre det uden brug af metoder på view fordi den også har de widgets som er views. Det betyder at svaret ikke er så enkelt. Et design med et vindue som samlende controller kræver et mindre interface for view; hvilket er fristende.
Om man skal leve med en Blob-metode kommer an på hvor meget indstilling af view fylder i designet. Er det virkelig så mange metoder man sparer, når det kommer til stykket?
7.6 Model-View-Binding
  Hvis man anvender individuelle views og controllere, er spørgsmålet hvordan disse skal implementeres.
Lave sub-klasser til widget-klasser Man kan lave dem ved specialisering af de widget-klasser der findes i det grafiske framework man anvender. Det kan i Java være JavaFX (eller tidligere Java Swing), eller det kan være Windows Presentation Foundation (WPF) (eller tidligere Windows Forms) i C#. Disse frameworks har widget-klasser, som man kan nedarve fra, og lave egne klasser, der håndterer de events der vedrører dem, og indeholde den program-logik der håndterer deres data.
En knap som controller Det kunne f.eks. være en knap, der nedarver fra Button-klassen i JavaFX. Når der bliver trykket på knappen, ved denne controller hvad der skulle gøres i forhold til Modellen. Knappen vil selv være event handler for den action event, der udløses når der trykkes på knappen, og hvis den også er observer på modellen (hvis den f.eks. skal disables når modellen er i en bestemt tilstand), vil den selv have update-metoden der bliver kaldt fra modellen.
Model-View-ViewModel En anden måde at gøre det på, er at indføre et nyt mellemliggende objekt som håndterer events og indeholder program-logikken (en slags individuel Whole-Part, hvis det giver nogen mening!). Det har den fordel, at man ikke skal specialisere widget-klasserne, men i stedet har opgaven placeret i et objekt for sig selv. Man kan sige at et sådant objekt er en mediator (se evt. Mediator Pattern), der formidler samarbejdet mellem View/Controller og Model. Man kalder generelt dette mønster for Model-View-Binding, eller bruger den mere udbredte betegnelse Model-View-ViewModel (MVVM)I MVVM kaldes Binding-objektet for ViewModel. (en betegnelse der stammer fra .Net Framework verdenen; hvor dette mønster først blev anvendt i WPF).
  Man kan illustrere samspillet mellem de tre objekter med følgende figurer:
Figur 10:
Binding mellem View/Controller og Model
One-Way Binding Øverste har vi en Binding, der er Observer på Model. Når der sker ændringer i modellen vil Binding opdatere View, og det er på den måde Binding der indeholder den nødvendige viden omkring hvilke data der skal vises, og hvordan der skal vises. På den måde er der ikke nogen kobling mellem View og Model, da al programlogikken omkring relationen mellem View og Model er indeholdt i Binding-objektet.
Two-Way-Binding Nederst har vi den samme opbygning med en Controller. De øverste pile i viser igen et Observer-forhold mellem Controller og ModelDet er de færreste Controllers, der er Observer på Model, men det er medtaget i figuren, for at illustrere denne mulighed., som udelukkende administreres af Binding. De nederste pile (der peger mod højre) illustrerer at Binding er Observer på Controller, der f.eks. kunne være en knap, og når der trykkes på knappen vil Binding vide hvad der skal ændres i Model.
  Når relationen kun går den ene vej, som i View-Binding-Model ovenfor, kalder man det en One-Way-Binding, mens man kalder det en Two-Way-Binding, når relationen går begge veje, som i Controller-Binding-Model, og der dermed sker en gensidig opdatering.
8. Kode eksempel
8.1 Beskrivelse
I det følgende skal der laves en GUI med fem tal og tre knapper.
Modellen indeholder kun ét heltal, og har en række metoder til at ændre og tilgå dette tals værdi. De fem tal i GUI-en viser alle tallet fra modellen.
GUI-en skal have følgende udseende når applikationen starter:
Figur 11:
GUI ved start
Lad os først identificere view- og controller-delen:
Figur 12:
View- og controller-dele af GUI'en
Når brugeren trykker på Minus-knappen skal modellens tal tælles ned og view skal opdateres:
Figur 13:
GUI-en efter ét tryk på Minus-knappen
Der skal være en begrænsning på hvor stort/lille tallet kan blive ved interaktion med controllerne. Vi vil hold os til én-cifrede tal, så -9 er den mindste og 9 den største værdi vi vil acceptere.
Begrænse i controlleren Hvis grænsen i den ene retning er nået, skal den relevante knap ikke acceptere interaktion med brugeren. Det skal ikke være modellen der laver denne begrænsning, da der i princippet kunne være andre views og andre controllere i systemet som vi ikke kender til, som arbejder med andre grænseværdier, evt. ingen!
Figur 14:
Plus-knappen dimmet ud, fordi grænse er nået
8.2 Løsning med individuelle views og controllere
8.2.1 Klassediagram
Lad os først se det samlede klassehierarki for løsningen med individuelle views og controllere:
Figur 15:
Klasse-diagram med individuelle views og controllere
Som man fornemmer har en løsning med individuelle views og controllere relativ mange klasser. Til gengæld spredes koden dermed i mange stykker, og de enkelte klasser bliver derfor relativ små. Lad os se hvordan disse klasser implementeres.
8.2.2 Model
Wrapper Modellen er nærmest kun en wrapper, der indeholder en integer. Den har tre metoder, der er skræddersyet til de tre controlleres anvendelse[[Som tidligere nævnt, kan man ikke regne med at modellen vil være lavet så målrettet til views behov, som det her er tilfældet. Men det er valgt, da det gøre eksemplet enklere.]]. Endelig er der en get-metode.
Source 1:
Modellen
import java.util.Observable;

public class Model extends Observable {
  private int value;

  public Model() {
    reset();
  }

  public void inc() {
    value++;

    fire();
  }

  public void dec() {
    value--;

    fire();
  }

  public void reset() {
    value = 0;

    fire();
  }

  private void fire() {
    setChanged();
    notifyObservers();
  }

  public int getState() {
    return value;
  }
}
8.2.3 View
  I forbindelse med både views of controllere, kan vi vælge mellem om vi vil specialisere en widget-klasse ved nedarvning, eller vi vil lave en Binding-klasse, der kobler widget indirekte med model. I dette eksempel og de vejledende løsninger til opgaverne, vil vi specialisere views og lave Binding-klasser i forbindelse med controllerne. På den måde får vi set begge muligheder.
  Man kan evt. selv prøve at gøre det omvendt for en ekstra øvelses skyld!
Labels For at få individuelle views, laves en subklasse til Label, der implementerer Observer.
View tilmelder sig selv som Observer på modellen.
Source 2:
View
import java.util.Observable;
import java.util.Observer;

import javafx.scene.control.Label;

public class View extends Label implements Observer {

  public View(Model model) {
    model.addObserver(this);

    // initialize view
    update(model, null);
  }

  @Override
  public void update(Observable subject, Object arg) {
    Model model = (Model) subject;
    int value = model.getState();

    setText("" + value);
  }
}
pseudo-kald Man bemærker, at vi initialiserer view med et pseudo-kald af update-metodenpseudo, fordi kaldet af update-metoden ikke kommer fra modellen, selvom det netop er modellen, der angives som parameter i kaldet..
8.2.4 Controller
Buttons Der laves en overordnet super-klasse for alle controllere. Fælles for alle controllere er at de har en knap. En controller tilmelder sig selv som EventHandler hos den tilhørende Button. Den gemmer en reference til såvel knappen som modellen.
Source 3:
Fælles super­klasse for alle controllere
import javafx.scene.control.Button;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;

public abstract class Controller implements EventHandler<ActionEvent> {
  protected Model model;
  protected Button button;

  public Controller(Button button, Model model) {
    this.button = button;
    this.model = model;

    button.setOnAction(this);
  }
}
To af de tre controllere er observere på modellen, idet de skal være disablede, hvis deres grænseværdi er nået. Der laves en speciel super-klasse til disse, som nedarver fra den overordnede controller klasse.
Source 4:
Fælles super­klasse for de to controllere, der observer modellen
import java.util.Observable;
import java.util.Observer;

import javafx.scene.control.Button;

public abstract class ObservingController extends Controller implements Observer {

  public ObservingController(Button button, Model model) {
    super(button, model);

    model.addObserver(this);
  }

  @Override
  public void update(Observable subject, Object obj) {
    Model model = (Model) subject;
    
    button.setDisable(grænseNået(model.getState()));
  }

  abstract protected boolean grænseNået(int value);
}
ObservingController overlader det til subklasserne at definere, hvad det vil sige at grænsen er nåetIdéen med at kalde en abstrakt metode, som senere bliver implementeret i subklasserne, men som man altså tager forskud på ved at kalde den allerede i superklassen, er nærmere beskrevet under Template Method Pattern.
Dernæst følger Plus- og Minus-controllerne. Den eneste forskel er implementationen af handle og grænseNået.
Source 5:
Plus- og minus-controllerne
import javafx.event.ActionEvent;
import javafx.scene.control.Button;

public class PlusController extends ObservingController {

  public PlusController(Button button, Model model) {
    super(button, model);
  }

  @Override
  public void handle(ActionEvent e) {
    model.inc();
  }

  @Override
  protected boolean grænseNået(int value) {
    return (value >= 9);
  }
}
import javafx.event.ActionEvent;
import javafx.scene.control.Button;

public class MinusController extends ObservingController {

  public MinusController(Button button, Model model) {
    super(button, model);
  }

  @Override
  public void handle(ActionEvent e) {
    model.dec();
  }

  @Override
  protected boolean grænseNået(int value) {
    return (value <= -9);
  }
}
Reset controlleren adskiller sig fra de to andre, ved at den ikke observer modellen. Derfor nedarver den direkte fra den overordnede controller klasse.
Source 6:
Reset controlleren
import javafx.event.ActionEvent;
import javafx.scene.control.Button;

public class ResetController extends Controller {

  public ResetController(Button button, Model model) {
    super(button, model);
  }

  @Override
  public void handle(ActionEvent e) {
    model.reset();
  }
}
8.2.5 Initialisering
Det hele initialiseres fra Applikations-objektet, der giver vinduet (Stage) sit indhold i form af de tre controllere og de fem views.
Source 7:
Initialiserer MVC objekt­systemet
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.geometry.Pos;

public class Main extends Application {

  @Override
  public void start(Stage stage) {
    stage.setTitle("MVC Tæller");

    // layout
    HBox viewPanel = new HBox();
    viewPanel.setAlignment(Pos.CENTER);
    viewPanel.setSpacing(8);
    viewPanel.setPadding(new Insets(10, 10, 10, 10));

    HBox controllerPanel = new HBox();
    controllerPanel.setAlignment(Pos.CENTER);
    controllerPanel.setSpacing(8);
    controllerPanel.setPadding(new Insets(10, 10, 10, 10));

    VBox vbox = new VBox();
    vbox.getChildren().addAll(viewPanel, controllerPanel);

    // model
    Model model = new Model();

    // views
    for (int i = 0; i < 5; i++) {
      View view = new View(model);
      viewPanel.getChildren().add(view);
    }

    // controllers
    Button minusButton = new Button("Minus");
    new MinusController(minusButton, model);
    controllerPanel.getChildren().add(minusButton);

    Button resetButton = new Button("Reset");
    new ResetController(resetButton, model);
    controllerPanel.getChildren().add(resetButton);

    Button plusButton = new Button("Plus");
    new PlusController(plusButton, model);
    controllerPanel.getChildren().add(plusButton);

    Scene scene = new Scene(vbox, 200, 90);
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}
Man bemærker, at der ikke er nogen eventhåndtering her, da denne udelukkende foregår i views og controllere.
8.3 Whole-Part løsning
 
Klasse-diagrammet bliver betydelig simplere fordi vinduet (i form af Applikations-objektet i JavaFX) nu påtager sig Whole-rollen både i view- og controller-sammenhæng.
Figur 16:
Klasse­diagram med Whole-Part implementation
Vi genbruger Model-klassen fra løsningen med individuelle views og controllere, og udvider Applikations-objektet:
Source 8:
Whole-Part Applikations­objektet
import java.util.Observable;
import java.util.Observer;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application implements Observer, EventHandler<ActionEvent> {
  private Model model;
  private Label[] labels;
  private Button plusButton, resetButton, minusButton;

  @Override
  public void start(Stage stage) throws Exception {
    stage.setTitle("MVC Tæller");

    // layout
    HBox viewPanel = new HBox();
    viewPanel.setAlignment(Pos.CENTER);
    viewPanel.setSpacing(8);
    viewPanel.setPadding(new Insets(10, 10, 10, 10));

    HBox controllerPanel = new HBox();
    controllerPanel.setAlignment(Pos.CENTER);
    controllerPanel.setSpacing(8);
    controllerPanel.setPadding(new Insets(10, 10, 10, 10));

    VBox vbox = new VBox();
    vbox.getChildren().addAll(viewPanel, controllerPanel);

    // model
    model = new Model();
    model.addObserver(this);

    // views
    labels = new Label[5];

    for (int i = 0; i < labels.length; i++) {
      Label view = new Label("0");
      
      labels[i] = view;
      viewPanel.getChildren().add(view);
    }

    // controllers
    minusButton = new Button("Minus");
    minusButton.setOnAction(this);
    controllerPanel.getChildren().add(minusButton);

    resetButton = new Button("Reset");
    resetButton.setOnAction(this);
    controllerPanel.getChildren().add(resetButton);

    plusButton = new Button("Plus");
    plusButton.setOnAction(this);
    controllerPanel.getChildren().add(plusButton);

    Scene scene = new Scene(vbox, 200, 90);
    stage.setScene(scene);
    stage.show();
  }

  @Override
  public void update(Observable subject, Object arg) {
    Model model = (Model) subject;
    
    int value = model.getState();

    for (Label view : labels)
      view.setText("" + value);

    plusButton.setDisable(value >= 9);
    minusButton.setDisable(value <= -9);
  }

  @Override
  public void handle(ActionEvent e) {
    if (e.getSource() == minusButton)
      model.dec();
    else if (e.getSource() == resetButton)
      model.reset();
    else if (e.getSource() == plusButton)
      model.inc();
  }

  public static void main(String[] args) {
    launch(args);
  }
}
Bemærk hvordan handle-metoden er ved at udvikle sig til en Blob-metode, med utallige if-else-sætninger.
At de fem views alle er labels simplificerer update i den konkrete implementation, men med forskellige widgets, med forskellige update-metoder (her er det setText), vil det blive betydelig mere blobbet.
9. Varianter
9.1 Document-View
I denne variant lader man view og controller smelte sammen, og kalder samtidig modellen for Document:
Figur 17:
Document-View objekt­system
Det mest tydelige eksempel på Document-View er nok tekstbehandlingsprogrammer; hvor man bruger view (hvor teksten vises) til at markere med musen og skrive "direkte i" (i anførselstegn fordi det reelt skal en tur omkring Document før det bliver opdateret i view).
I JavaFX er flere widgets Document-Views, f.eks. TextField og TextArea.
10. Relationer til andre mønstre
Observer MVC uden Observer Pattern er vanskeligt at forestille sig. Det centrale i MVC er netop opdatering et sted, når der sker en ændring et andet sted.
Whole-Part Anvendelsen af Whole-Part Pattern i forbindelse med view, henholdsvis controller, er et valg man må træffe ud fra de konkrete omstændigheder.
View-Handler Hvis man ofte åbner og lukker views, der er tilknyttet samme model, kan det evt. være en fordel at bruge View-Handler Pattern til at administrere dem.
Repetitionsspørgsmål
1 Hvad er modellens opgave?
2 Hvad er views opgave ifht. modellen?
3 Hvad er controllerens opgave ifht. modellen?
4 Hvad kan være controllerens opgave ifht. view?
5 Hvilke udgør GUI-en?
6 Hvilken ineffektivitet kan der forekomme?
7 Hvilket pattern bruges til at styrre opdateringen af view og evt. controller?
8 Hvornår vil det være naturligt at lade view instantiere controller?
9 Hvilke controllere har ikke noget forhold til modellen?
10 I hvilken forbindelse kan man anvende Whole-Part pattern?
11 Hvilken ulempe kan opstå når man anvender en Whole-Part-løsning?
12 Hvordan kan man implementere controllers configuration af view?
13 Hvad er idéen i Document-View?
Svar på repetitionsspørgsmål
1 Modellen administrerer de data der skal vises/manipuleres.
2 View skal vise en repræsentation af modellens data.
3 Controlleren skal give brugeren mulighed for at ændre modellens data.
4 Controlleren kan configurere den måde view viser modellen.
5 View og controller.
6 Opdateringen af view kan være ineffektiv, hvis der indgår mange kald. Det kan være mange kald af update fra modellen eller når view pull'er data fra modellen.
7 Observer pattern.
8 Hvis man bruger et Whole-Part pattern; hvor controllerne styres af et vindue, der optræder som view.
9 De controllere der kun bruges til at indstille view.
10 Man kan anvende Whole-Part til at samle alle view respektiv controllere, i stedet for at have individuelle views respektiv controllere.
11 handle- og update-metoderne kan hurtigt udvikle sig til Blob-metoder.
12 Enten ved metode-kald fra controller til view, eller ved at bruge Whole-Part, hvor controller får direkte adgang til de indeholdte views.
13 I visse sammenhænge kan det være bekvemt at samle view og controller på grund af en stærk visuel sammenhæng mellem det viste og de handlinger brugeren udfører — f.eks. en tekst-editor.