SLIDE 1 Compiler Design
Spring 2018
4.X Patterns (again)
1
Thomas R. Gross Computer Science Department ETH Zurich, Switzerland
SLIDE 2 Why?
§ Remember questionnaire (from the beginning of the semester)? § Question 7: “Please name three design patterns that you encountered in earlier projects/classes.” § Answers
§ Complete (3+ patterns): 33% § Blank: 55%
2
SLIDE 3 Patterns
§ Singleton: 27% § Factory: 27% § Visitor: 22% Not a pattern:
§ Object-oriented programming § Design by contract § Functional programming § Lazy loading § Inheritance
3
SLIDE 4 Patterns – some technical reasons
§ Control flow always difficult to deal with § Patterns like Strategy or Iterator raise the level of programming
§ Hide details § Easier to read than explicit control flow statements § Simpler to write § Easier to extend/understand/maintain
4
SLIDE 5 Trivial example
§ Print date using different formats § Option 1: Test at each place in the program P where the date is to be printed
switch(format): { case (INTERNATIONAL): // print YEAR-MONTH-DAY break; case (BANK): // print MONTH-DAY-YEAR break; ... }
§ Option 2: Invoke function/method F passed to P
§ F is defined so that the correct format is chosen
5
SLIDE 6 Auxiliary classes
import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Date; class MyDate { // Won’t work in long-lived programs int day; int month; int year; MyDate() { Calendar cal = new GregorianCalendar(); year = cal.get(cal.YEAR); month = cal.get(cal.MONTH); day = cal.get(cal.DAY_OF_MONTH); } }
6
SLIDE 7 Select format 1 (international)
interface Print { void print(); } class InternationalFormatPrint implements Print { MyDate d_; InternationalFormatPrint(MyDate d) { d_ = d; } public void print() { System.out.println(d_.day + "/" + d_.month + "/" + d_.year ); } }
7
SLIDE 8 Select format 2 (bank)
class BankFormatPrint implements Print { MyDate d_; BankFormatPrint (MyDate d) { d_ = d; } public void print() { System.out.println(d_.month + "/" + d_.day +"/" + d_.year ); } }
8
SLIDE 9 Example
public static void main(String args[]) { MyDate d = new MyDate(); Print p = selectPrinter(d); doThePrinting(p); } static Print selectPrinter(Date d) { if (user.input() == ‘b‘) { return new BankFormatPrint(d); } else { return new InternationalFormatPrint(d); } } static void doThePrinting(Print service) { service.print(); }
9
SLIDE 10 Output
Test6> java Test6 30/1/2018 Test6> java Test6 1/30/2018 § Not a big deal
§ (This is a simple example after all)
§ But the idea is powerful
§ No need to deal with a possibly complicated switch-statement § Only passing the printer instance as parameter is needed
10
SLIDE 11 Other advantage: Extensibility
§ Imagine adding support for a new format, e.g., Japanese § With Option 1: Extending switch statement is required
switch(format): { case (INTERNATIONAL): // print YEAR-MONTH-DAY break; case (BANK): // print MONTH-DAY-YEAR break; case (JAPANESE): // print YEAR-MONTH-DAY break; }
11
SLIDE 12 Other advantage: Extensibility
§ At all places where formatting is needed
§ Error-prone § High risk of introducing inconsistent behavior
12
SLIDE 13 Other advantage: Extensibility
§ Option 2: Requires only adding a new implementation for the Print interface
class JapaneseFormatPrint implements Print { MyDate d_; JapaneseFormatPrint(MyDate d) { d_ = d; } public void print() { System.out.println(d_.year + "/" + d_.month +"/" + d_.day ); } }
§ Where new formatting is needed: Pass a JapaneseFormatPrint instance to existing code
13
SLIDE 14 public static void main(String args[]) { MyDate d = new MyDate(); Print p = selectPrinter(d); doThePrinting(p); } static Print selectPrinter(Date d) { if (user.input() == ‘b‘) { return new BankFormatPrint(d); } else if (user.input() == ‘j‘){ return new JapaneseFormatPrint(d); } else { return new InternationalFormatPrint(d); } } static void doThePrinting(Print service) { service.print(); }
14
SLIDE 15 The Strategy pattern
§ You’ve just seen an example of the Strategy pattern
15
Context contextInterface() Strategy algorithmInterface() ConcreteStrategy3 algorithmInterface() ConcreteStrategy2 algorithmInterface() ConcreteStrategy1 algorithmInterface()
SLIDE 16 The Strategy pattern
§ You’ve just seen an example of the Strategy pattern
16
MyDate
// direct access to fields
Print print() JapaneseFormatPrint print() Int’lFormatPrint print() BankFormatPrint print()
SLIDE 17 The Strategy pattern
§ Idea: Encapsulate a set of related algorithms into a class hierarchy
§ Benefit: Simplify control-flow (e.g., reduce the need for conditional statements) § Benefit: Algorithm can be changed independently of context § Benefit: Decouple implementation of algorithm from data structure(s) in context
17
SLIDE 18 Is Strategy really that great?
§ Should we always use Strategy when our program uses at least two different algorithms on one data structure?
§ It depends
§ A pattern is a solution that works well in some settings
§ But not always § You have to weight pros/cons
18
SLIDE 19 Pros/cons
§ A switch-statement is not that bad after all…
§ If you have a program with two algorithms and a data structure § A program will not be changed/extended § Don’t forget: Change is rather the norm and not an exception
§ Implementing Strategy (often) requires interface/virtual calls
§ (Some) VM engineers: Don’t use virtual calls, they’re expensive § Good for performance, maintenance cost paid later
19
SLIDE 20 An alternative
§ Advantage: Simpler class structure § Disadvantage: Mixes MyDate and algorithms that operate on it § Can become a problem if algorithms and/or data structure in MyDate complicated
20
MyDate print() JapaneseFormatDate print() Int’lFormatDate print() BankFormatDate print()
SLIDE 21 Working with data structures
§ MyDate: Only three integer fields
§ Direct access to fields sufficient to print date
21
MyDate
// direct access to fields
Print print() JapaneseFormatPrint print() Int’lFormatPrint print() BankFormatPrint print()
SLIDE 22 Working with complex data structures
§ What should we do if the data structure is complex?
§ We could use an iterator to enumerate all elements
22
SLIDE 23 Working with complex data structures
§ Example: list of dates
class JapaneseFormatPrint implements Print { List<MyDate> list_; JapaneseFormatPrint(List<MyDate> list) { list_ = list; } public void print() { Iterator iter; while (iter.hasNext()) { MyDate d = iter.next() System.out.println(d_.year + "/" + d_.month +"/" + d_.day ); } } }
23
SLIDE 24 What about accessing even more complex data structures?
§ There may not be an iterator § The order of processing/invoking a method may matter
§ (Just think of trees)
§ Visitor pattern internalizes the logic of “visiting” the elements
24
SLIDE 25 Visitor
§ Coupling of data structure and access method(s) § Powerful in connection with trees § Object must be prepared to “accept” a visitor § Example: arithmetic expressions
§ Sums § Constants
25
SLIDE 26 Visitor
// Data structure abstract class ArithExpr { abstract int accept(Visitor v); } // Visitor interface Visitor { int forConst(Const c); int forSum(Sum s); }
26
SLIDE 27 class Const extends ArithExpr { private int value; Const (int v) { value = v; } int getValue() { return value; } int accept(Visitor v) { return v.forConst(this); } }
27
SLIDE 28 class Sum extends ArithExpr { ArithExpr left, right; Sum (ArithExpr l, ArithExpr r) { left = l; right = r; } ArithExpr getLeft() { return left;} ArithExpr getRight() { return right;} int accept(Visitor v) { return v.forSum(this); } }
28
SLIDE 29
Visitor examples
class EvalVisitor implements Visitor { public int forConst(Const c) { return c.getValue(); } public int forSum(Sum s) { return s.getLeft().accept(this) + s.getRight().accept(this); } } class CountVisitor implements Visitor { public int forConst(Const c) { return 1;} public int forSum(Sum s) { return s.getLeft().accept(this) + s.getRight()).accept(this); } }
SLIDE 30
Example, continued
class ShowArithVisitors { public static void main(String[] args) { ArithExpr c2, c3, s1, s2; c2 = new Const(2); c3 = new Const(3); s1 = new Sum(c2,c3); s2 = new Sum(c2, s1); Visitor v1 = new EvalVisitor(); Visitor v2 = new CountVisitor(); System.out.println(s2.accept(v1)); System.out.println(s2.accept(v2)); }
SLIDE 31 ObjectStructure ConcreteVisitor visitConcreteElem(ce: ConcreteElement) Visitor {interface} visitConcreteElem(ce: Element) ConcreteElement accept(v: Visitor) Element {interface} accept(v: Visitor)
accept(v: Visitor) { v.visitConcreteElem(this) }
SLIDE 32 Discussion
§ Who’s responsible for traversing the object structure? § In our case: the visitor
class EvalVisitor implements Visitor { … public int forSum(Sum s) { return s.getLeft().accept(this) + s.getRight().accept(this); } }
§ Could be the object structure as well
class Sum extends ArithExpr { … int accept(Visitor v) { return getLeft().accept(v) + getRight().accept(v); } }
32
SLIDE 33 Discussion (cont’d)
§ Should we always use the Visitor pattern for programs
- perating on tree-like object structures?
§ It depends.
§ Visitor beneficial if:
§ Object structure contains objects with different (unrelated) functionality
§ E.g., Const represents a value via the field value Sum is an arithmetic expression with fields left and right
33
SLIDE 34 class Const extends ArithExpr { private int value; Const (int v) { value = v; } int getValue() { return value; } int accept(Visitor v) { return v.forConst(this); } }
34
SLIDE 35 class Sum extends ArithExpr { ArithExpr left, right; Sum (ArithExpr l, ArithExpr r) { left = l; right = r; } ArithExpr getLeft() { return left;} ArithExpr getRight() { return right;} int accept(Visitor v) { return v.forSum(this); } }
35
SLIDE 36 Discussion (cont’d)
§ Should we always use the Visitor pattern for programs
- perating on tree-like object structures?
§ It depends.
§ Visitor beneficial if:
§ Object structure contains objects with different (unrelated) functionality
§ E.g., Const represents a value via the field value Sum is an arithmetic expression with fields left and right
§ Object structure changes rarely but new operations are added often
§ E.g., JavaLi compiler: IR changes rarely, various operations implemented in visitors
36
SLIDE 37 Summary
§ Design pattern: A possible way to design (part of) your program
§ (Some) benefits: Simpler code, extensibility, maintainability § Not a general solution: You have to weigh pros/cons
§ Patterns we’ve talked about: Strategy, Iterator (briefly), Visitor § More to come…
37
SLIDE 38
Observer
§ Original problem: need to show an object i and want to update each view upon a change § Example: table with values
§ Text § Bar graph § Pie chart
§ If there is a change: Need to redraw the figures
§ Allow changes only to text... § Modify text in response to changes to the figures
SLIDE 39 Multiple views
20 40 60 80 100 1st Qtr 2nd Qtr 3rd Qtr 4th Qtr North West East 1s 1st Qt Qtr 2n 2nd Qt Qtr 3r 3rd Qt Qtr 4t 4th Qt Qtr Ea East 2 0 . 2 0 . 4 2 7 . 2 7 . 4 9 0 9 0 2 0 . 2 0 . 4 Wes West 3 0 . 3 0 . 6 3 8 . 3 8 . 6 3 4 . 3 4 . 6 3 1 . 3 1 . 6 No North rth 4 5 . 4 5 . 9 4 6 . 4 6 . 9 4 5 4 5 4 3 . 4 3 . 9 10 20 30 40 50 60 70 80 90 100 1st Qtr 2nd Qtr 3rd Qtr 4th Qtr East West North
SLIDE 40
Basic idea
§ The table object (base object) is connected with the presentation objects (text, bar chart, pie chart, etc.)
§ All these objects are independent
§ Each presentation object is notified when there is a change to the base object § Changes result in events
§ Notification: send a message § Sending a message: invoking a method
SLIDE 41
Observer
§ Base object: subject § Presentation object: observer
§ Informed about updates by subject
§ Observers register with the subject that they want to be informed
§ A method “update” performs any necessary steps § Must allow way to de-register
SLIDE 42 Observer
Subject Observer attach(Observer) detach(Observer) notify() update() for all obj in
{
} ConcreteSubject getState() setState() ConcreteObserver update()
subject.getState( )
*
s
subject return subjectState
SLIDE 43 Observer in action
Concrete Subject Concrete Observer 1 Concrete Observer 2
setState() notify() update() getState() update() getState()
SLIDE 44 Another example: Working with collections
§ Iterator delivers objects (references to objects) § Methods (defined in class for instances) define operations
§ You want to allow extension
§ Introduce sub-class, override method § Provide hook for externally defined functions
§ Apply a function to all elements of a set/collection/...
47
SLIDE 45 Practice
§ Apply a function F to all elements of a set/collection/...
§ Provide hook for externally defined functions
§ Example of “high order functions”
§ Function F is the “variable”
48
SLIDE 46 Practice
§ Given: some class C for objects we want to work with
§ Nodes in a tree, records of persons, ....
§ A hook (map) in class C that allows us to call methods
§ Including the function F § Hook must allow parameters
§ An interface I that prescribes how method is to be invoked
§ Interface must contain a method (call it “mapIt”)
§ For Java we need to put the definition of function F into a class (I_F)
§ Implementation of mapIt
§ No need to extend C, must only implement an interface
49
SLIDE 47 Example
§ Nodes in a list
§ Class C: DList (double-linked list of nodes) § DList declares “map“
50
SLIDE 48 List
class Node { private Object value_; private Node next_; private Node prev_; Node(Object obj) { value_ = obj; prev_ = null; next_ = null; } void setValue (Object obj) { value_ = obj;} Object getValue () { return value_;} void setNext (Node next) { next_ = next; } void setPrev (Node prev) { prev_ = prev;} Node getNext () { return next_; } Node getPrev () { return prev_; } }
51
SLIDE 49 Class C: DList
// Double-linked list class DList { private Node head_; private Node tail_; DList() { head_ = null; tail_ = null; } void map(FunctionHandle f) { Iterator i_ = createFwdIterator(); for (i_.first(); !i_.isDone(); i_.next()) { Node node_ = (Node) i_.getCurrent(); f.mapIt(node_); } } // map // ... }
52
SLIDE 50 Example
§ Nodes in a list
§ Class C: DList (double-linked list of nodes) § DList declares “map“
§ Interface I: FunctionHandle
§ Declares “mapIt”
54
SLIDE 51 Interface I and class I_F
interface FunctionHandle { void mapIt(Node node); }
// Print elements class Function1 implements FunctionHandle { public void mapIt(Node node) { System.out.println(node.getValue()); } } // Increment (Integer) elements class Function2 implements FunctionHandle { public void mapIt(Node node) { node.setValue(new Integer( (((Integer) node.getValue()).intValue()) + 1) ); } }
55
SLIDE 52 Example
§ Nodes in a list
§ Class C: DList (double-linked list of nodes) § DList declares “map“
§ Interface I: FunctionHandle
§ Declares “mapIt”
§ Classes I_F define functions you want invoke: Function1 and Function2
§ Function1: print node value § Function2: increment node value
56
SLIDE 53 Interface I and class I_F
interface FunctionHandle { void mapIt(Node node); }
// Print elements class Function1 implements FunctionHandle { public void mapIt(Node node) { System.out.println(node.getValue()); } } // Increment (Integer) elements class Function2 implements FunctionHandle { public void mapIt(Node node) { node.setValue(new Integer( (((Integer) node.getValue()).intValue()) + 1) ); } }
57
SLIDE 54 Use
public class Map2Example { public static void main(String args[]) { DList list = new DList(); // Initialize list Iterator iter = list.createFwdIterator(); FunctionHandle fct1 = new Function1(); FunctionHandle fct2 = new Function2(); list.map(fct1); list.map(fct2); } }
§ Compact § Control flow is hidden
58