420 likes | 433 Views
Learn about various creational design patterns such as Factory, Abstract Factory, Prototype, Singleton, and Builder. Understand the problems they solve and how to implement them.
E N D
Creational Design Patterns Yaodong Bi December 20, 2019
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 Client +useStack(Stack) stack +push() +pop() void useStack(Stack s) { while (condition) { stackn = new Stack(); //????? } } Stack = new MyStack(); useStack(s); //????? MyStack +push() +pop()
Factory - structure void useProduct(Creator c) { Product p = c.factoryMethod(); // use p } …. Creator c = new ConcreteCreator(); useProduct(c); Client +useProduct() Product Creator +factoryMethod() Product factorMethod() { Return new ConcreteProduct(); } Concrete Creator +factoryMethod() Concrete Product
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 Concrete Creator Client Creator Product factoryMethod() factoryMethod() constructor() Concrete Product op() 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
Client Abstract Factory - structure ProductA Factory +createProductA() +createproductB() ProductA1 ProductA2 Factory1 +createProductA() +createproductB() Factory2 +createProductA() +createproductB() ProductB ProductB createProductB() { Return new ProductB1(); } ProductA createProductA() { Return new ProductA1(); } ProductB1 ProductB2
Abstract Factory – Two Families Factory1 +createProductA() +createproductB() Factory2 +createProductA() +createproductB() ProductA1 ProductB1 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 Data Structures Via List DSFactoryViaArray +createStack() +createQueue) DSFactoryViaList +createStack) +createQueue() StackViaArray QueueViaArray StackViaList QueueViaList
DataStructsClient Abstract Factory - structure Stack DSFactory +createStack() +createQueue() StackViaArray StackViaList DSFactoryViaArray +createStack() +createQueue) DSFactoryViaList +createStack) +createQueue() Queue Stack createStack (){ return new StackViaArray(); } Stack createStack() { return new StackViaList(); } QueueViaArray QueueViaList Queue createQueue (){ return new QueueViaArray();} Queue createQueue (){ return new QueueViaList();}
Abstract Factory – sample code 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(); } } 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(); }
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 clientneeds 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
Client +anOperation() Prototype - structure Prototype +clone() +otherOperations() prototype ConcreteProduct1 +clone(); +otherOperations(); ConcreteProduct2 +cone(); +otherOperations(); Prototype p = prototype.clone(); Return a copy of itself 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.
Client +anOperation() Client +anOperation() Client +anOperation() Shallow vs. deep cloning (copying) prototype ref :Prototype +clone() -Nested:ref :Nested +op() -data prototype ref :Prototype +clone() -Nested:ref :Nested +op() -data ref clone:Prototype +clone() -Nested:ref Shallow cloning prototype ref :Prototype +clone() -Nested:ref :Nested +op() -data ref clone:Prototype +clone() -Nested:ref clone:Nested +op() -data Deep cloning
Shallow cloning: sample code 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; 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 } Shallow vs. deep cloning Deep cloning: sample code 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 }
Abstract Factory – Two Families Data Structures Via Array Data Structures Via List StackViaArray Stack StackViaList QueueViaArray 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 soleInstance <<static>> return soleInstance;
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
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() { } } Singleton – sample code 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
Director +buildProduct() Client +madeProduct() Builder - structure Builder +buildPartA() +buildPartB() +buildPartC() director builder BuilderA +buildPartA() +buildPartB() +buildPartC() +getProductA() BuilderB +buildPartA() +buildPartB() +buildPartC() +getProductB() For (every part needed in product) if (part A) builder.buildPartA(); else if (part B) builder.buildPartB(); else if (part C) builder.buildPartC(); } ProductA 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 Concrete Builder Product builder = constructor() constructor() constructor(builder) buildProduct() buildPartA() addPartA() buildPartB() addPartB() buildPartC() addPartC() getProduct()
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; } } Builder – sample code 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