Design Patterns Factory Method Oliver Haase 1 Idea If client - - PowerPoint PPT Presentation

design patterns
SMART_READER_LITE
LIVE PREVIEW

Design Patterns Factory Method Oliver Haase 1 Idea If client - - PowerPoint PPT Presentation

Design Patterns Factory Method Oliver Haase 1 Idea If client knows when to create certain object(s), but doesn't know (neither care) how, then... make client an abstract class; define an abstract method for object creation; leave


slide-1
SLIDE 1

Oliver Haase

Design Patterns

1

Factory Method

slide-2
SLIDE 2

Idea

If client knows when to create certain object(s), but doesn't know (neither care) how, then...

2

  • make client an abstract class;
  • define an abstract method for object creation;
  • leave implementation to concrete subclass(es).

Also known as virtual constructor.

slide-3
SLIDE 3

DocManager Reloaded

3

Originally:

@ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager { private final Collection<Document> docs; public DocManager() { docs = new ConcurrentLinkedQueue<Document>(); } public void createDoc() { Document doc = new LatexDoc(); docs.add(doc); doc.open(); } public void openDocs() { for ( Document doc : docs ) doc.open(); } ... }

slide-4
SLIDE 4

DocManager Reloaded

4

With factory method:

@ThreadSafe // assuming that concrete Document is threadsafe public abstract class DocManager { private final Collection<Document> docs; public DocManager() { docs = new ConcurrentLinkedQueue<Document>(); } protected abstract Document newDocument(); public void createDoc() { Document doc = newDocument(); docs.add(doc); doc.open(); } public void openDocs() { for ( Document doc : docs ) doc.open(); } ... }

slide-5
SLIDE 5

DocManager Reloaded

5 @ThreadSafe // assuming that LatexDoc is threadsafe public class LatexDocManager extends DocManager { @Override protected Document newDocument() { return new LatexDoc(); } }

Concrete subclass LatexDocManager:

slide-6
SLIDE 6

Structure

6

newDocument() createDoc()

  • penDocs()

DocManager Document doc = newDocument(); docs.add(doc); doc.open();

  • pen()

close() Document

  • pen()

close() LatexDoc newDocument() LatexDocManager return new LatexDoc();

slide-7
SLIDE 7

Structure - in General

7

factoryMethod() someOperation() Creator Product product = factoryMethod(); product.use(); use() Product use() ConcreteProduct factoryMethod() ConcreteCreator return new ConcreteProduct();

superclass/interface of products to be created implementation of concrete product

  • defines factory method
  • uses factory method to create products
  • may provide default implementation

implements factory method such that concrete product gets created

slide-8
SLIDE 8

Comparison: Starting Point

8

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

slide-9
SLIDE 9

Misc

9

  • Pro: Simple technique to decouple client from object

creation

  • Con: Creator class must be extended to override a single

method ⇒ might lead to bloated class hierarchy

  • Relationship with other patterns: Abstract factories

usually use factory methods that are implemented by concrete factories.

slide-10
SLIDE 10

Naming Conventions

  • get<Type>: returns an instance that is described by the

input params; can be either an existing or a new instance

  • new<Type>: as above; guarantees that each invocation

return a new instance

10

slide-11
SLIDE 11

Caveat

Don't confuse with static factory methods that a class provides to instantiate itself. ⇒ Alternative to direct constructor invocation, see Joshua Bloch: Effective Java, 2nd Edition, item 1. Naming convention for static factory methods:

  • getInstance: see get<Type>
  • newInstance: see new<Type>

11

slide-12
SLIDE 12

Excercise

Use the factory method pattern to remove the dependencies

  • n ConcreteBufferHolder and ConcreteUdpTask.

12 @ThreadSafe // because DatagramSocket and Executor are threadsafe public class UdpServer { private static final int POOL_SIZE = 10; private final DatagramSocket udpSocket; private final ExecutorService executor; public UdpServer(int port) throws SocketException { udpSocket = new DatagramSocket(port); executor = Executors.newFixedThreadPool(POOL_SIZE); } public void start() throws IOException { while ( true ) { BufferHolder bufferHolder = new ConcreteBufferHolder(); byte[] buffer = bufferHolder.getBuffer(); DatagramPacket packet = new DatagramPacket(buffer, buffer.length); udpSocket.receive(packet); executor.execute(new ConcreteUdpTask(packet)); } } }

slide-13
SLIDE 13

Prototype

13

… Somewhere in the deeply remote past it seriously traumatized a small random group of atoms drifting through the empty sterility of space and made them cling together in the most extraordinarily unlikely patterns. These patterns quickly learnt to copy themselves (this was part of what was so extraordinary about the patterns) and went on to cause massive trouble on every planet they drifted on to. That was how life began in the Universe … - Douglas Adams, The Hitchhiker's Guide to the Galaxy.

slide-14
SLIDE 14

Idea

If client knows when to create certain object(s), but doesn't know (neither care) how, then...

14

. . . provide client with prototype of the object that can be cloned to get more instances.

slide-15
SLIDE 15

DocManager Reloaded

15

Originally:

@ThreadSafe // assuming that LatexDoc is threadsafe public class DocManager { private final Collection<Document> docs; public DocManager() { docs = new ConcurrentLinkedQueue<Document>(); } public void createDoc() { Document doc = new LatexDoc(); docs.add(doc); doc.open(); } public void openDocs() { for ( Document doc : docs ) doc.open(); } ... }

slide-16
SLIDE 16

DocManager Reloaded

16

With prototype:

@ThreadSafe // assuming that concrete Document is threadsafe public class DocManager { private final Collection<Document> docs; private final Document prototype; public DocManager(Document prototype) { docs = new ConcurrentLinkedQueue<Document>(); this.prototype = prototype; } public void createDoc() { Document doc = prototype.clone(); docs.add(doc); doc.open(); } public void openDocs() { for ( Document doc : docs ) doc.open(); } ... }

slide-17
SLIDE 17

DocManager Reloaded

17

DocManager docManager = new DocManager(new LatexDoc()); docManager.createDoc();

Sample usage of prototype-based DocManager:

slide-18
SLIDE 18

Structure

18

  • pen()

close() clone() Document

  • pen()

close() clone() LatexDoc Document doc = prototype.clone(); docs.add(doc); doc.open(); return copy of itself createDoc()

  • penDocs()

prototype: Document DocManager

slide-19
SLIDE 19

General Structure

19

use() clone() Product use() clone() ConcreteProduct Product product = prototype.clone(); product.use(); return copy of itself someOperation() prototype: Product Client

implementation

  • f concrete

product, incl. clone

  • peration

superclass/interface of products to be created,

  • incl. declaration of

clone operation

  • owns a product prototype
  • creates new products by asking the prototype to clone itself
slide-20
SLIDE 20

20

How does prototype get into client? ⇒ out of scope of this pattern!

slide-21
SLIDE 21

21

Pros & Cons

  • Pro:
  • avoids bloating of type hierarchy, i.e. no client subclass

(factory method), no factory interfaces (→abstract factories)

  • client can be dynamically configured with new prototype
  • Con:
  • product must support clone operation
  • difficult or impossible if product already exists
  • correct implementation of clone operation difficult, clone

concept in Java seriously flawed ⇒ see later

  • if products with various settings are needed, product type

must provide setters even if it could otherwise be immutable

slide-22
SLIDE 22

Relationship with Other Patterns

  • Abstract factories usually use factory methods that are

implemented by concrete factories

  • Alternative option: concrete factory gets prototype for each

product type and uses cloning to create new products ⇒ fewer types, more flexibility, less type safety

22

slide-23
SLIDE 23

Java’s Clone Concept

  • class Object contains a protected clone method
  • a subclass can only use Object.clone() if it implements

the Cloneable marker interface, otherwise

Object.clone() throws CloneNotSupportedException

  • cloneable subclass must provide public clone method

which is not enforced by Cloneable

  • Object.clone
  • returns a copy of the right type, i.e. the object’s runtime

type

  • makes a shallow copy of all fields, i.e. each field is copied

into new instance

23

slide-24
SLIDE 24

Java’s Clone Concept

To implement clone method,

  • call super.clone()
  • replace shallow copies of own fields with deep copies as

needed; usually done with fields’ clone methods

24

works only if each supertype in the hierarchy adheres to this behavior!

slide-25
SLIDE 25

Cloning: Example I

25 @Immutable public final class PhoneNumber implements Cloneable { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(short areaCode, short prefix, short lineNumber) { this.areaCode = areaCode; this.prefix = prefix; this.lineNumber = lineNumber; } ... @Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch ( CloneNotSupportedException e) { throw new AssertionError(); // Can’t happen } } }

all cloning code samples from J. Bloch: Effective Java, Second Edition.

slide-26
SLIDE 26

Cloning: Example II

26 @Threadsafe public final class Stack implements Cloneable { private Object[] elements; private int size = 0; private static final int INITIAL_CAPACITY = 16; public Stack() { this.elements = new Object[INITIAL_CAPACITY]; } public synchronized void push(Object e) { ensureCapacity(); elements[size++] = e; } public synchronized Object pop() { if ( size == 0 ) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; return result; }

slide-27
SLIDE 27

Cloning: Example II, cont’d

27

private void ensureCapacity() {

if ( elements.length == size ) elements = Arrays.copyOf(elements, 2 * size +1); } @Override public synchronized Stack clone() { try { Stack result = (Stack) super.clone(); result.elements = elements.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); // Can’t happen } } }

Note that

  • cloned stack has its own copy of elements array that

references same objects as original stack’s elements array;

  • the above wouldn’t work if elements were final!
slide-28
SLIDE 28

Cloning: Example III

28

public final class HashTable implements Cloneable { private Entry[] buckets; private static class Entry { final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } } public HashTable(int size) { buckets = new Entry[size]; } ... }

slide-29
SLIDE 29

Cloning: Example III, cont’d

29

@Override public HashTable clone() {

try { HashTable result = (HashTable) super.clone(); result.buckets = buckets.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); // Can’t happen } } }

Broken, because cloned buckets array references same linked lists as original array!

clone method, first try:

slide-30
SLIDE 30

Cloning: Example III, cont’d

30

private static class Entry { final Object key; Object value; Entry next; Entry(Object key, Object value, Entry next) { this.key = key; this.value = value; this.next = next; } Entry deepCopy() { return new Entry(key, value, next == null ? null: next.deepCopy()); } }

Add deepCopy method to Entry class:

slide-31
SLIDE 31

Cloning: Example III, cont’d

31

@Override public HashTable clone() {

try { HashTable result = (HashTable) super.clone(); result.buckets = new Entry[buckets.length]; for ( int i = 0; i < buckets.length; i++ ) if ( buckets[i] != null ) result.buckets[i] = buckets[i].deepCopy(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); // Can’t happen } } }

Alternatively,

  • 1. call super.clone()
  • 2. set all fields to their initial values;

3.use high-level operations to reconstruct state

clone method, second try:

slide-32
SLIDE 32

Java Cloning - Summary

32

  • J. Bloch advises to generally avoid clone but to use copy

constructor instead ⇒ not directly suitable for prototype patterns because Client does not know prototype’s runtime type ⇒ declare clone/copy operation in Product, have each

ConcreteProduct implement clone/copy directly, e.g.

through copy constructor

slide-33
SLIDE 33

‘Own’ Cloning: Example I

33 public final class PhoneNumber implements Product { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(short areaCode, short prefix, short lineNumber) { this.areaCode = areaCode; this.prefix = prefix; this.lineNumber = lineNumber; } public PhoneNumber(PhoneNumber pn) { this(pn.areaCode, pn.prefix, pn.lineNumber); } ... @Override public PhoneNumber copy() { return new PhoneNumber(this); } }

slide-34
SLIDE 34

‘Own’ Cloning: Example II

34

public final class Stack implements Product { private Object[] elements; private int size = 0; private static final int INITIAL_CAPACITY = 16; public Stack() { this.elements = new Object[INITIAL_CAPACITY]; } public Stack(Stack s) { elements = s.elements.clone(); } ... @Override public synchronized Stack copy() { return new Stack(this); } }

slide-35
SLIDE 35

Synchronization

If ConcreteProduct is to be thread-safe, clone operation needs to be synchronized like any other method.

35

slide-36
SLIDE 36

Discussion

  • Prototype pattern is flexible, dynamically configurable, and

doesn’t bloat type hierarchy

  • Rather don’t use Java’s cloning concept but provide your
  • wn, independent cloning, preferably based on copy

constructors (consistent with final concept)

  • Prototype pattern not suitable if objects with different

states are needed (requires setters for otherwise immutable fields)

36

slide-37
SLIDE 37

Excercise

Use the prototype pattern to remove the dependencies on

ConcreteBufferHolder and ConcreteUdpTask.

37 @ThreadSafe // because DatagramSocket and Executor are threadsafe public class UdpServer { private static final int POOL_SIZE = 10; private final DatagramSocket udpSocket; private final ExecutorService executor; public UdpServer(int port) throws SocketException { udpSocket = new DatagramSocket(port); executor = Executors.newFixedThreadPool(POOL_SIZE); } public void start() throws IOException { while ( true ) { BufferHolder bufferHolder = new ConcreteBufferHolder(); byte[] buffer = bufferHolder.getBuffer(); DatagramPacket packet = new DatagramPacket(buffer, buffer.length); udpSocket.receive(packet); executor.execute(new ConcreteUdpTask(packet)); } } }