<<

Design Introduction

Oliver Haase

‘An instrument, a tool, an utensil, whatsoever it be, if it be fit for the purpose it was made for, it is as it should be though he perchance that made and fitted it, be out of sight and gone.’ — Marc Aurel, Roman Emperor, 121 – 180 ad

1 Bibliography The bible for patterns. The authors are widely known as the Gang of Four (GoF), their patterns as GoF patterns.

et al. : Elements of Reusable Object-Oriented Software, Addison–Wesley, 1994, ISBN 78-0201633610. ‣ Elisabeth Freeman et al. Head First Design Patterns, O'Reilly, 2004, ISBN 978-0596007126. ‣ Joshua Bloch. Effective Java, Second Edition, Addison Wesley, 2008, ISBN 978-0-321-35668-0.

2 Not that fast, buddy!

Before we start, let’s do some ground work...

3 Relationships between Classes (and Interfaces)

‣ Specialization and generalization ‣ Association ‣ Aggregation ‣ Composition ‣ Creation

4 Association

Weakest form of relationship: A is related to B ‣ Undirected Association: no information about navigability A B

‣ Unidirectional Association: navigation from instances of A to instances of B A B

‣ Bidirectional Association: navigation both ways A B

‣ in Java: A has reference to (collection of) B, or vice versa

5 Aggregation

‣ Somewhat stronger than an association ‣ Models part-of relationship, e.g. B is part of A:

A B

‣ Example:

Course Student

‣ in Java: A has reference to (collection of) B

6 Composition

‣ Stronger than aggregation ‣ Part-of relationship, with ownership, i.e. lifespan of B depends on A

A B

‣ Example:

Building Room

‣ in Java: A creates (collection of) B, has reference to it/them.

7 Don’t you never forget this again, or...

8 Design Patterns - Definition

Design : “Each [design] pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem" - , 1977. Christopher Alexander: born 1936 in , studied mathematics and architecture (Cambridge), received PhD in architecture (Harvard), professor for architecture (UoC, Berkeley), known as the founder of design patterns

9 Design Patterns - Goals

‣ fast to develop, clean solution through solution reuse ‣ simplified maintenance through clean structures and déjà vu effects ‣ improved extensibility through anticipatory design ‣ (partly) opportunity for code generation or language support (e.g. ) ‣ (partly) opportunity for class libraries (e.g. )

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand." - Martin Fowler. 10 Principles of Reusable Design

According to GoF, there are two fundamental principles of reusable software design (that also underly most if not all patterns):

1st Principle of Reusable Design: Program against an interface, not an implementation.

2nd Principle of Reusable Design: Prefer composition over inheritance.

11 Excursion: 1st Principle in Java How it’s usually done: public interface HelloSayer { String getGreeting(String name); } public class NiceHelloSayer implements HelloSayer { @Override public String getGreeting(String name) { return “hello “ + name + “, how are you?”; } }

Usage: HelloSayer helloSayer = new NiceHelloSayer(); helloSayer.getGreeting(“John Doe”); ...Or: NiceHelloSayer niceHelloSayer = new NiceHelloSayer(); niceHelloSayer.getGreeting(“John Doe”);

12 Excursion: 1st Principle in Java Implementation with nested class (J. Bloch, Effective Java): public final class NiceHelloSayer { private static final class Implementation implements HelloSayer { @Override public String getGreeting(String name) { return "hello " + name + ", how are you?"; } }

public static Implementation getInstance() { return new Implementation(); }

private NiceHelloSayer() { throw new AssertionError(); } }

Usage: HelloSayer helloSayer = NiceHelloSayer.getInstance(); String greeting = helloSayer.getGreeting(“John Doe”); 13 Excursion: 2nd Principle

Inheritance breaks encapsulation, leads to strong coupling between super and subclass.

⇒ Fragile Base Class Problem: Seemingly correct subclass can break when superclass is (correctly) changed.

14 Excursion: 2nd Principle Assume, you want to implement an InstrumentedHashSet that keeps book of the number of attempted add operations:

@ThreadSafe public class InstrumentedHashSet extends HashSet { private int addCount = 0;

public InstrumentedHashSet() {} public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } }

15 Excursion: 2nd Principle

Problem: HashSet internally uses add to implement addAll ⇒ elements added by addAll get counted twice!

16 Excursion: 2nd Principle

Second attempt without overriding addAll:

@ThreadSafe public class InstrumentedHashSet extends HashSet { private int addCount = 0;

public InstrumentedHashSet() {} public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor); } @Override public boolean add(E e) { addCount++; return super.add(e); } public int getAddCount() { return addCount; } }

What if HashSet’s implementation is changed so that addAll does not use add anymore???

17 Excursion: 2nd Principle Solution using Composition and Delegation:

@ThreadSafe public class InstrumentedSet implements Set { private int addCount = 0; private Set s;

public InstrumentedSet(Set s) { this.s = s; }

public int getAddCount() { return addCount; } @Override public boolean add(E e) { addCount++; return s.add(e); } @Override public boolean addAll(Collection c) { addCount += c.size(); return s.addAll(c); } @Override public void clear() { s.clear(); }

// similarly override all other Set methods }

18 Excursion: 2nd Principle

Solution by Composition ‣ agnostic of specific Set implementation, works not only for HashSet ‣ more boilerplate code (due to lack of language support) ‣ InstrumentedSet independent of changes in the specific Set implementation. ‣ loose coupling

19 Excursion: 2nd Principle

Now, from the perspective of extending a threadsafe class while preserving its synchronization strategy.

Example: provide a thread-safe list implementation with additional putIfAbsent method (Goetz et al., Java Concurrency in Practice) ⇒ your check-then-act warning bells should ring!

20 Excursion: 2nd Principle

Solution by Inheritance: Extend synchronized Vector class (Java specifies that Vector synchronizes on its intrinsic )

@ThreadSafe public class BetterVector extends Vector { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(E); if ( absent ) { add(x); } return absent; } } Drawback: If baseclass, Vector, were to change its synchronization policy, subclass’s synchronization policy would breaks ⇒ Another break of encapsulation ⇒ tight coupling 21 Excursion: 2nd Principle Solution by Composition

@ThreadSafe public class ImprovedList implements List { private final List list;

public ImprovedList(List list) { this.list = list; }

public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(E); if ( absent ) { list.add(x); } return absent; }

public synchronized void clear() { list.clear(); }

// … similarly delegate all other list methods }

22 Excursion: 2nd Principle Solution by Composition

‣ agnostic of specific list implementation ‣ extra layer of synchronization, in fact Java monitor pattern ‣ certain performance penalty ‣ more boilerplate code (due to lack of language support) ‣ ImprovedList’s synchronization policy independent of changes in the specific list implementation. ‣ loose coupling

23 Principles of Reusable Design

Key motivation behind both design principles:

Holy Grail of Reusable Software Design: Avoid code dependencies to the largest extent possible.

24 Design Patterns - Categorization

GoF categorize design patterns based on two criteria:

1. Purpose: • creational: patterns for object creation • structural: patterns that compose classes and objects • behavioral: patterns that distribute responsibility for behavior across classes and objects, and make them interact 2. Scope: • class based: based on relationships between classes, mainly inheritance • object based: based on relationships between objects

25 Creational Patterns

“The way to get started is to quit talking and begin doing" - Walt Disney.

26 A Simple, Motivating Example... Consider an oversimplified document manager that can create, open, and close LaTeX documents: @ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager { private final Collection docs;

public DocManager() { docs = new ConcurrentLinkedQueue(); }

public void createDoc() { LatexDoc doc = new LatexDoc(); docs.add(doc); doc.open(); }

public void openDocs() { for ( LatexDoc doc : docs ) doc.open(); } ... } 27 But...

... what about the 1st principle of reusable software design?

Ok, make LatexDoc an implementation of an interface:

Document open() close()

LatexDoc open() close()

28 Then...

Use Document, rather than LatexDoc, in DocManager:

@ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager { private final Collection docs;

public DocManager() { docs = new ConcurrentLinkedQueue(); }

public void createDoc() { Document doc = new LatexDoc(); docs.add(doc); doc.open(); }

public void openDocs() { for ( Document doc : docs ) doc.open(); } ... }

29 Dependency Situation

Now DocManager has become dependent on both LatexDoc and Document:

DocManager Document createDoc() open() openDocs() close() Document doc = new LatexDoc(); docs.add(doc); doc.open(); LatexDoc open() close()

30 So What?

What’s wrong with this creational dependency? ‣ DocManager cannot be used for other document types, e.g. plain text documents, even though its functionality is applicable to other document types as well; ‣ Difficult to use document mock-up for testing; ‣ In a framework, DocManager would not even know - and care about - what document type to create.

31 More Abstractly

Client Product someOperation() use()

Product product = new ConcreteProduct(); ConcreteProduct product.use(); use()

Most creational patterns are about removing the creational dependency between Client and ConcreteProduct.

32