Review of Course/Syllabus
Download
Report
Transcript Review of Course/Syllabus
Creational Design Patterns
Yaodong Bi
July 18, 2015
Creational design patterns
Factory
Abstract Factory
Prototype
Singleton
Builder
Factory
Problem
– The client needs to create an object of a
subclass, but it does not know the subclass
until runtime.
Design Purpose
– Create individual objects in situations
where the constructor alone is inadequate.
Design Pattern Summary
– Use methods to return required objects
Factory – an example
void useStack(Stack s) {
while (condition) {
stackn = new Stack(); //?????
}
}
Stack = new MyStack();
useStack(s); //?????
Client
+useStack(Stack)
stack
+push()
+pop()
MyStack
+push()
+pop()
Factory - structure
void useProduct(Creator c) {
Product p = c.factoryMethod();
// use p
}
….
Creator c = new ConcreteCreator();
useProduct(c);
Client
+useProduct()
Product
Concrete
Product
Creator
+factoryMethod()
Concrete
Creator
+factoryMethod()
Product factorMethod() {
Return new
ConcreteProduct();
}
Factory - participants
Product
– defines the interface of objects the factory method creates.
ConcreteProduct
– implements the Product interface.
Creator
– declares the factory method, which returns an object of type
Product. Creator may also define a default implementation of the
factory method that returns a default ConcreteProduct object.
– Creator and Product could be the same class
ConcreteCreator
– overrides the factory method to return an instance of a
ConcreteProduct.
Client
– It depends only on Product and Creator.
Collaborations
– Creator relies on its subclasses to define the factory method so
that it returns an instance of the appropriate ConcreteProduct.
Factory – sequence diagram
Client
Creator
factoryMethod()
op()
Concrete
Creator
factoryMethod()
Product
constructor()
Concrete
Product
op()
Factory – sample code
Class StackClient {
void opUsingStacks(StackCreator
sc) {
Stack s1 = sc.createStack();
...
Stack si = sc.createStack();
...
Stack sn = sc.createStack();
}
}
Interface Stack {
void push(Item i);
Item pop();
}
Interface StackCreator {
Stack createStack();
}
Class MyStack implements Stack {
...
}
Class YourStack implements Stack {}
Class MyStackCreator implements
StackCreator {
Stack createStack() {
return new MyStack();
}
}
Class YourStackCreator implements
StackCreator {
Stack createStack() {
return new YourStack();
}
}
Class ClientDriver {
void main() {
StackClient c = new StackClient();
StackCreator sc = new
MyStackCreator();
c.opUsingStacks(sc);
sc = new YourStackCreator();
c.opUsingStacks(sc);
}
}
Factory – comments
Factory methods are necessary for the OCP on
object creation
Factory methods make the DIP more viable
Factory Method gives subclasses a hook for
providing an extended version of an object
Creator could be a concrete class providing a
default implementation of Product
Creator and Product could be the same class
There is an advantage of using a separate creator
class for the product
– The product and its creation are separate concerns
– When the product is very complex, the creator can create
products (even the first one) as demanded – lazy loading
Abstract Factory
Design purpose
– Provide an interface for creating families
of related or dependent objects without
specifying their concrete classes
Pattern summary
– Capture family creation in a class
containing a factory method for each
class in the family
Abstract Factory – examples
Word processor for different window systems
– Word processor is designed to a set of interfaces of graphical
elements
– Each windows system manufactures its own set of button,
windows, dialogs, etc
– Each installation of the word processor is given a windows
system (concrete factory)
Kitchen showroom
– The program is designed to a common set of cabinets, counters,
etc
– Each different style of kitchen furniture provide the furniture in
the style (classic, contemporary, etc)
– Each individual kitchen showroom is given a style (concrete
factory)
GUI themes
Abstract Factory - structure
ProductA
Factory
+createProductA()
+createproductB()
ProductA1
ProductA2
Client
Factory1
ProductB
ProductB1
ProductB2
+createProductA()
+createproductB()
ProductA createProductA() {
Return new
ProductA1();
}
Factory2
+createProductA()
+createproductB()
ProductB createProductB() {
Return new
ProductB1();
}
Abstract Factory – Two Families
Factory1
+createProductA()
+createproductB()
ProductA1
ProductB1
Factory2
+createProductA()
+createproductB()
ProductA2
ProductB2
Abstract Factory - participants
ProductA and ProductB
– defines the interface of a family of objects.
ProductA1, ProductB1, ProductA2, and ProductB2
– Two separate families of product implementation.
Factory
– Defines the interface of the factory that returns the products
Factory1 and Factory2
– Concrete factories that can produce products of the family.
– Factory1 produces ProductA1 and ProductB1, Factory2 produces
ProductA2 and ProductB2
Client
– It depends only on the interfaces of the family products and the
factory interface.
Collaborations
– The client asks a subclass of Factory to create concrete products
of ProductA and productB.
Abstract Factory – Two Families
Data Structures Via Array
DSFactoryViaArray
+createStack()
+createQueue)
StackViaArray
QueueViaArray
Data Structures Via List
DSFactoryViaList
+createStack)
+createQueue()
StackViaList
QueueViaList
Abstract Factory - structure
Stack
DSFactory
+createStack()
+createQueue()
StackViaArray
StackViaList
DataStructsClient
DSFactoryViaArray
+createStack()
+createQueue)
DSFactoryViaList
+createStack)
+createQueue()
Queue
Stack createStack (){
return new
StackViaArray(); }
QueueViaArray
Stack createStack() {
return new
StackViaList(); }
QueueViaList
Queue createQueue (){
return new
QueueViaArray();}
Queue createQueue (){
return new
QueueViaList();}
Abstract Factory – sample code
Class DataStructsClient {
DSFactory af;
DataStructsClient(
DSFactory af) {
this.af = af;
}
void op() {
Stack s1 = af.createStack();
Stack s2 = af.createStack();
...
Queue q = af.createQueue();
...
Tree t = af.createTree();
}
}
Interface DSFactory {
Stack createStack();
Queue createQueue();
Tree createTree();
}
Interface Stack {...}
Interface Queue {...}
Interface Tree {...}
Class StackViaArray
implements Stack {...}
Class QueueViaArray
implements Queue {...}
Class TreeViaArray
implements Tree {...}
Class DSFactoryViaArray implements
DSFactory {
Stack createStack() {
return new StackViaArray();
}
Queue createQueue() {
return new QueueViaArray();
}
Tree createTree() {
return new TreeViaArray();
}
}
Abstract Factory – sample code
Class StackViaList
implements Stack {...}
Class QueueViaList
implements Queue {...}
Class TreeViaList
implements Tree {...}
Class DSFactoryViaList implements
DSFactory {
Stack createStack() {
return new StackViaList();
}
Queue createQueue() {
return new QueueViaList();
}
Tree createTree() {
return new TreeViaList();
}
}
Class ClientDriver {
void main() {
// Using Array-based structures
DSFactory af =
new DSFactoryViaArray();
DataStructsClient dsc =
new DataStructsClient(af);
dsc.op();
// Using list-based structures
DSFactory af2 =
new DSFactoryViaList();
DataStructsClient dsc2 =
new DataStructsClient(af2);
dsc2.op();
}
}
Abstract Factory – comments
Use the Abstract Factory when a client needs to use
one of multiple families of products
It is hard to add new types of products since
adding a new product means adding a new factory
method to the AbstractFactory interface and its all
subclasses
When families of products are different
combinations of the same set of products and/or
the # of families is large, many ConcreteFactory
subclasses would be needed. In this case, the
Prototype pattern may be employed
Prototype
Design purpose
– Create a set of almost identical objects
whose type is determined at runtime
Pattern summary
– Assume that a prototype instance is
known; clone it whenever a new instance
is needed
Prototype – examples
Kitchen showroom
– A showroom may have all cabinets in one
style, counters in a different style, etc
– Instead of creating a factory for each
possible combination of different furniture
in different styles, each individual
showroom is given a prototype of each
type of furniture of a style
– Each prototype can clone itself
Prototype - structure
Client
Prototype
prototype
+clone()
+otherOperations()
+anOperation()
Prototype p =
prototype.clone();
ConcreteProduct1
+clone();
+otherOperations();
Return a copy of itself
ConcreteProduct2
+cone();
+otherOperations();
Return a copy of itself
Prototype - participants
Prototype
– defines the interface (an operation) of cloning
itself.
ConcreteProduct1 and ConcreteProduct2
– Concrete objects that can clone themselves.
Client
– Obtain more objects by asking them to clone
themselves.
Collaborations
– The client asks the prototype to clone itself for a
new object of the prototype.
Shallow vs. deep cloning (copying)
Client
prototype
+anOperation()
Client
prototype
Deep cloning
:Prototype
prototype
clone:Prototype
:Nested
+op()
-data
ref
+clone()
-Nested:ref
Shallow cloning
+anOperation()
ref
+clone()
-Nested:ref
+anOperation()
Client
:Prototype
:Nested
+op()
-data
ref
+clone()
-Nested:ref
:Prototype
ref
+clone()
-Nested:ref
clone:Prototype
+clone()
-Nested:ref
:Nested
+op()
-data
ref
clone:Nested
+op()
-data
Shallow vs. deep cloning
Shallow cloning: sample code
Deep cloning: sample code
Class Prototype implements Cloneable {
private int x;
private Nested ref = new Nested();
Class Prototype implements Cloneable {
private int x;
private Nested ref = new Nested();
public Prototype clone() {
Prototype p = new Prototype()
p.x = this.x;
p.ref = this.ref.clone();
return p;
}
}
Class Nested implements Cloneable {
int data;
public void op() {}
public Nested clone() {
Nested n = new Nested()
n.data = this.data;
return n;
}
}
Class Client {
// the same Client as for Shallow cloning
}
public Prototype clone() {
Prototype p = new Prototype()
p.x = this.x;
p.ref = this.ref;
return p;
}
}
Class Nested {
int data;
public void op() {}
}
Class Client {
private Prototype prototype =
new Prototype();
public void op() {
Prototype clone = prototype.clone();
// use clone
}
Abstract Factory – Two Families
Data Structures Via Array
StackViaArray
QueueViaArray
Data Structures Via List
Stack
StackViaList
Queue
QueueViaList
Prototype – sample code
Class PrototypeClient {
Stack sp;
Queue qp;
PrototypeClient(
Stack s, Queue q) {
this.s = s;
this.q = q;
}
void op() {
Stack s1 = sp.clone();
Stack s2 = sp.clone();
...
Queue q1 = qp.clone();
Queue q2 = qp.clone();
}
}
Interface Queue {
Queue clone();
...
}
Interface Stack {
Stack clone();
...
}
Class StackViaArray implements Stack {
Stack clone() {
StackViaArray s =
new StackViaArray();
s.attr = this.attr;
return s;
}
...
}
Class StackViaList implements Stack {
Stack clone() {
StackViaList s =
new StackViaList();
s.attr = this.attr;
return s;
}
...
}
Prototype – sample code
Class QueueViaArray
implements Queue {
Queue clone() {
QueueViaArray s =
new QueueViaArray();
s.attr = this.attr;
return s;
}
...
}
Class QueueViaList
implements Queue {
Queue clone() {
QueueViaList s =
new QueueViaList();
s.attr = this.attr;
return s;
}
...
}
Interface Queue {
Queue clone();
...
}
Class ClientDriver {
void main() {
Stack s = new StackViaArray();
Queue q = new QueueViaList();
PrototypeClient pc =
new PrototypeClient(s, q);
pc.op();
}
}
Prototype – comments
If the clone does not need to be
identical as its original, a factory
method may be used
Be aware of the shallow cloning
problem – to the deepest level
When prototypes can be grouped in a
small # of different combinations, the
Abstract Factory may be suitable
Singleton
Design purpose
– Ensure there is exactly one instance of a
class
– Be able to obtain the instance from
anywhere in the application
Pattern summary
– Make the constructor of class private or
protected, define a private static attribute
of the class, define a public accessor to it.
Singleton – an example
A concrete factory in the Abstract
Factory pattern in most cases should
have only one instance – all objects
are produced by the same factory
In JGrasp (or any IDE), we want one
JVM (Singleton) for all Java programs.
Singleton - structure
Singleton
+static getInstance();
+operations()
-data
return soleInstance;
soleInstance
<<static>>
Singleton - participants
Singleton
– Declare all constructors private and provide only
one entry for obtaining a reference to the sole
instance.
Client
– Clients can get to the sole instance of Singleton
by asking Singleton to return a reference to it.
Collaborations
– A client can only call getInstance() to get a
reference to the sole instance.
– A client cannot create any instance of Singleton
Singleton – sample code
class Singleton {
// since private no client can access
// this reference directly
private static Singleton soleInstance
= new Singleton();
private int data;
// since protected, no client can
// create any instance directly
protected Singleton();
// the only entry for clients to get a
// reference to the sole instane
public static Singleton getInstance()
{
return soleInstance;
}
// other operations
public void operations() { }
}
class Client1 {
void anOperation() {
// Singleton ref = new Singleton();
// illegal since Singleton() is protected
Singleton ref = Singleton.getInstance();
ref.operations();
}
}
class Client2 {
void anOperation() {
Singleton ref = Singleton.getInstance();
// use ref
}
}
NOTE: objects of Client1 and Client2
would all share the same sole
instance of Singleton.
Singleton – comments
Lazy loading
– If the object of Singleton is complex, it may take much resource to
create. We better create the object only when it is referenced – lazy
loading
– Change the body of getInstance() with the following
if (soleInstance == null) {
soleInstance = new Singleton();
}
return soleInstance;
Not thread safe (Java)
– Two concurrent threads could cause two instances created if
executed concurrently
– Solution: Make getInstance() synchronized
All static members?
– Since there is only one instance, why don’t we just make all
members static?
– If yes, then the instance may not fit with the rest of the application:
e.g., display(Employee) would not work if singleton CEO is all static
Builder
Design purpose
– Separate the construction of a complex
object from its representation so that the
same construction process can create
different representations
Pattern summary
– Use a builder to encapsulate the
representation.
Builder – an example
Language translator
– Translate a Java programs to other
programming languages (C++, Delphi)
– The translation process is the same for all
target languages – mapping “import” to
something (“include” in C++)
– Each target language has a builder to
handle each keyword/structure
Builder - structure
Client
+madeProduct()
director
Director
builder
+buildProduct()
For (every part needed in product)
if (part A)
builder.buildPartA();
else if (part B)
builder.buildPartB();
else if (part C)
builder.buildPartC();
}
Builder
+buildPartA()
+buildPartB()
+buildPartC()
BuilderA
+buildPartA()
+buildPartB()
+buildPartC()
+getProductA()
ProductA
BuilderB
+buildPartA()
+buildPartB()
+buildPartC()
+getProductB()
ProductB
Builder - participants
ProductA and ProductB
– Concrete products that are created by different builders.
Director
– A class that knows what steps it takes to build a product, but it does not
know how each step is to be carried out or does not know how each part
may be added to the final product
Builder
– Defines an interface for concrete builders
BuildlerA and BuilderB
– Concrete builders who know to construct each part of the product and
add it to the final product
Client
– A client selects a director and a concrete builder to build the product it
needs. The client asks the concrete builder to return the final constructed
product
Collaborations
– The director knows what parts are needed for the final product and the
selected concrete builder knows how to product the part and add it to
the final product.
Builder – sequence diagram
Client
Director
builder = constructor()
Concrete
Builder
constructor()
constructor(builder)
buildProduct()
buildPartA()
buildPartB()
buildPartC()
getProduct()
addPartA()
addPartB()
addPartC()
Product
Builder – sample code
Class Product {
addPartA() {}
addPartB() {}
addPartC() {}
…
}
Class ConcreteBuilder
implements Builder {
Private: Product p;
}
ConcreteBuilder() {
p = new Product();
}
buildPartA() {… p.addPartA(); … }
buildPartB() {… p.addPartB(); … }
buildPartC() {… p.addPartC(); … }
Product getResult() { return p; }
Class Director {
private Builder b;
Director(Builder b) {this.b = b;}
void buildProduct() {
for each part in Product {
if (partA) b.buildPartA();
else if (partB) b.buildPartB();
else if (partC) b.buildPartC();
}
}
}
client() {
Builder b = new ConcreteBuilder();
Director d = new Director(b);
d.buildProduct()
FinalProduct fp = b.getResult();
}
Creational design patterns
Factory
Abstract Factory
Prototype
Singleton
Builder