Model View Controller Prasun Dewan Comp 114. Model View Controller Pattern Issue –How to create...

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