<<

Design patterns

OOD Lecture 6 Next lecture

• Monday, Oct 1, at 1:15 pm, in 1311 • Remember that the poster sessions are in two days – Thursday, Sep 27 • 1:15 or 3:15 pm (check which with your TA) • Room 2244 + 2245 The object design activity

Select sub-system

Specification Re-use

Specifying types & signatures Specifying visibility Identifying missing attributes & operations Identifying components Adjusting components

Specifying constraints Specifying exceptions Adjusting patterns Identifying patterns

Check use cases

Restructuring Optimization

Revisiting inheritanc Optimizing access paths

Collapsing classes Caching complex computations

Realizing associations Delaying complex computations What are ?

• Software problems generally have more than one solution: How do you choose which to use? – Patterns give guidance in the form of solution types based on experience • Patterns are also communication tools: “I’m using pattern X” instead of lengthy descriptions of solution details. Communication abstraction • Design patterns are not designs — only helpful hints Pattern elements

• A name • Description of a typical problem • The elements that make up the pattern: Classes, relations, responsibilities, collaborations • Consequences: Pros, cons, trade-offs • An example solution The concept of design patterns • Nothing new in itself. Patterns are to a large degree verbalized and externalized experience, no matter what your discipline • The “Gang of Four book” (GoF) started people thinking more in these terms for s/w design – Gamma, Erich, Richard Helm, Ralph Johnson, John Vlissides (1995) Design Patterns: Elements of Reusable Object-Oriented Software. Addison- Wesley. ISBN 978-0-2016-3361-0 • Inspired by architectural theory: (1977) A Pattern Language: Towns, Buildings, Construction

“Classic” patterns

• Creational patterns – Abstract factory, Builder, Factory method, Singleton, Prototype • Structural patterns – Adapter, Bridge, Composite, Decorator, Façade, Flyweight, Proxy • Behavioural patterns – Chain of Responsibility, Command, Interpreter, , Mediator, Memento, Observer, State, Strategy, Template method, Visitor Factory method • Problem – Hard-coding class names in code makes code less flexible • Damages re-use, changes might require multiple consistent updates – Testing: E.g. stubbing production data – Shielding user from complex creation, possibly involving data the user shouldn’t have access to • Solution – Decouple creation point from class name – Use separate method for instance creation – Encapsulates the places needing changes – Multiple pluggable creators with common interface Factory method UML

«interface» Creator +factoryMethod() : Product

Product ConcreteCreator +factoryMethod() : Product

• DIP + OCP (+ ISP + LSP), decoupling • (technique for achieving dependency inversion) • Dependency defined by Creator • Injector/Provider/Container = ConcreteCreator • Dependent = whatever uses Product Factory method: Before class Bonniers { void publish(String title, String author, String text) { … String isbn = kb.lookup(title, author); Book b = new ISBNBook(title, author, isbn); … } } public class ISBNBook { public ISBNBook(String title, String author, String isbn) { … } … } Factory method: After public interface BookCreator { «interface» Book makeBook(String title, String author); Creator } +factoryMethod() : Product public class BooksWithISBN implements BookCreator { Product ConcreteCreator @override +factoryMethod() : Product Book makeBook(String title, String author) { String isbn = kb.lookup(title, author); return new ISBNBook(title, author, isbn); } } // different package class Bonniers { BookCreator bc; void publish(String title, String author, String text) { ... Book b = bc.makeBook(title, author); ... } } Factory method Other uses • Also used to encapsulate object creation when it’s complex or it’s convenient to delay decision class ImageReaderFactory { static ImageReader getImageReader(InputStream is) { switch(determineImageType(is)) { case ImageReaderFactory.GIF: return new GifReader(is); case ImageReaderFactory.JPEG: return new JpegReader(is); // … } } } Factory method Limitations • Introducing factory methods might break existing clients • Reduces coupling by increasing complexity

• Generalization of

«interface» «uses» Client AbstractFactory +createProductA() : AbstractProductA +createProductB() : AbstractProductB «uses» «interface» AbstractProductA

ConcreteFactory1 «instantiates» ProductA1 ProductA2

+createProductA() : ProductA1 +createProductB() : ProductB1 «instantiates» ConcreteFactory2 «uses»

+createProductA() : ProductA2 «interface» +createProductB() : ProductB2 AbstractProductB

«instantiates»

ProductB1 ProductB2

«instantiates» Composite • Description: Represent a hierarchy of variable width and depth so that leaves and composites can be uniformly accessed through a common interface • Examples – Syntax trees for arithmetical expressions (such as (((x+2)*y)+3)*(z+1)) – Organizational chart – In general, anything that can be seen as tree structured Composite Example

Domain object Solution object • Instance diagram

Add

x + (y * 2) x Multiply

y 2 Composite Solution

Structure Participants • Component – Provides the common interface Component * – May provide interface to both

parent parent and children (e.g. evaluate for arithmetic expressions) • Composite Leaf Composite leaves – Primitive with children – Implements behaviour for 1 composite objects • Leaf – Primitive w/out children Navigability – uni- or bidirectional – not part of – Implements behaviour for the pattern terminal objects Composite Example in code interface Expression { int evaluate(Map environment); } class Add implements Expression { private Expression lhs; private Expression rhs; public Add(Expression l, Expression r) { lhs = l; rhs = r; } public int evaluate(Map environment) { return lhs.evaluate(environment) + rhs.evaluate(environment); } }

C o m p o n e n t * class Integer implements Expression {

private int value; p a r e n t public Integer(int v) { value = v; } public int evaluate(Map _env) { return value; } }

l e a v e s class Variable implements Expression { L e a f C o m p o s i t e

private String name; 1 public Variable(String n) { name = n; } public int evaluate(Map env) { return environment.get(name); } } Composite Consequences and issues • Pro – Simplifies the client – Easy to add new kinds of components (e.g. division) • Contra – Recursive composition of objects • Behaviour depends on composition • Issues – How to provide access to children? • In Component (Expression) or in Composite (Add etc) • Pseudo-problem – Explicit parent references? – Sharing components? – Child ordering? Proxy Structural pattern • Expose only a subset of an object interface to clients • Improve the performance or the security of a system by delaying expensive computations, using memory only when needed, or checking access before loading an object into memory Proxy Solution

Structure Participants • Subject Subject Client attribute1 – The interface that clients see attribute2 operation1() • RealObject operation2() – Class with expensive computations or security RealObject ProxyObject attribute1 issues attribute1 attribute2 attribute2 moreAttributes • operation1() 1 0..1 operation1() ProxyObject operation2() operation2() moreOperations() – Acts on behalf of RealObject until the expensive computation is needed, when it delegates to the real object Proxy Consequences • Pro: The Client is shielded from any optimizations for handling RealObjects • Contra: Adds a level of indirection between Client and RealObject (i.e. increases complexity) Proxy Example in code

Subject Client interface AlgebraServer { attribute1 int calculate(Expression expression); attribute2 } operation1() operation2() class RemoteServer implements AlgebraServer{ int calculate(Expression expression) { ... } } RealObject ProxyObject attribute1 attribute1 attribute2 class CachingProxy implements AlgebraServer { attribute2 moreAttributes HashMap cache = new HashMap(); operation1() 1 0..1 operation1() RemoteServer server; operation2() operation2() moreOperations() int calculate(Expression expression) { int result;

if (cache.hasKey(expression)) { result = cache.get(expression); } else { result = server.calculate(expression); cache.put(expression, new Value(result)); }

return result; } } Strategy Behavioural pattern • Decouple a policy-deciding class from a set of mechanisms so that different mechanisms can be changed transparently from a client Library example • University library lending policies – Normal book, no outstanding requests: • Undergraduate borrower: 2 weeks • Everyone else: 4 weeks – Normal book, outstanding request: 1 week – Reference book: Cannot be borrowed – New book: 1 week – Reserved book: 3 days Strategy pattern Library example: Implementation #1 int getLoanLength(Book b, Patron p) { if (p.isReserved()) { return 3; } if (b.isNormalBook()) { if (p.hasOutstandingRequest()) { return 7;} if (p.isUnderGraduate()) { return 14; } else { return 28; } } if(p.isNewBook()) { return 7; } return 0; // is reference book } • Logic implementation in single method – Inflexible, as logic is hard-coded – Results in ugly code that is hard to read Strategy pattern Library example: Design #2

Book -loanLength : unsigned short(idl) +getLoanLength() : unsigned short(idl)

NewBook ReferenceBook ReserveBook -loanLength : unsigned short(idl) = 7 -loanLength : unsigned short(idl) = 0 -loanLength : unsigned short(idl) = 3

• Logic spread among special subclasses – Neater, but – Very inflexible • Every time a book is reserved, it has to change class Strategy pattern

• Problem – Many related classes differ only in their behaviour – OR need custom behaviour for individual instances – OR class has many behaviours implemented through many conditional statements • Solution – Define interface abstracting algorithm – Each algorithm variant encapsulated into separate class implementing algorithm interface – Context class calls strategy object to perform algorithm Strategy pattern Solution

Structure Participants • Strategy: Interface that abstracts algorithm «uses» Client Context «interface» Strategy • Concrete strategy: A class 1 1..* for each algorithm variant that implements the ConcreteStrategyA ConcreteStrategyB interface • Context: A class that calls the Strategy object to perform the algorithm Strategy pattern Library example, solution #3 UML

«uses» Client Book «interface» LendingPolicy 1 1..* +getLoanLength()

NewBookPolicy ReferencePolicy

+getLoanLength() +getLoanLength()

NormalPolicy ReservedPolicy

+getLoanLength() +getLoanLength() Strategy pattern Library example, solution #3 code class Book { // Context LendingPolicy policy; … int loanLength(Patron p) { return policy.loanLength(p); } } interface LendingPolicy { int loanLength(Patron p); } // Strategy class NewBookPolicy implements LendingPolicy { // Concrete strategy int loanLength(Patron p) { return 7; } } class ReferencePolicy implements LendingPolicy { // Concrete strategy int loanLength(Patron p) {return 0; } } class NormalPolicy implements LendingPolicy { // Concrete strategy int loanLength(Patron p) {

if (p.isUnderGraduate()) { return 14; } «uses» Client Context «interface» else { return 28; } Strategy }} 1 1..*

ConcreteStrategyA ConcreteStrategyB Strategy pattern However • Some wouldn’t agree with that definition of the strategy pattern, but would rather use this Policy

«uses» Client Context «interface» Strategy 1 1..*

ConcreteStrategyA ConcreteStrategyB

• where the Policy determines which concrete strategy that Context uses Strategy pattern Consequences • Pro – ConcreteStrategies can be substituted transparently from Context – New algorithms can be added without modifying Context or Client • Contra – Increased complexity (especially in the variant with a separate Policy object) Summary of remaining patterns Creational • Abstract factory – Encapsulating platforms • Builder – Separate the construction of a complex object from its representation allowing the same construction process to create various representations. Fits Composites well. Incremental + director • Prototype – Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. • Singleton – Ensure a class has only one instance, and provide a global point of access to it.

Summary of remaining patterns Structural • Adapter – Convert the interface of a legacy class into a different interface expected by the client, so that the client and the legacy class can work together without changes • Bridge (cf Strategy p) – Decouple an interface from an implementation so that implementations can be substituted, possibly at runtime. • Decorator – Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to sub- classing for extending functionality. • Façade – Reduce coupling between a set of related classes and the rest of the system • Flyweight – Factor out common data into separate objects with “pointer” – Cf database design

Decorator pattern example Summary of remaining patterns Behavioural • Chain of Responsibility • Messages passed along extensible chain of processors • Command • Encapsulate requests so that they can be executed, undone, or queued independently of the request • Interpreter • Behavioural variant on • Iterator • Sequential access to aggregate elements • Mediator • Class encapsulating object interactions -> • Memento • Originator – memento – caretaker. E.g. pseudo-RNG • Observer (Publish/Subscribe) • Maintain consistency across the states of one Publisher and many Subscribers • State • Cf Strategy and Bridge. Interface attribute – changeable subclass value • Template method • Algorithm steps overridden by subclasses • Visitor • Separate operations from structure. Fits Composites well