550 likes | 629 Views
Design Patterns – II. Lecture IV. Singleton Pattern. Intent Ensure a class only has one instance, and provide a global point of access to it Motivation Sometimes we want just a single instance of a class to exist in the system
E N D
Design Patterns – II Lecture IV
Singleton Pattern • Intent • Ensure a class only has one instance, and provide a global point of access to it • Motivation • Sometimes we want just a single instance of a class to exist in the system • For example, we want just one window manager. Or just one factory for a family of products. • We need to have that one instance easily accessible • And we want to ensure that additional instances of the class can not be created
Structure • Controlled access to sole instance • Permits a variable number of instances
Implementation /*Class Singleton is an implementation of a class that only allows one instantiation. */ public class Singleton { // The private reference to the one and only instance. private static Singleton uniqueInstance = null; // An instance attribute. private int data = 0; /*Returns a reference to the single instance.Creates the instance if it does not yet exist. (This is called lazy instantiation.) */ public static Singleton instance() { if(uniqueInstance== null) uniqueInstance = new Singleton(); return uniqueInstance; } private Singleton() {} // Accessors and mutators here! }
Here's a test program: public class TestSingleton { public static void main(String args[]) { // Get a reference to the single instance of Singleton. Singleton s = Singleton.instance(); // Set the data value. s.setData(34); System.out.println("First reference: " + s); System.out.println("Singleton data value is: " + s.getData()); // Get another reference to the Singleton. // Is it the same object? s = null; s = Singleton.instance(); System.out.println("\nSecond reference: " + s); System.out.println("Singleton data value is: " + s.getData()); } }
Singleton Pattern And the test program output: First reference: Singleton@1cc810 Singleton data value is: 34 Second reference: Singleton@1cc810 Singleton data value is: 34
Singleton • Note that the singleton instance is only created when needed. • This is called lazy instantiation. • Thought experiment: What if two threads concurrently invoke • the instance() method? Any problems? • Two instances of the singleton class could be created! • How could we prevent this? Several methods: • Make the instance() synchronized. Synchronization is expensive, however,and is really only needed the first time the unique instance is created. • Do an eager instantiation of the instance rather than a lazy instantiation.
Thread-Safe Singleton /*Class Singleton is an implementation of a class that only allows one instantiation.*/ public class Singleton { // The private reference to the one and only instance. // Let’s eagerly instantiate it here. private static Singleton uniqueInstance = new Singleton(); // An instance attribute. private int data = 0; /*Returns a reference to the single instance.*/ public static Singleton instance() { return uniqueInstance; } /*The Singleton Constructor. Note that it is private! No client can instantiate a Singleton object!*/ private Singleton() {} // Accessors and mutators here! }
Singleton • What if we want to be able to subclass Singleton and have the single instance be a subclass instance? • For example, suppose MazeFactory had subclassesEnchantedMazeFactoryand AgentMazeFactory. We want toinstantiate just one factory, either an EnchantedMazeFactory or an AgentMazeFactory. • How could we do this? Several methods: • Have the static instance() method of MazeFactory determine the particularsubclass instance to instantiate. This could be done via an argument orenvironment variable. The constructors of the subclasses can not beprivate in this case, and thus clients could instantiate other instances of thesubclasses. • Have each subclass provide a static instance() method. Now the subclass constructors can be private.
Method 1 /*Class MazeFactory is an implementation of a class thatonly allows one instantiation of a subclass. */ public abstract class MazeFactory { // The private reference to the one and only instance. private static MazeFactoryuniqueInstance = null; // The MazeFactory constructor. If you have a default constructor, it can not be private here! protected MazeFactory() {} // Return a reference to the single instance. // If instance not yet created, create "enchanted" as default. public static MazeFactory instance() { if (uniqueInstance == null) return instance("enchanted"); else return uniqueInstance; } // Create the instance using the specified String name. public static MazeFactory instance(String name) { if(uniqueInstance == null) if (name.equals("enchanted")) uniqueInstance = new EnchantedMazeFactory(); else if (name.equals("agent")) uniqueInstance = new AgentMazeFactory(); return uniqueInstance; } }
Method I • Client code to create factory the first time: MazeFactory factory = MazeFactory.instance("enchanted"); • Client code to access the factory: MazeFactory factory = MazeFactory.instance(); • Note that the constructors of EnchantedMazeFactoryandAgentMazeFactorycan not be private, since MazeFactorymustbe able to instantiate them. Thus, clients could potentiallyinstantiate other instances of these subclasses.
Method I • The instance(String) methods violates the Open-Closed Principle,since it must be modified for each new MazeFactory subclass • We could use Java class names as the argument to theinstance(String) method, yielding simpler code: public static MazeFactoryinstance(String name) { if (uniqueInstance == null) uniqueInstance = Class.forName(name).newInstance(); return uniqueInstance; }
Method 2 • Have each subclass provide a static instance method() /** * Class MazeFactory is an implementation of a class that * only allows one instantiation of a subclass. This version * requires its subclasses to provide an implementation of * a static instance() method. */ public abstract class MazeFactory { // The protected reference to the one and only instance. protected static MazeFactoryuniqueInstance = null; // The MazeFactory constructor. // If you have a default constructor, it can not be private here! protected MazeFactory() {} // Return a reference to the single instance. public static MazeFactory instance() {return uniqueInstance;} }
Method 2 /** * Class EnchantedMazeFactory is an implementation of a class * that only allows one instantiation. */ public class EnchantedMazeFactory extends MazeFactory { // Return a reference to the single instance. public static MazeFactory instance() { if(uniqueInstance == null) uniqueInstance = new EnchantedMazeFactory(); return uniqueInstance; } // Private subclass constructor!! private EnchantedMazeFactory() {} }
Method 2 • Client code to create factory the first time: MazeFactory factory = EnchantedMazeFactory.instance(); • Client code to access the factory: MazeFactory factory = MazeFactory.instance(); • Note that now the constructors of the subclasses are private. Onlyone subclass instance can be created! • Also note that the client can get a null reference if it invokesMazeFactory.instance() before the unique subclass instance is first created • Finally, note that uniqueInstance is now protected!
Façade Pattern • Intent • Provide a unified interface to a set of interfaces in a subsystem. Façadedefines a higher-level interface that makes the subsystem easier to use. • Motivation • Structuring a system into subsystems helps reduce complexity • Subsystems are groups of classes, or groups of classes and other subsystems • The interface exposed by the classes in a subsystem or set of subsystems • can become quite complex • One way to reduce this complexity is to introduce a facade object thatprovides a single, simplified interface to the more general facilities of a subsystem
Façade - Applicability • Use the Facade pattern: • To provide a simple interface to a complex subsystem. This interface isgood enough for most clients; more sophisticated clients can look beyond the facade. • To decouple the classes of the subsystem from its clients and othersubsystems, thereby promoting subsystem independence and portability
Consequences • Benefits • It hides the implementation of the subsystem from clients, making the subsystem easier to use • It promotes weak coupling between the subsystem and its clients. This allowsyou to change the classes the comprise the subsystem without affecting the clients. • It reduces compilation dependencies in large software systems • It simplifies porting systems to other platforms, because it's less likely thatbuilding one subsystem requires building all others • It does not prevent sophisticated clients from accessing the underlying classes • Note that Facade does not add any functionality, it just simplifies interfaces • Liabilities • It does not prevent clients from accessing the underlying classes!
Adapter Pattern • Intent • Convert the interface of a class into another interface clients expect. • Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. • Motivation • Sometimes a toolkit or class library can not be used because its interface is incompatiblewith the interface required by an application • We can not change the library interface, since we may not have its source code • Even if we did have the source code, we probably should not change thelibrary for each domain-specific application
The Adapter Pattern • Applicability • Use the Adapter pattern when • Youwant to use an existing class, and its interface does not match the one you need • You want to create a reusable class that cooperates with unrelated classes with incompatible interfaces • Implementation Issues • How much adapting should be done? • Simple interface conversion that just changes operation names and order of arguments • Totally different set of operations • Does the adapter provide two-way transparency? • A two-way adapter supports both the Target and the Adaptee interface. Itallows an adapted object (Adapter) to appear as an Adaptee object or a Target object
Example 1 • The classic round pegs and square pegs! • Here's the SquarePeg class: /** * The SquarePeg class. * This is the Target class. */ public class SquarePeg { public void insert(String str) { System.out.println("SquarePeg insert(): " + str); } }
Example 1 • And the RoundPeg class: /** * The RoundPeg class. * This is the Adaptee class. */ public class RoundPeg { public void insertIntoHole(String msg) { System.out.println("RoundPeg insertIntoHole(): " + msg); } }
Example 1 • If a client only understands the SquarePeg interface for insertingpegs using the insert() method, how can it insert round pegs? A peg adapter! /** * The PegAdapter class. * This is the Adapter class. * It adapts a RoundPeg to a SquarePeg. * Its interface is that of a SquarePeg. */ public class PegAdapter extends SquarePeg { private RoundPeg roundPeg; public PegAdapter(RoundPeg peg) {this.roundPeg = peg;} public void insert(String str) {roundPeg.insertIntoHole(str);} }
Example 1 // Test program for Pegs. public class TestPegs { public static void main(String args[]) { // Create some pegs. RoundPeg roundPeg = new RoundPeg(); SquarePeg squarePeg = new SquarePeg(); // Do an insert using the square peg. squarePeg.insert("Inserting square peg..."); // Now we'd like to do an insert using the round peg. // But this client only understands the insert() // method of pegs, not a insertIntoHole() method. // The solution: create an adapter that adapts // a square peg to a round peg! PegAdapter adapter = new PegAdapter(roundPeg); adapter.insert("Inserting round peg..."); } } • Client program output: SquarePeg insert(): Inserting square peg... RoundPeginsertIntoHole(): Inserting round peg…
Example 2 • Notice in Example 1 that the PegAdapter adapts a RoundPeg to aSquarePeg. The interface for PegAdapter is that of a SquarePeg. • What if we want to have an adapter that acts as a SquarePeg or aRoundPeg? Such an adapter is called a two-way adapter. • One way to implement two-way adapters is to use multipleinheritance, but we can't do this in Java • But we can have our adapter class implement two different Java interfaces!
Example 2 /** *The IRoundPeg interface. */ public interface IRoundPeg { public void insertIntoHole(String msg); } /** *The ISquarePeg interface. */ public interface ISquarePeg { public void insert(String str); }
Example 2 • Here are the new RoundPeg and SquarePeg classes. These areessentially the same as before except they now implement the appropriate interface. // The RoundPeg class. public class RoundPeg implements IRoundPeg { public void insertIntoHole(String msg) { System.out.println("RoundPeg insertIntoHole(): " + msg); } } // The SquarePeg class. public class SquarePeg implements ISquarePeg { public void insert(String str) { System.out.println("SquarePeg insert(): " + str); } }
Example 2 • And here is the new PegAdapter: /** * The PegAdapter class. * This is the two-way adapter class. */ public class PegAdapter implements ISquarePeg, IRoundPeg { private RoundPeg roundPeg; private SquarePeg squarePeg; public PegAdapter(RoundPeg peg) {this.roundPeg = peg;} public PegAdapter(SquarePeg peg) {this.squarePeg = peg;} public void insert(String str) {roundPeg.insertIntoHole(str);} public void insertIntoHole(String msg){squarePeg.insert(msg);} }
Example 2 • A client that uses the two-way adapter: // Test program for Pegs. public class TestPegs { public static void main(String args[]) { // Create some pegs. RoundPeg roundPeg = new RoundPeg(); SquarePeg squarePeg = new SquarePeg(); // Do an insert using the square peg. squarePeg.insert("Inserting square peg..."); // Create a two-way adapter and do an insert with it. ISquarePeg roundToSquare = new PegAdapter(roundPeg); roundToSquare.insert("Inserting round peg..."); // Do an insert using the round peg. roundPeg.insertIntoHole("Inserting round peg..."); // Create a two-way adapter and do an insert with it. IRoundPeg squareToRound = new PegAdapter(squarePeg); squareToRound.insertIntoHole("Inserting square peg..."); } }
Example 2 • Client program output: SquarePeg insert(): Inserting square peg... RoundPeginsertIntoHole(): Inserting round peg... RoundPeginsertIntoHole(): Inserting round peg... SquarePeg insert(): Inserting square peg...
Visitor Pattern • Intent • Represent an operation to be performed on the elements of an objectstructure. Visitor lets you define a new operation without changing theclasses of the elements on which it operates. • Motivation • Consider a compiler that parses a program and represents the parsedprogram as an abstract syntax tree (AST). The AST has many differentkinds of nodes, such as Assignment , Variable Reference, and Arithmetic Expression nodes. • Operations that one would like to perform on the AST include: • Checking that all variables are defined • Checking for variables being assigned before they are used • Type checking • Code generation • Pretty printing/formatting
Visitor • These operations may need to treat each type of node differently • One way to do this is to define each operation in the specific node class
Visitor • Motivation • Problemswith this approach: • Addingnew operations requires changes to all of the node classes • It can be confusing to have such a diverse set of operations in each node class. • For example, mixing type-checking code with pretty-printing code can be hard to understand and maintain. • Another solution is to encapsulate a desired operation in a separate object,called a visitor. The visitor object then traverses the elements of the tree.When an tree node "accepts" the visitor, it invokes a method on the visitorthat includes the node type as an argument. The visitor will then executethe operation for that node - the operation that used to be in the node class.
Visitor • Applicability • Use the Visitor pattern in any of the following situations: • When many distinct and unrelated operations need to be performed onobjects in an object structure, and you want to avoid "polluting" their • classes with these operations • When the classes defining the object structure rarely change, but you oftenwant to define new operations over the structure. (If the object structureclasses change often, then it's probably better to define the operations in those classes.) • When an object structure contains many classes of objects with differinginterfaces, and you want to perform operations on these objects that depend on their concrete classes
Visitor • Consequences • Benefits • Adding new operations is easy • Related behavior isn't spread over the classes defining the object structure; it‘slocalized in a visitor. Unrelated sets of behavior are partitioned in their own visitor subclasses. • Visitors can accumulate state as they visit each element in the object structure.Without a visitor, this state would have to be passed as extra arguments to theoperations that perform the traversal. • Liabilities • Adding new ConcreteElement classes is hard. Each new ConcreteElementgives rise to a new abstract operation on Visitor and a correspondingimplementation in every ConcreteVisitor class. • The ConcreteElement interface must be powerful enough to let visitors do theirjob. You may be forced to provide public operations that access an element‘sinternal state, which may compromise its encapsulation.
Double-Dispatch • The Visitor pattern allows you to add operations to classeswithout changing them using a technique called double-dispatch • Single-Dispatch • The actual method invoked depends on the name of the request (methodsignature) and the type of the receiver object • For example, calling foo() on a object of Type X, invokes the foo() method of X • The actual underlying type will be discovered through polymorphism • This is the standard technique used in languages like Java and C++ • Double-Dispatch • The actual method invoked depends on the name of the request and the types of two receivers
Double Dispatch • For example, consider an object of type Visitor1 calling accept(Visitor1)on an object of Type ElementA: • The Visitor1 object dispatches a call to the accept(Visitor) method of ElementA • The accept(Visitor) method of ElementA dispatches a call back to the visitor(Visitor1), invoking the visit(ElementA) method of Visitor1 and passing itself as an argument. • This round trip effectively picks up the right type of Element, ensuring that thecorrect visit() method of the Visitor object is called • Effectively, then, the method invoked depends on the request name(accept(Visitor)), the type of the Element object (ElementA) and the typeof the Visitor object (Visitor1)
The Composite Pattern • Intent • Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objectsuniformly. This is called recursive composition. • Motivation
Motivation • Applicability • Use the Composite pattern when • You want to represent part-whole hierarchies of objects • You want clients to be able to ignore the difference between compositionsof objects and individual objects. Clients will treat all objects in the composite structure uniformly.