View
244
Download
1
Category
Preview:
Citation preview
Model View Controller
Prasun Dewan
Comp 114
Model View Controller Pattern
• Issue– How to create user-interface objects like
object editor
• Model-View-Controller Pattern– Observer sub-pattern
User Interface Objects• How to reuse code among different user
interfaces?
• How to simultaneously create multiple user interfaces to same object?– Different views for same user.
• Slide sorter vs. Outline
– How to create same or different view for each user sharing an object
• Distributed presentation
Counter
• Can add arbitrary positive/negative value to an integer.
• Different user interfaces.
Console Input and Output
Console Input and JOption Output
Console Input,Output and JOption Output
Implementation Constraint
• Re-use as much code as possible in the three implementations
Pattern-free Implementation
public class ConsoleUI { static int counter = 0; public static void main(String[] args) {
while (true) { int nextInput = readInt(); if (nextInput == 0) return; counter += nextInput; System.out.println("Counter: " + counter);}
}}
Pattern-free Implementationpublic class ConsoleUI { static int counter = 0; public static void main(String[] args) {
while (true) { int nextInput = readInt(); if (nextInput == 0) return; counter += nextInput;
JOptionPane.showMessageDialog(null, "Counter: " + counterValue);
} }}
Counter code duplicated
Model/Interactor Separation
AConsoleUI
AMixedUI
Counter AMultipleUI
Model
Model has no UI code and only semantics!
Interactor
Interactor
Interactor
Composing Model and Interactor
public static main (String args[]) (new AConsoleUI()).edit (new ACounter());}
Counter Modelpackage models;public class ACounter implements Counter {
int counter = 0;public void add (int amount) {
counter += amount;}public int getValue() {
return counter;}
}
• Code reusability• Less duplication• Fewer changes
Console Interactor
package interactors;public class AConsoleUI implements ConsoleUI { public void edit (Counter counter) {
while (true) { int nextInput = readInt(); if (nextInput == 0) return; counter.add(nextInput); System.out.println("Counter: " + counter.getValue());}
}}
Shared model code
Input
Output
Mixed Interactorpackage interactors;public class AConsoleUI implements ConsoleUI { public void edit (Counter counter) {
while (true) { int nextInput = readInt(); if (nextInput == 0) return; counter.add(nextInput); JOptionPane.showMessageDialog(null,
"Counter: " + counter.getValue());}
}}
Shared model code
Input
Output
Multiple Interactorpackage interactors;public class AConsoleUI implements ConsoleUI { public void edit (Counter counter) {
while (true) { int nextInput = readInt(); if (nextInput == 0) return; counter.add(nextInput); System.out.println("Counter: " + counter.getValue()); JOptionPane.showMessageDialog(null,
"Counter: " + counter.getValue());}
}}
Shared model code
Input
Output
Drawbacks of Monolithic UI
AConsoleUI
AMixedUI
AMultipleUI
ACounter
Duplicated input code
Duplicated output code
Model/Interactor Pattern
Interactor
Model
Arbitrary UI unaware methods
Computation code
UI Code
MVC Pattern
Controller View
Model
Per
form
s In
put
Per
form
s O
utpu
t
Write methods
Read methods
Controller View
MVC Pattern in Counter
Modeladd() getValue()
Per
form
s In
put
Per
form
s O
utpu
t
Multiple Views and Controllers
Model
Controller 1
Controller 2
Controller 3
Controller M
View 1
View 2
View 3
View N
Syncing Controllers & View
Model
Controller 1
Controller 2
Controller 3
Controller M
View 1
View 2
View 3
View N
Observer/Observable Pattern
Model
Changed object notifies views
ObservableObserver
Controller 1
Controller 2
Controller 3
Controller M
View 1
View 2
View 3
View N
Observer/Observable Pattern
Observable 1
Observer 1
Observer 2
Observer 3
Observer NObservable 2
Observer with multiple Observables
• A single battle simulation view observing– Multiple planes– Multiple tanks
Notification Scheme
Observable 1
• Each observer is registered with observable.
• Each write method in observable calls a notification method in each observer.
•Notification method in observer reads model .
Observable 2
• Each student is registered with professor’s listserv.
• When web page is updated mail sent to students.
•Student reads web page.
Observer 1
Observer 2
Observer 3
Observer N
General Notification Scheme•Observers may have multiple observerables with common notification method.
•Notification method parameter indicates which observable.
Observable 1
Observable 2
Observer 1
Observer 2
Observer 3
Observer N
MVC Pattern
Controller View
Model
Per
form
s In
put
Per
form
s O
utpu
t
Write methods
Read methods
Notification method
Implementation dependent issues
• How does controller know about model?– Model connection method invoked on it.– By model or some other program
• Main
• How is observable registered with observer.– It registers itself if it knows about observable.– Model registers it if it knows about observer.– Some other code registers it
• Main
Model, View and Controller (MVC)
ControllerView
Model
Performs Input Performs Output
Write method
Read method
Notification method
Model connection
method
Observer registration
method
Counter Observable and Observer?
package models;public interface ObservableCounter extends Counter {
}
package models;public interface CounterObserver {
}
Counter Observable and Observer
package models;public interface ObservableCounter extends Counter {
public void addObserver(CounterObserver observer);public void removeObserver(CounterObserver observer);
}
package models;public interface CounterObserver {
public void update(ObservableCounter counter);}
Called whenever model is updated Updated model
Console View, JOption View
Common interface of all views
Counter Model
package models;import java.util.Vector;public class AnObservableCounter extends ACounter implements ObservableCounter {
Vector observers = new Vector();public void addObserver(CounterObserver observer) {
observers.addElement(observer);observer.update(this);
}public void removeObserver(CounterObserver observer) {
observers.removeElement(observer);}void notifyObservers() { for (int observerNum = 0; observerNum < observers.size(); observerNum++)
((CounterObserver) observers.elementAt(observerNum)).update(this);}public void add (int amount) {
super.add(amount);notifyObservers();
}}
Each write method notifies all.
Give this observable initial
value
Console View
package views;public class ACounterConsoleView implements CounterObserver {
public void update(ObservableCounter counter) {System.out.println("Counter: " + counter.getValue());
}}
Console View
package views;import models.ObservableCounter;import models.CounterObserver;public class ACounterConsoleView implements CounterObserver {
public void update(ObservableCounter counter) {System.out.println("Counter: " + counterValue);
}}
JOption View
package views;import models.ObservableCounter;import models.CounterObserver;import javax.swing.JOptionPane;public class ACounterJOptionView implements CounterObserver {
public void update(ObservableCounter counter) { JOptionPane.showMessageDialog(null, "Counter: " +
counterValue);}
}
Console Controller Interface
package controllers;import models.Counter;public interface CounterController {
public void setModel (Counter theCounter);public void processInput();
}
Console Controllerpackage controllers;import java.io.BufferedReader;import java.io.InputStreamReader;import models.Counter;import java.io.IOException;public class ACounterController implements CounterController {
Counter counter;public void setModel (Counter theCounter) {
counter = theCounter;}public void processInput() {
while (true) {int nextInput = readInt();if (nextInput == 0) return;counter.add(nextInput);
}}//readInt() …
}
Output method not called directly
Console Mainpublic static main (String args[]) Counter model = new ACounter(); model.addObserver (new ACounterConsoleView()); CounterController controller = new ACounterController(); controller.setModel(model); controller.processInput();}
Console and JOption Mainpublic static main (String args[]) Counter model = new ACounter(); model.addObserver (new ACounterJOptionView()); CounterController controller = new ACounterController(); controller.setModel(model); controller.processInput();}
Input Code Shared
Console Mainpublic static main (String args[]) Counter model = new ACounter(); model.addObserver (new ACounterConsoleView()); CounterController controller = new ACounterController(); controller.setModel(model); controller.processInput();}
public static main (String args[]) Counter model = new ACounter(); model.addObserver(new AConsoleJOptionView()); model.addObserver (new ACounterConsoleView()); CounterController controller = new ACounterController(); controller.setModel(model); controller.processInput();}
Mixed UI Main
Composition code duplicated
Facade
Façade Pattern
Controller View
ModelWrite methods
Read methods
Notification method
Interactor
Facade pattern
Component1
Component2
ComponentN
Facade
Façade: Compose objects into a single unit exposing methods relevant to that unit
• E.g. scanner, parser, program tree, code generator objects combined into one compiler object– Takes program text as input– Produces code as output– Passes text to scanner, which passes tokens to parser, which creates
program tree, which is processed by code generator, which produces output
• Compiler user not aware of internal components• Componentizing code is a pain as components must be combined• Facades removes this problem, creating a simple façade to complex
internals
Interactor Facade
• Provides a single edit() method taking model as argument
• Instantiates controller and view.• Makes view observer of model• Connects controller to model.• Starts controller.
ConsoleControllerAndView Facadepackage interactors;import models.ObservableCounter;import models.CounterObserver;import controllers.ACounterController;import controllers.CounterController;import views.ACounterConsoleView;public class AConsoleControllerAndView implements CounterInteractor {
public void edit(ObservableCounter model) {CounterObserver view = new ACounterConsoleView();model.addObserver(view);CounterController controller = new ACounterController();controller.setModel(model);controller.processInput();
}}
Must be last action
Console Controller And JOption View Main
package main;import models.AnObservableCounter;import facades.AConsoleControllerAndJOptionView;public class ACounterMain {
public static void main(String[] args) { (new AConsoleContollerAndJOptionView()).edit(new
AnObservableCounter());}
}
AConsoleControllerAndView
Console View + Controller
AnObservableCounter
ACounterController ACounterConsole View
ACounterJOptionView
AConsoleControllerAndJView
Console Controller + JOptionView
AnObservableCounter
ACounterController ACounterConsole View
ACounterJOptionView
AConsoleControllerAndViewAndJOptionViewAConsoleControllerAndView
ACounterJOptionView
(Console Controller + View) + JOption View
AnObservableCounter
ACounterController ACounterConsole View
Facade over facade
package interactors;import models.ObservableCounter;import models.CounterObserver;import views.ACounterJOptionView;public class AConsoleContollerAndViewAndJOptionView implements CounterInteractor {
public void edit(ObservableCounter model) {model.addObserver(new ACounterJOptionView());(new AConsoleContollerAndView()).edit(model);
}}
Main with two views and OEpackage main;import models.AnObservableCounter;import bus.uigen.ObjectEditor;import facades.AConsoleControllerAndViewAndJOptionView;public class ACounterMain { public static void main(String[] args) { ObservableCounter model = new AnObservableCounter(); (new ObjectEditor()).edit(model);
(new ConsoleControllerAndViewAndJOptionView()).edit(model);
}}
Observers that are not views
• Spreadsheet cell observes cells on which it depends.
• Monitoring of appliance usage– Each time I do setChannel() on TV event
logged.
• Any big brother app!
• Counter observer?
Rocket Observer
Rocket added observer before view
AConsoleControllerAndView
Instances created and composed
AnObservableCounter
ACounterController ACounterConsole View
ARocket
ARocketLauncher
Rocket Interface
package models;import models.CounterObserver;public interface Rocket extends CounterObserver {
public void launch() ;}
Rocket Launching Facade
package models;import models.ObservableCounter;public class ARocket implements Rocket {
public void update(ObservableCounter counter) {if (counter.getValue() == 0)
launch();}public void launch() {
System.out.println("LIFT OFF!!!");}
}
Rocket Launching Facade
package interactors;import models.ObservableCounter;import models.CounterObserver;import models.ARocket;import facades.AConsoleContollerAndView;public class ARocketLaunchCountDown implements CounterInteractor {
public final int INITIAL_COUNTER_VALUE = 10;public void edit(ObservableCounter counter) {
counter.add(INITIAL_COUNTER_VALUE);CounterObserver rocket = new ARocket();counter.addObserver(rocket);(new AConsoleContollerAndView()).edit(counter);
}}
Rocket launching mainpackage main;import models.AnObservableCounter;import models.ARocketLauncher;import facades.ARocketLaunchCountDown;public class ACounterMain {
public static void main(String[] args) { (new ARocketLaunchCountDown()).edit(new AnObservableCounter());
}}
Counter Observable and Observer
package models;public interface ObservableCounter extends Counter {
public void addObserver(CounterObserver observer);public void removeObserver(CounterObserver observer);
}
package models;public interface CounterObserver {
public void update(ObservableCounter counter);}
Called whenever model is updated Updated model
Console View, JOption View
Common interface of all views
Basic Notification
Observable 1
• Each observer is registered with observable.
• Each write method in observable calls a notification method in each observer.
•Parameter indicates observervable
•Notification method in observer reads model . Observable 2
Observer 1
Observer 2
Observer 3
Observer N
Basic Notification
package models;public interface CounterObserver {
public void update(ObservableCounter counter);}
Called whenever observer is updated Updated observable
Implicit Observer
package models;public interface CounterObserver {
public void setObservable(ObservableCounter counter);public void update();
}
Implicit observable
Notification with Change Description
package models;public interface CounterObserver {
public void update(ObservableCounter counter, int newCounterVal);}
No need to call read method after notification
Java Observable and Observerpackage java.util;public class Observable {
public void addObserver(Observer observer) {…}’public void deleteObserver(Observer observer) {…}’void notifyObservers(Object arg) {…};…
}
package java.util;public interface Observer {
public void update(Observable o, Object arg);}
A notifying class can subclass from Observable
“Standard” observer interface
Notification with Changed Value
package models;public interface CounterObserver {
public void update(ObservableCounter counter, int newCounterVal);}
New value of observable attribute
• Observer may display change to user.
• Observer interested in change does not need to keep old value to determine change
• Observer interested in absolute value must keep old value
package models;public interface CounterObserver {
public void update(ObservableCounter counter, int counterIncrement);}
Difference between new and old value of observable attribute
Notification with Change
• Observer interested in change does not need to keep old value to determine change
• Observer interested in absolute value need not keep old value
• Makes observable harder to code
package models;public interface CounterObserver {
public void update (ObservableCounter counter, int oldCounterValue, int newCounterValue);}
Notification with New and Old Value
Old and new value of observable attribute
package models;public interface CounterObserver {
public void update(CounterChangeEvent event);}
Notification with Single Event Object
package models;public interface CounterChangeEvent {
ObservableCounter getCounter(); int getOldCounterValue(); int getNewCounterValue();}
• Easy to pass single object to different methods handling event.
• Can make event info very elaborate– Time when event occurred– Unique ID for event– ….
• Don’t have to declare parameters for event information fields not of interest.
• Can return object from a function.
Java TextField and ActionListenerpackage java.awt;public class TextField {
public void addActionListener(ActionListener l) {…};public void removeActionListener(ActionListener l) {…}; …
}
package java.awt.Event;public interface ActionListener {
public void actionPerformed(ActionEvent e);} Button, MenuItem and
other components also define these methods
Should have been put in an interface
Java TextField and ActionListenerpackage java.awt.event;public class ActionEvent extends java.awt.AWTEvent {
public String getActionCommand() {…};public paramString() {…};
long getWhen() {…};….
}
package java.awt.event;public class AWTEvent extends java.util.EventObject {
int getID() {…};….
}
package java.util;public class EventObject {
Object getSource() {…};String toString() {…};
}
Observing multiple attributes
package bmi;public interface BMISpreadsheet {
public double getHeight(); public void setHeight (double newVal); public double getWeight() ;public void setWeight(double newWeight) ;public double getBMI();
}
package bmi;public interface BMIObserver { public void updateHeight (BMISpreadsheet bmi, int oldHeight, int newHeight);
public void updateWeight(BMISpreadsheet bmi, int oldWeight, int newWeight); public void updateBMI(BMISpreadsheet bmi, double oldBMI, double newBMI);}
Multiple update methods
package bmi;public interface BMISpreadsheet {
public double getHeight(); public void setHeight (double newVal); public double getWeight() ;public void setWeight(double newWeight) ;public double getBMI();
}
package bmi;public interface BMIObserver { public void update (BMISpreadsheet bmi, String propertyName, Object oldValue, Object newValue);}
Single update method
package bmi;public interface BMISpreadsheet {
public double getHeight(); public void setHeight (double newVal); public double getWeight() ;public void setWeight(double newWeight) ;public double getBMI();
}
• Not type safe• New methods not
needed as new properties added
• Can be generalized for arbitrary bean objects“Wght”
“One”
package java.beans;public class PropertyChangeEvent extends java.util.EventObject { public PropertyChangeEvent (Object source, String propertyName, Object oldValue, Object newValue) {…} public Object getNewValue() {…} public Object getOldValue() {…} public String getPropertyName() {…} ….}
Property Changes
package java.beans;public interface PropertyChangeListener extends java.util.EventListener { public void propertyChange (PropertyChangeEvent evt);}
package bmi;public class AnObservableBMISpreadsheet extends ABMISpreadsheet{ public void
addPropertyChangeListener(PropertyChangeListener l) { …} …}
Must notify in write methods
Property Changes
• If Object defines– addPropertyChangeListener(PropertyChangeListener l)
{ …}• ObjectEditor automatically calls it to register itself.• Setter method can can now call in listeners
– public void propertyChange PropertyChangeEvent evt);• ObjectEditor reacts by updating display of property
– Does not have to do polling refresh by calling all getter methods when a setter is invoked.
– Can disable this polling refresh by invoking ViewAuto Refresh command
Multiple Observer/Observable Interfaces
• An observable may need to register and notify multiple kinds of observers
• An observer may need to interact with multiple kinds of observables. Observable 1
Observable 2
Observer 1
Observer 2
Observer 3
Observer N
Recommended