Creational Design Patterns Yaodong Bi 22 November 2020
Creational Design Patterns Yaodong Bi 22 November 2020
Creational design patterns Factory n Abstract Factory n Prototype n Singleton n Builder n
Factory n Problem – The client needs to create an object of a subclass, but it does not know the subclass until runtime. n Design Purpose – Create individual objects in situations where the constructor alone is inadequate. n Design Pattern Summary – Use methods to return required objects
Factory – an example void use. Stack(Stack s) { while (condition) { stackn = new Stack(); //? ? ? } } Stack = new My. Stack(); use. Stack(s); //? ? ? Client +use. Stack(Stack) stack +push() +pop() My. Stack +push() +pop()
Factory - structure void use. Product(Creator c) { Product p = c. factory. Method(); // use p } …. Creator c = new Concrete. Creator(); use. Product(c); Client +use. Product() Product Concrete Product Creator +factory. Method() Concrete Creator +factory. Method() Product factor. Method() { Return new Concrete. Product(); }
Factory - participants n Product n Concrete. Product n Creator n Concrete. Creator n Client n Collaborations – defines the interface of objects the factory method creates. – implements the Product interface. – 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 Concrete. Product object. – Creator and Product could be the same class – overrides the factory method to return an instance of a Concrete. Product. – It depends only on Product and Creator. – Creator relies on its subclasses to define the factory method so that it returns an instance of the appropriate Concrete. Product.
Factory – sequence diagram Client factory. Method() op() Creator Concrete Creator factory. Method() Product constructor() Concrete Product op()
Factory – sample code Class Stack. Client { void op. Using. Stacks(Stack. Creator sc) { Stack s 1 = sc. create. Stack(); . . . Stack si = sc. create. Stack(); . . . Stack sn = sc. create. Stack(); } } Interface Stack { void push(Item i); Item pop(); } Interface Stack. Creator { Stack create. Stack(); } Class My. Stack implements Stack {. . . } Class Your. Stack implements Stack {} Class My. Stack. Creator implements Stack. Creator { Stack create. Stack() { return new My. Stack(); } } Class Your. Stack. Creator implements Stack. Creator { Stack create. Stack() { return new Your. Stack(); } } Class Client. Driver { void main() { Stack. Client c = new Stack. Client(); Stack. Creator sc = new My. Stack. Creator(); c. op. Using. Stacks(sc); sc = new Your. Stack. Creator(); c. op. Using. Stacks(sc); } }
Factory – comments n n n 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 n Design purpose – Provide an interface for creating families of related or dependent objects without specifying their concrete classes n Pattern summary – Capture family creation in a class containing a factory method for each class in the family
Abstract Factory – examples n Word processor for different window systems n Kitchen showroom n GUI themes – 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) – 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)
Abstract Factory - structure Product. A Factory +create. Product. A() +createproduct. B() Product. A 1 Product. A 2 Client Factory 1 +create. Product. A() +createproduct. B() Product. B 1 Product. B 2 Product. A create. Product. A() { Return new Product. A 1(); } Factory 2 +create. Product. A() +createproduct. B() Product. B create. Product. B() { Return new Product. B 1(); }
Abstract Factory – Two Families Factory 1 +create. Product. A() +createproduct. B() Product. A 1 Product. B 1 Factory 2 +create. Product. A() +createproduct. B() Product. A 2 Product. B 2
Abstract Factory - participants n Product. A and Product. B n Product. A 1, Product. B 1, Product. A 2, and Product. B 2 n Factory 1 and Factory 2 n Client n Collaborations – defines the interface of a family of objects. – Two separate families of product implementation. – Defines the interface of the factory that returns the products – Concrete factories that can produce products of the family. – Factory 1 produces Product. A 1 and Product. B 1, Factory 2 produces Product. A 2 and Product. B 2 – It depends only on the interfaces of the family products and the factory interface. – The client asks a subclass of Factory to create concrete products of Product. A and product. B.
Abstract Factory – Two Families Data Structures Via Array DSFactory. Via. Array +create. Stack() +create. Queue) Stack. Via. Array Queue. Via. Array Data Structures Via List DSFactory. Via. List +create. Stack) +create. Queue() Stack. Via. List Queue. Via. List
Abstract Factory - structure Stack DSFactory +create. Stack() +create. Queue() Stack. Via. Array Stack. Via. List Data. Structs. Client DSFactory. Via. Array +create. Stack() +create. Queue) Queue. Via. Array Queue. Via. List Stack create. Stack (){ return new Stack. Via. Array (); } Queue create. Queue (){ return new Queue. Via. Arra y(); } DSFactory. Via. List +create. Stack) +create. Queue() Stack create. Stack() { return new Stack. Via. List (); } Queue create. Queue (){ return new Queue. Via. Li st(); }
Abstract Factory – sample code Class Data. Structs. Client { DSFactory af; Data. Structs. Client( DSFactory af) { this. af = af; } void op() { Stack s 1 = af. create. Stack(); Stack s 2 = af. create. Stack(); . . . Queue q = af. create. Queue(); . . . Tree t = af. create. Tree(); } } Interface DSFactory { Stack create. Stack(); Queue create. Queue(); Tree create. Tree(); } Interface Stack {. . . } Interface Queue {. . . } Interface Tree {. . . } Class Stack. Via. Array implements Stack {. . . } Class Queue. Via. Array implements Queue {. . . } Class Tree. Via. Array implements Tree {. . . } Class DSFactory. Via. Array implements DSFactory { Stack create. Stack() { return new Stack. Via. Array(); } Queue create. Queue() { return new Queue. Via. Array(); } Tree create. Tree() { return new Tree. Via. Array(); } }
Abstract Factory – sample code Class Stack. Via. List implements Stack {. . . } Class Queue. Via. List implements Queue {. . . } Class Tree. Via. List implements Tree {. . . } Class DSFactory. Via. List implements DSFactory { Stack create. Stack() { return new Stack. Via. List(); } Queue create. Queue() { return new Queue. Via. List(); } Tree create. Tree() { return new Tree. Via. List(); } } Class Client. Driver { void main() { // Using Array-based structures DSFactory af = new DSFactory. Via. Array(); Data. Structs. Client dsc = new Data. Structs. Client(af); dsc. op(); // Using list-based structures DSFactory af 2 = new DSFactory. Via. List(); Data. Structs. Client dsc 2 = new Data. Structs. Client(af 2); dsc 2. op(); } }
Abstract Factory – comments n n n 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 Abstract. Factory 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 Concrete. Factory subclasses would be needed. In this case, the Prototype pattern may be employed
Prototype n Design purpose – Create a set of almost identical objects whose type is determined at runtime n Pattern summary – Assume that a prototype instance is known; clone it whenever a new instance is needed
Prototype – examples n 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() +other. Operations() +an. Operation() Prototype p = prototype. clone(); Concrete. Product 1 +clone(); +other. Operations(); Return a copy of itself Concrete. Product 2 +cone(); +other. Operations(); Return a copy of itself
Prototype - participants n Prototype – defines the interface (an operation) of cloning itself. n Concrete. Product 1 and Concrete. Product 2 – Concrete objects that can clone themselves. n Client – Obtain more objects by asking them to clone themselves. n Collaborations – The client asks the prototype to clone itself for a new object of the prototype.
Shallow vs. deep cloning (copying) Client prototype Shallow cloning +an. Operation() Deep cloning : Prototype prototype clone: Prototype : Nested +op() -data ref +clone() -Nested: ref +an. Operation() 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(); 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 Stack. Via. Array Queue. Via. Array Data Structures Via List Stack. Via. List Queue. Via. List
Prototype – sample code Class Prototype. Client { Stack sp; Queue qp; Prototype. Client( Stack s, Queue q) { this. s = s; this. q = q; } void op() { Stack s 1 = sp. clone(); Stack s 2 = sp. clone(); . . . Queue q 1 = qp. clone(); Queue q 2 = qp. clone(); } } Interface Queue { Queue clone(); . . . } Interface Stack { Stack clone(); . . . } Class Stack. Via. Array implements Stack { Stack clone() { Stack. Via. Array s = new Stack. Via. Array(); s. attr = this. attr; return s; }. . . } Class Stack. Via. List implements Stack { Stack clone() { Stack. Via. List s = new Stack. Via. List(); s. attr = this. attr; return s; }. . . }
Prototype – sample code Class Queue. Via. Array implements Queue { Queue clone() { Queue. Via. Array s = new Queue. Via. Array(); s. attr = this. attr; return s; }. . . } Class Queue. Via. List implements Queue { Queue clone() { Queue. Via. List s = new Queue. Via. List(); s. attr = this. attr; return s; }. . . } Interface Queue { Queue clone(); . . . } Class Client. Driver { void main() { Stack s = new Stack. Via. Array(); Queue q = new Queue. Via. List(); Prototype. Client pc = new Prototype. Client(s, q); pc. op(); } }
Prototype – comments If the clone does not need to be identical as its original, a factory method may be used n Be aware of the shallow cloning problem – to the deepest level n When prototypes can be grouped in a small # of different combinations, the Abstract Factory may be suitable n
Singleton n Design purpose – Ensure there is exactly one instance of a class – Be able to obtain the instance from anywhere in the application n 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 n In JGrasp (or any IDE), we want one JVM (Singleton) for all Java programs. n
Singleton - structure Singleton +static get. Instance(); +operations() -data return sole. Instance; sole. Instance <<static>>
Singleton - participants n Singleton – Declare all constructors private and provide only one entry for obtaining a reference to the sole instance. n Client – Clients can get to the sole instance of Singleton by asking Singleton to return a reference to it. n Collaborations – A client can only call get. Instance() 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 sole. Instance = 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 get. Instance() { return sole. Instance; } // other operations public void operations() { } } class Client 1 { void an. Operation() { // Singleton ref = new Singleton(); // illegal since Singleton() is protected Singleton ref = Singleton. get. Instance(); ref. operations(); } } class Client 2 { void an. Operation() { Singleton ref = Singleton. get. Instance(); // use ref } } NOTE: objects of Client 1 and Client 2 would all share the same sole instance of Singleton.
Singleton – comments n 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 get. Instance() with the following if (sole. Instance == null) { sole. Instance = new Singleton(); } return sole. Instance; n Not thread safe (Java) n All static members? – Two concurrent threads could cause two instances created if executed concurrently – Solution: Make get. Instance() synchronized – 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 n Design purpose – Separate the construction of a complex object from its representation so that the same construction process can create different representations n Pattern summary – Use a builder to encapsulate the representation.
Builder – an example n 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 +made. Product() director Director builder +build. Product() For (every part needed in product) if (part A) builder. build. Part. A(); else if (part B) builder. build. Part. B(); else if (part C) builder. build. Part. C(); } Builder +build. Part. A() +build. Part. B() +build. Part. C() Builder. A +build. Part. A() +build. Part. B() +build. Part. C() +get. Product. A() Product. A Builder. B +build. Part. A() +build. Part. B() +build. Part. C() +get. Product. B() Product. B
Builder - participants n Product. A and Product. B n Director n Builder n Buildler. A and Builder. B n Client n Collaborations – Concrete products that are created by different builders. – 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 – Defines an interface for concrete builders – Concrete builders who know to construct each part of the product and add it to the final product – 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 – 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) build. Product() build. Part. A() build. Part. B() build. Part. C() get. Product() add. Part. A() add. Part. B() add. Part. C() Product
Builder – sample code Class Product { add. Part. A() {} add. Part. B() {} add. Part. C() {} … } Class Concrete. Builder implements Builder { Private: Product p; } Concrete. Builder() { p = new Product(); } build. Part. A() {… p. add. Part. A(); … } build. Part. B() {… p. add. Part. B(); … } build. Part. C() {… p. add. Part. C(); … } Product get. Result() { return p; } Class Director { private Builder b; Director(Builder b) {this. b = b; } void build. Product() { for each part in Product { if (part. A) b. build. Part. A(); else if (part. B) b. build. Part. B(); else if (part. C) b. build. Part. C(); } } } client() { Builder b = new Concrete. Builder(); Director d = new Director(b); d. build. Product() Final. Product fp = b. get. Result(); }
Creational design patterns Factory n Abstract Factory n Prototype n Singleton n Builder n
- Slides: 42