Creational Design Patterns Note 2022 PDF
Document Details
Uploaded by StylizedTheme
University of Moratuwa
2022
Prof Chandana Gamage and Dr Buddhika Karunarathne
Tags
Summary
These lecture notes from the University of Moratuwa cover creational design patterns, including Abstract Factory, Builder, Factory Method, Prototype, and Singleton. The notes detail the problems addressed by each pattern and their solutions, along with examples.
Full Transcript
Design Patterns: Creational CS1040 Program Construction Prof Chandana Gamage and Dr Buddhika Karunarathne Department of Computer Science & Engineering University of Moratuwa June 23, 2022...
Design Patterns: Creational CS1040 Program Construction Prof Chandana Gamage and Dr Buddhika Karunarathne Department of Computer Science & Engineering University of Moratuwa June 23, 2022 1 / 51 Acknowledgment These lecture notes are based on material from the GoF book Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, ”Design Patterns: Elements of Reusable Object-Oriented Software Addison-Wesley.” Reading, MA, 1995 and Wikipedia content. 2 / 51 Creational Patterns ◮ Abstract factory: Provide an interface for creating families of related or dependent objects without specifying their concrete classes. ◮ Builder: Separate the construction of a complex object from its representation, allowing the same construction process to create various representations. ◮ Factory method: Define an interface for creating a single object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. ◮ Prototype: Specify the kinds of objects to create using a prototypical instance, and create new objects from the ’skeleton’ of an existing object, thus boosting performance and keeping memory footprints to a minimum. ◮ Singleton: Ensure a class has only one instance, and provide a global point of access to it. 3 / 51 Abstract Factory 1/2 ◮ Problem: ◮ It is required to make an application independent of how its objects are created. ◮ It is required to make a class independent of how the objects it requires are created. ◮ It is required to make families of related or dependent objects creation efficient and uniform. 4 / 51 Abstract Factory 1/2 ◮ Problem: ◮ It is required to make an application independent of how its objects are created. ◮ It is required to make a class independent of how the objects it requires are created. ◮ It is required to make families of related or dependent objects creation efficient and uniform. ◮ Solution: ◮ Encapsulate object creation in a separate (factory) object. This can be done by defining an interface (for example, AbstractFactory) for creating objects and then implement the interface. ◮ Using this approach, a class delegates object creation to a factory object instead of creating objects directly. 4 / 51 Abstract Factory 2/2 ◮ Creating objects directly within the class that requires the objects is inflexible because it commits the class to particular objects and makes it impossible to change the instantiation later independently from (without having to change) the class. ◮ Creating objects directly within the class stops the class from being reusable if other objects are required, and it makes the class hard to test because real objects can not be replaced with mock objects for testing purposes. ◮ Use of this pattern makes it possible to interchange concrete implementations without changing the code that uses them, even at runtime. ◮ A factory is the location of a concrete class in the code at which objects are constructed. 5 / 51 Abstract Factory - Class Diagram Source: Vanderjoe - CC BY-SA 4.0 6 / 51 Abstract Factory - Example Source: Image Kernels Explained Visually by Victor Powell http://setosa.io/ev/image-kernels/ An image kernel is a small matrix used to apply effects like the ones you might find in Photoshop or Gimp, such as blurring, sharpening, outlining or embossing. The Identity Kernel does not create any effect. 7 / 51 Abstract Factory - Example Source: Image Kernels Explained Visually by Victor Powell http://setosa.io/ev/image-kernels/ This is an example application of a Kernel for Blurring. 8 / 51 Abstract Factory - Example Source: Image Kernels Explained Visually by Victor Powell http://setosa.io/ev/image-kernels/ This is an example application of a Kernel for Sharpening. 9 / 51 Abstract Factory - Code Example 1/6 class Image { public Image() {} public Image(String fname) {} } // These generic abstract interfaces for Filters are // created for use by abstract Factory interface BlurFilter { public Image apply(Image img); } interface EdgeDetectFilter { public Image apply(Image img); } interface SharpenFilter { public Image apply(Image img); } 10 / 51 Abstract Factory - Code Example 2/6 // These are the concrete implementations of the Filters // created independently. There can be many variants class BlurFilter3X3 implements BlurFilter { public Image apply(Image img) { System.out.println("applying blur filter with 3X3 kernel"); return new Image(); } } class EdgeDetectFilter3X3 implements EdgeDetectFilter { public Image apply(Image img) { System.out.println("applying edge detection filter with 3X3 ke return new Image(); } } class SharpenFilter3X3 implements SharpenFilter { public Image apply(Image img) { System.out.println("applying sharpenning filter with 3X3 kerne return new Image(); } } 11 / 51 Abstract Factory - Code Example 3/6 // This is the Abstract Factory that the clients would // use and they do not need to know any specific // implementation details. interface FilterFactory { public BlurFilter createBlurFilter(); public EdgeDetectFilter createEdgeDetectFilter(); public SharpenFilter createSharpenFilter(); } 12 / 51 Abstract Factory - Code Example 4/6 // This is a Concrete implementation of Abstract Factory. // It is only here we decide exactly how filters are created. // This is done independent of any client programs. // Also, we can create many different Concrete Factories. class FilterFactory3X3 implements FilterFactory { public BlurFilter createBlurFilter() { System.out.println("creating blur filter with 3X3 kernel"); return new BlurFilter3X3(); } public EdgeDetectFilter createEdgeDetectFilter() { System.out.println("creating edge detection filter with 3X3 ke return new EdgeDetectFilter3X3(); } public SharpenFilter createSharpenFilter() { System.out.println("creating sharpenning filter with 3X3 kerne return new SharpenFilter3X3(); } } 13 / 51 Abstract Factory - Code Example 5/6 public class PhotoBooth { private BlurFilter blurf; private EdgeDetectFilter edgef; private SharpenFilter sharpf; private FilterFactory ffactory; public PhotoBooth(FilterFactory ffactory) { this.ffactory = ffactory; // Different filters are created by the Factory object blurf = ffactory.createBlurFilter(); edgef = ffactory.createEdgeDetectFilter(); sharpf = ffactory.createSharpenFilter(); } public Image blur(Image img) { return blurf.apply(img); } public Image edgeDetect(Image img) {return edgef.apply(img);} public Image sharpen(Image img) { return sharpf.apply(img); } 14 / 51 Abstract Factory - Code Example 6/6 public static void main(String [] args) { // Use a concrete Filter Factory instance FilterFactory3X3 ff = new FilterFactory3X3(); // Create a Photo Booth with specific Filter Factory PhotoBooth pb = new PhotoBooth(ff); Image lena = new Image("lena.jpg"); lena = pb.sharpen(lena); lena = pb.edgeDetect(lena); lena = pb.blur(lena); } } 15 / 51 Builder 1/2 ◮ Problem: ◮ It is required to separate the construction of a complex object from its representation. ◮ It is required to simplify a class that include the creation of a complex object. 16 / 51 Builder 1/2 ◮ Problem: ◮ It is required to separate the construction of a complex object from its representation. ◮ It is required to simplify a class that include the creation of a complex object. ◮ Solution: ◮ Encapsulate creating and assembling the parts of a complex object in a separate Builder object. ◮ Using this design pattern, a class can delegate to different Builder objects to create different representations of a complex object. 16 / 51 Builder 2/2 ◮ Advantages: ◮ Allows for the varying of an objects internal representation. ◮ Encapsulates code for construction and representation. ◮ Provides control over steps of construction process. ◮ Disadvantages: ◮ Requires creating a separate ConcreteBuilder class for each different type of object. ◮ Requires the builder classes to be mutable. ◮ Data members of class are not guaranteed to be initialized. ◮ Dependency injection may be less supported. 17 / 51 Builder - Class Diagram Source: Vanderjoe - CC BY-SA 4.0 18 / 51 Builder - Example Source: Analog-to-Digital Converter Design Guide http://www.microchip.com Selecting the most suitable A/D converter (ADC) for an application is based on more than just the precision or bits. Different architectures are available, each exhibiting advantages and disadvantages in various data-acquisition systems. 19 / 51 Builder - Example Source: Analog-to-Digital Converter Design Guide http://www.microchip.com The required accuracy or precision of ADC system defines a category based on the number of bits required. Successive Approximation Register (SAR) converters typically range from 8 to 16 bits. Delta-sigma converters (∆Σ) can achieve an accuracy of up to 24 bits. 20 / 51 Builder - Code Example 1/10 class BitRegister { public BitRegister(String s) { System.out.println("generating bit register "+s); }} class SampledValue { public SampledValue(String s) { System.out.println("generating sampled value "+s); }} class AnalogValue { public AnalogValue(String s) { System.out.println("generating analog value "+s); }} class ControlSignal { public ControlSignal(String s) { System.out.println("generating control signal "+s); }} 21 / 51 Builder - Code Example 2/10 class Clock { public ControlSignal output() { return new ControlSignal("[clock]"); } } // Successive Approximation Register class SAR { private Clock clk; private Comparator comp; public SAR(Clock clk, Comparator comp) { this.clk = clk; this.comp = comp; } public BitRegister output() { BitRegister br = comp.output(); ControlSignal cs = clk.output(); System.out.println ("computing successive approximation register output"); return new BitRegister("[SARout]"); }} 22 / 51 Builder - Code Example 3/10 // Digital to Analog Converter class DAC { private SampledValue vref; private SAR sar; private boolean initialized; public DAC(SampledValue vref, SAR sar) { this.vref = vref; this.sar = sar; initialized = true; } // This constructor is for the initial creation of a DAC public DAC(SampledValue vref) { this.vref = vref; initialized = false; } public SampledValue output() { BitRegister br; if(initialized) br = sar.output(); else br = new BitRegister("[initializing SAR]"); System.out.println("computing D-to-A converter output"); return new SampledValue("[DACout]"); }} 23 / 51 Builder - Code Example 4/10 // Sample and Hold class SAH { private Clock clk; private AnalogValue ain; public SAH(Clock clk, AnalogValue ain) { this.clk = clk; this.ain = ain; } public SampledValue output() { ControlSignal cs = clk.output(); System.out.println("computing sample-and-hold outout"); return new SampledValue("[Sout]"); } } 24 / 51 Builder - Code Example 5/10 class Comparator { private SAH sah; private DAC dac; public Comparator(SAH sah, DAC dac) { this.sah = sah; this.dac = dac; } public BitRegister output() { SampledValue sv1 = sah.output(); SampledValue sv2 = dac.output(); System.out.println("computing comparator output"); return new BitRegister("[Cout]"); } } 25 / 51 Builder - Code Example 6/10 class ShiftRegister { private Clock clk; private SAR sar; public ShiftRegister(Clock clk, SAR sar) { this.clk = clk; this.sar = sar; } public BitRegister output() { BitRegister br = sar.output(); ControlSignal cs = clk.output(); System.out.println("computing shift register output"); return new BitRegister("[Dout]"); } } 26 / 51 Builder - Code Example 7/10 // This is the complex SAR-ADC object class SARADC { private Clock clk; private AnalogValue ain; private SampledValue vref; private SAH sah; private DAC dac0; private Comparator comp0; private SAR sar0; private DAC dac; private Comparator comp; private SAR sar; private ShiftRegister sr; // cont... 27 / 51 Builder - Code Example 8/10 // cont... public ShiftRegister buildComplexObject(AnalogValue ain) { clk = new Clock(); this.ain = ain; vref = new SampledValue("[Vref]"); sah = new SAH(clk,ain); dac0 = new DAC(vref); comp0 = new Comparator(sah,dac0); sar0 = new SAR(clk,comp0); dac = new DAC(vref,sar0); comp = new Comparator(sah,dac); sar = new SAR(clk,comp); sr = new ShiftRegister(clk,sar); return sr; } } 28 / 51 Builder - Code Example 9/10 // This is a Concrete ADC Builder for SAR-type ADC. // It builds the complex SARADC object using many // products/objects. class SARADCBuilder implements ADCBuilder { private SARADC sar; private AnalogValue input; private ShiftRegister output; public void build(AnalogValue input) { sar = new SARADC(); output = sar.buildComplexObject(input); } public BitRegister output() { return output.output(); } } 29 / 51 Builder - Code Example 10/10 // This is the Abstract ADC Builder interface. interface ADCBuilder { public void build(AnalogValue input); public BitRegister output(); } // This is Client class (Director) interacting with a Concrete // Builder (SARADCBuilder) that is implementing the Abstract // Builder (ADCBuilder) interface. public class TemperatureMonitor { public static void main(String [] args) { AnalogValue lm57 = new AnalogValue("[analog input signal]"); // Let A-to-D Converter creation be handled by a Converter // Builder. This way, client program is completely isolated // from complex object construction required for building an // A-to-D Converter. ADCBuilder adcb = new SARADCBuilder(); adcb.build(lm57); BitRegister br = adcb.output(); } } 30 / 51 Factory Method 1/2 ◮ Problem: ◮ It is required to create objects at compile-time without having to specify the exact class of the object that will be created at runtime. ◮ It is required to create an object instance of a class where subclasses are able to redefine which class to instantiate. ◮ It is required for a class to defer instantiation of an object to a subclass. 31 / 51 Factory Method 1/2 ◮ Problem: ◮ It is required to create objects at compile-time without having to specify the exact class of the object that will be created at runtime. ◮ It is required to create an object instance of a class where subclasses are able to redefine which class to instantiate. ◮ It is required for a class to defer instantiation of an object to a subclass. ◮ Solution: ◮ Create objects by calling a Factory Method that is specified in an interface and implemented by child clsses. ◮ Create objects by calling a Factory Method that is implemented in a base class and optionally overridden by derived classes. ◮ This way, objects are not created by directly calling a constructor for that class within the class that use it. 31 / 51 Factory Method 2/2 ◮ Creating an object often requires complex processes not appropriate to include within a composing object. ◮ The object’s creation may lead to a significant duplication of code, may require information not accessible to the composing object, may not provide a sufficient level of abstraction, or may otherwise not be part of the composing object’s concerns. ◮ The factory method design pattern handles these problems by defining a separate method for creating the objects, which subclasses can then override to specify the derived type of object that will be created. ◮ The factory method pattern uses inheritance, where object creation is done by subclasses that implement the factory method to create objects. 32 / 51 Factory Method - Class Diagram Source: Vanderjoe - CC BY-SA 4.0 33 / 51 Factory Method - Example Source: BWI Group Consider the implementation of an Anti-lock Braking System control module software. The sensor modules for a car (with 4-wheels and a shorter wheel-base) could be different from that of a truck. 34 / 51 Factory Method - Code Example 1/4 // This is a Generic/Abstract ABS Sensor module. abstract class ABSSensor { public void install() {log("I’m a Generic ABS Sensor");} public void log(String s) { System.out.println(s); } } // This is a Concrete Car ABS Sensor module. // It overrides methods to specialize itself. class CarABSSensor extends ABSSensor { public void install() { log("I’m a Car ABS Sensor"); } } // This is a Concrete Truck ABS Sensor module. // It overrides methods to specialize itself. class TruckABSSensor extends ABSSensor { public void install() { log("I’m a Truck ABS Sensor"); } } 35 / 51 Factory Method - Code Example 2/4 // This is a Generic/Abstract ABS Controller module. // It will install an ABS Controller using generic ABS // sensor modules. This code at compile time is generic // and uses the Factory Method makeABSSensor(). abstract class ABSController { private ABSSensor abss; // Factory Method public abstract ABSSensor makeABSSensor(); public void log(String s) { System.out.println(s); } public void install() { log("Making an ABS Controller"); abss = makeABSSensor(); abss.install(); } } 36 / 51 Factory Method - Code Example 3/4 // This Concrete Car ABS Controller module overrides // factory method, makeABSSensor() to specialize itself. class CarABSController extends ABSController { public ABSSensor makeABSSensor() { log("Making a Car ABS Sensor"); return new CarABSSensor(); } } // This Concrete Truck ABS Controller module overrides // factory method, makeABSSensor() to specialize itself. class TruckABSController extends ABSController { public ABSSensor makeABSSensor() { log("Making a Truck ABS Sensor"); return new TruckABSSensor(); } } 37 / 51 Factory Method - Code Example 4/4 // This test code shows that the correct sensor is // installed in the specific controller. This decision on // which sensor to use is deferred to a subclass and // can be decided at runtime. // The ABSController class developer can work // independently. public class BenzABSController { public static void main(String [] args) { CarABSController carabsc = new CarABSController(); carabsc.install(); TruckABSController truckabsc = new TruckABSController(); truckabsc.install(); } } 38 / 51 Prototype 1/2 ◮ Problem: ◮ It is required to create objects in a manner that allows which objects to create can be specified at run-time. ◮ It is required to instantiate dynamically loaded classes. 39 / 51 Prototype 1/2 ◮ Problem: ◮ It is required to create objects in a manner that allows which objects to create can be specified at run-time. ◮ It is required to instantiate dynamically loaded classes. ◮ Solution: ◮ Define a Prototype object that returns a copy of itself. Then at runtime, create new objects by copying a Prototype object. ◮ The problem addressed by the Prototype design pattern occurs due to the creation of objects directly within the class that require (and use) those objects. ◮ This manner of object creation is inflexible as it commits the class to particular objects at compile-time. ◮ This prevents the specification of which objects to be created at run-time. 39 / 51 Prototype 2/2 ◮ The use of Prototype avoid subclasses of an object creator in the client application (this is what the factory method pattern does). ◮ Allows the creation of object instances using much cheaper cloning rather than the more expensive new object creation using new operator. ◮ Allows configuration of a class with different Prototype objects, which are copied to create new objects, and even more, Prototype objects can be added and removed at run-time. 40 / 51 Prototype - Class Diagram Source: Vanderjoe - CC BY-SA 4.0 41 / 51 Prototype - Example Source: NASA/JPL-Caltech Consider the implementation of camera system for the Mars Rover. Some of the images can be only B&W if used only for obstacle detection while for scientific experiments and visual exploration, other cameras need to provide color images. 42 / 51 Prototype - Code Example 1/4 // ImageBuffer is the "abstract" Prototype abstract class ImageBuffer implements Cloneable { public ImageBuffer clone() throws CloneNotSupportedException { return (ImageBuffer) super.clone(); } public void log(String s) { System.out.println(s); } } 43 / 51 Prototype - Code Example 2/4 // ImageBW is a "concrete" object to be created // using a prototype class ImageBW extends ImageBuffer { public ImageBuffer clone() throws CloneNotSupportedException { log("Cloning an image buffer for black & white"); return (ImageBW) super.clone(); } } // ImageRGB is a "concrete" object to be created // using a prototype class ImageRGB extends ImageBuffer { public ImageBuffer clone() throws CloneNotSupportedException { log("Cloning an image buffer for color"); return (ImageRGB) super.clone(); } 44 / 51 Prototype - Code Example 3/4 // BufferMaker is a class with a Factory Method class BufferMaker { private ImageBW imgbw; private ImageRGB imgrgb; // Create instances of these objects only once public BufferMaker() { imgbw = new ImageBW(); imgrgb = new ImageRGB(); } // Create multiple instances by cloning of objects public ImageBuffer makeImageBuffer(char imgtyp) throws CloneNotSupportedException { switch(imgtyp) { case ’b’: return imgbw.clone(); case ’c’: return imgrgb.clone(); default: return null; } } } 45 / 51 Prototype - Code Example 4/4 public class MarsRover { public static void main(String [] args) { ImageBuffer [] mars = new ImageBuffer; BufferMaker bufmaker = new BufferMaker(); int i=0; try { mars[i++] = bufmaker.makeImageBuffer(’b’); mars[i++] = bufmaker.makeImageBuffer(’c’); mars[i++] = bufmaker.makeImageBuffer(’b’); mars[i++] = bufmaker.makeImageBuffer(’c’); mars[i++] = bufmaker.makeImageBuffer(’a’); } catch (CloneNotSupportedException e) { System.out.println("Exception!"); } } } 46 / 51 Singleton 1/2 ◮ Problem: ◮ It is required to ensure that only a single instance of a class is instatiated. ◮ It is required to provide easy access to the sole instance of a class. 47 / 51 Singleton 1/2 ◮ Problem: ◮ It is required to ensure that only a single instance of a class is instatiated. ◮ It is required to provide easy access to the sole instance of a class. ◮ Solution: ◮ The constructor for the class must be hidden by restricting it’s visibility (for example, by declaring as private). ◮ A public method must be provided to get a reference to the sole instance of the object (for example, as getInstance(). ◮ The class instantiates itself once (for example, by declaring the class as static). ◮ Use of a static class allows for a static class method to to be used to refer to the sole instance of the class where that instance is kept as a private static variable. 47 / 51 Singleton 2/2 ◮ The Singleton design pattern is viewed negatively as it introduces global state into an application. It is useful in comparison to the alternative of using a global variable. 48 / 51 Singleton - Class Diagram Source: w3sDesign 49 / 51 Singleton - Code Example 1/2 class Logger { // this is the Singleton object // we define a static sharable object as a constant private static final Logger INSTANCE = new Logger(); private int logCount; private Logger() { // private constructor logCount = 0; } public static Logger getInstance() { // static operation return INSTANCE; } public void incCount() { logCount++; } public int getCount() { return logCount; } } 50 / 51 Singleton - Code Example 2/2 public class LogTester { public static void main(String [] args) { // we define a set of Logger object references Logger [] log = new Logger; for(int i=0; i