Compiler Design Spring 2018 4.X Patterns (again) Thomas R. Gross Computer Science Department ETH Zurich, Switzerland 1
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
Patterns § Singleton: 27% § Factory: 27% § Visitor: 22% Not a pattern: § Object-oriented programming § Design by contract § Functional programming § Lazy loading § Inheritance 3
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
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 5 § F is defined so that the correct format is chosen
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
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 }
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 }
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 }
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
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
Other advantage: Extensibility § At all places where formatting is needed § Error-prone § High risk of introducing inconsistent behavior 12
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
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
The Strategy pattern § You’ve just seen an example of the Strategy pattern Strategy Context algorithmInterface() contextInterface() ConcreteStrategy1 ConcreteStrategy2 ConcreteStrategy3 algorithmInterface() algorithmInterface() algorithmInterface() 15
The Strategy pattern § You’ve just seen an example of the Strategy pattern Print MyDate print() // direct access to fields BankFormatPrint Int’lFormatPrint JapaneseFormatPrint print() print() print() 16
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
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
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
MyDate An alternative print() BankFormatDate Int’lFormatDate JapaneseFormatDate print() print() print() 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 20 complicated
Working with data structures § MyDate: Only three integer fields § Direct access to fields sufficient to print date Print MyDate print() // direct access to fields BankFormatPrint Int’lFormatPrint JapaneseFormatPrint print() print() print() 21
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
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
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
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
Visitor // Data structure abstract class ArithExpr { abstract int accept(Visitor v); } // Visitor interface Visitor { int forConst(Const c); int forSum(Sum s); } 26
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
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
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); } }
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)); }
Visitor {interface} visitConcreteElem(ce: Element) ConcreteVisitor visitConcreteElem(ce: ConcreteElement) ObjectStructure Element {interface} accept(v: Visitor) ConcreteElement accept(v: Visitor) { accept(v: Visitor) v.visitConcreteElem(this) }
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 }
Discussion (cont’d) § Should we always use the Visitor pattern for programs operating 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
Recommend
More recommend