Creational Patterns: Prototype, Factory Method, and Singleton
This chapter is excerpted from C# 3.0 Design Patterns: Use the Power of C# 3.0 to Solve Real-World Problems by Judith Bishop, published by O'Reilly Media
The creational patterns aim to separate a system from how its objects are created, composed, and represented. They increase the system's flexibility in terms of the what, who, how, and when of object creation. Creational patterns encapsulate the knowledge about which classes a system uses, but they hide the details of how the instances of these classes are created and put together. Programmers have come to realize that composing systems with inheritance makes those systems too rigid. The creational patterns are designed to break this close coupling. In this and the following chapter, we shall make further use of some C# features that help to abstract the instantiation process-generics and delegates (introduced in Chapter 3, Structural Patterns: Composite and Flyweight and Chapter 4, Structural Patterns: Adapter and Façade, respectively) are two of these.
We'll start by looking at three small patterns that are helpful in combination with many others. The Prototype pattern ensures that when copies of complex objects are made, they are true copies. The Factory Method pattern is a means of creating objects without knowing the exact subclass being used. Finally, the Singleton pattern ensures that only one of a class can be built and that all users are directed to it.
Prototype Pattern
Role
The Prototype pattern creates new objects by cloning one of a few stored prototypes. The Prototype pattern has two advantages: it speeds up the instantiation of very large, dynamically loaded classes (when copying objects is faster), and it keeps a record of identifiable parts of a large data structure that can be copied without knowing the subclass from which they were created.
Illustration
Let's again consider the Photo Group application discussed in Chapter 3, Structural Patterns: Composite and Flyweight, which held groups of photographs (see Figure 3.3, "Flyweight pattern illustration-Photo Group"). At some stage, we might like to archive one of the groups by copying it to another album. Then, later, we can bring it back again (perhaps if the original is deleted by mistake). In this case, the archive becomes a holder of prototypes that can be copied whenever required. We shall call the updated version of the application with this added functionality Photo Archive.
Design
Objects are usually instantiated from classes that are part of the program. The Prototype pattern presents an alternative route by creating objects from existing prototypes. The UML for the Prototype pattern is given in Figure 5.1, "Prototype pattern UML diagram".
Figure 5.1. Prototype pattern UML diagram
Given a key, the program creates an object of the required type, not by instantiation, but by copying a clean instance of the class. This process of copying, or cloning, can be repeated over and over again. The copies, or clones, are objects in their own right, and the intention of the pattern is that their state can be altered at will without affecting the prototype. During the run of the program new prototypes can be added, either from new classes or from variations on existing prototypes. Although there are other designs, the most flexible is to keep a prototype manager that maintains an indexed list of prototypes that can be cloned. The main players in the pattern are:
IPrototype
Defines the interface that says prototypes must be cloneablePrototype
A class with cloning capabilitiesPrototypeManager
Maintains a list of clone types and their keysClient
Adds prototypes to the list and requests clones
QUIZ: Match the Prototype Pattern Players with the Photo Archive Illustration
To test whether you understand the Prototype pattern, cover the lefthand column of the table below and see if you can identify its players among the items from the illustrative example (using the picture in Figure 3.3, "Flyweight pattern illustration-Photo Group"), as shown in the righthand column. Then check your answers against the lefthand column.
IPrototype | Facility for archiving |
Prototype | An archived photo set |
PrototypeManager | The Photo Library |
Client | The user |
C# Features-Cloning and Serialization
MemberwiseClone is a method that is available on all objects. It copies the values of all fields and any references, and returns a reference to this copy. However, it does not copy what the references in the object point to. That is, it performs what is known as a shallow copy. Many objects are simple, without references to other objects, and therefore shallow copies are adequate. To preserve the complete value of the object, including all its subobjects use a deep copy.
It is not easy to write a general algorithm to follow every link in a structure and recreate the arrangement elsewhere. However, algorithms do exist, and in the .NET Framework they are encapsulated in a process called serialization. Objects are copied to a given destination and can be brought back again at will. The options for serialization destinations are several, including disks and the Internet, but the easiest one for serializing smallish objects is memory itself. Thus a deep copy consists of serializing and deserializing in one method.
A generic method that will work for all types that are marked as serializable (such as lists and so on) is shown in Example 5.1, "Prototype pattern theory code-namespace". Note that there are two Serialization namespaces that must be imported. Marking a type as serializable is done with the [Serializable( )] attribute.
Serialization is part of the .NET Framework, not the C# language. The following reference is to the .NET library online.
cf. https://msdn2.microsoft.com/en-us/library/ system.runtime.serialization(vs.71).aspx
Warning
Serializing an object structure is possible only if all referenced objects are serializable. Avoid serializing an object that has a reference to a "resource," such as an open file handler or a database connection.
Part of the Prototype pattern then relies on a namespace with two methods: Clone and DeepCopy. In fact, Clone is merely a synonym for MemberwiseClone and can be omitted. The namespace is shown in Example 5.1, "Prototype pattern theory code-namespace".
Example 5.1. Prototype pattern theory code-namespace
1 using System;
2 using System.Collections.Generic;
3 using System.Runtime.Serialization;
4 using System.Runtime.Serialization.Formatters.Binary;
5
6 namespace PrototypePattern {
7 // Prototype Pattern Judith Bishop Nov 2007
8 // Serialization is used for the deep copy option
9 // The type T must be marked with the attribute [Serializable( )]
10
11 [Serializable( )]
12 public abstract class IPrototype <T> {
13
14 // Shallow copy
15 public T Clone( ) {
16 return (T) this.MemberwiseClone( );
17 }
18
19 // Deep Copy
20 public T DeepCopy( ) {
21 MemoryStream stream = new MemoryStream( );
22 BinaryFormatter formatter = new BinaryFormatter( );
23 formatter.Serialize(stream, this);
24 stream.Seek(0, SeekOrigin.Begin);
25 T copy = (T) formatter.Deserialize(stream);
26 stream.Close( );
27 return copy;
28 }
29 }
30 }
Implementation
The implementation of the Prototype pattern in C# is greatly assisted by two facilities in the .NET Framework: cloning and serialization. Both of these are provided as interfaces in the System namespace.
Now, consider the program to test the namespace, as in Example 5.2, "Prototype pattern theory code". It is a small example that sets up three prototypes, each consisting of a country, a capital, and a language (lines 26-28). The last of these, Language, refers to another class called DeeperData. The purpose of this class is to create a reference in the prototype. The example will show that for this deeper data item, there is a difference between shallow copying and deep copying.
Example 5.2. Prototype pattern theory code
1 using System;
2 using System.Collections.Generic;
3 using System.Runtime.Serialization;
4 using PrototypePattern;
5
6 // Prototype Pattern Judith Bishop Dec 2006, Nov 2007
7 // Serializable is used for the deep copy option
8
9 [Serializable( )]
10 // Helper class used to create a second level data structure
11 class DeeperData {
12 public string Data {get; set;}
13
14 public DeeperData(string s) {
15 Data = s;
16 }
17 public override string ToString ( ) {
18 return Data;
19 }
20 }
21
22 [Serializable( )]
23 class Prototype : IPrototype <Prototype> {
24
25 // Content members
26 public string Country {get; set;}
27 public string Capital {get; set;}
28 public DeeperData Language {get; set;}
29
30 public Prototype (string country, string capital, string language) {
31 Country = country;
32 Capital = capital;
33 Language = new DeeperData(language);
34 }
35
36 public override string ToString( ) {
37 return Country+"\t\t"+Capital+"\t\t->"+Language;
38 }
39
40 }
41 [Serializable( )]
42 class PrototypeManager : IPrototype <Prototype> {
43 public Dictionary <string, Prototype> prototypes
44 = new Dictionary <string, Prototype> {
45 {"Germany",
46 new Prototype ("Germany", "Berlin", "German")},
47 {"Italy",
48 new Prototype ("Italy", "Rome", "Italian")},
49 {"Australia",
50 new Prototype ("Australia", "Canberra", "English")}
51 };
52 }
53
54 class PrototypeClient : IPrototype <Prototype> {
55
56 static void Report (string s, Prototype a, Prototype b) {
57 Console.WriteLine("\n"+s);
58 Console.WriteLine("Prototype "+a+"\nClone "+b);
59 }
60
61 static void Main ( ) {
62
63 PrototypeManager manager = new PrototypeManager( );
64 Prototype c2, c3;
65
66 // Make a copy of Australia's data
67 c2 = manager.prototypes["Australia"].Clone( );
68 Report("Shallow cloning Australia\n===============",
69 manager.prototypes["Australia"], c2);
70
71 // Change the capital of Australia to Sydney
72 c2.Capital = "Sydney";
73 Report("Altered Clone's shallow state, prototype unaffected",
74 manager.prototypes["Australia"], c2);
75
76 // Change the language of Australia (deep data)
77 c2.Language.Data = "Chinese";
78 Report("Altering Clone deep state: prototype affected *****",
79 manager.prototypes["Australia"], c2);
80
81 // Make a copy of Germany's data
82 c3 = manager.prototypes["Germany"].DeepCopy( );
83 Report("Deep cloning Germany\n============",
84 manager.prototypes["Germany"], c3);
85
86 // Change the capital of Germany
87 c3.Capital = "Munich";
88 Report("Altering Clone shallow state, prototype unaffected",
89 manager.prototypes["Germany"], c3);
90
91 // Change the language of Germany (deep data)
92 c3.Language.Data = "Turkish";
93 Report("Altering Clone deep state, prototype unaffected",
94 manager.prototypes["Germany"], c3);
95 }
96 }
97 /* Output
98 Shallow cloning Australia
99 ===============
100 Prototype Australia Canberra ->English
101 Clone Australia Canberra ->English
102
103 Altered Clone's shallow state, prototype unaffected
104 Prototype Australia Canberra ->English
105 Clone Australia Sydney ->English
106
107 Altering Clone deep state: prototype affected *****
108 Prototype Australia Canberra ->Chinese
109 Clone Australia Sydney ->Chinese
110
111 Deep cloning Germany
112 ============
113 Prototype Germany Berlin ->German
114 Clone Germany Berlin ->German
115
116 Altering Clone shallow state, prototype unaffected
117 Prototype Germany Berlin ->German
118 Clone Germany Munich ->German
119
120 Altering Clone deep state, prototype unaffected
121 Prototype Germany Berlin ->German
122 Clone Germany Munich ->Turkish
123 */
The main program consists of a series of experiments demonstrating the effects of cloning and deep copying. In the first group, Australia is shallow copied. Lines 104-105 show the changing of Australia's clone. The capital is Canberra in the prototype and Sydney in the clone. The statement responsible is on line 72; however, changing the language to Chinese (as is done on line 77) also changes the prototype's language (line 108). That is not what we wanted. We got the error because we did a shallow copy and the language in the prototype and in the clone reference the same DeeperData object.
In the next experiment, we clone Germany using a deep copy (line 82). The output on line 118 shows that altering the clone's shallow state-its capital (line 87)-works correctly, as does altering the deep state-its language to Turkish (line 92). Line 121 shows the prototype after the changes; it is unaffected.
Tip
Remember that all classes involved in the prototype must add the Serializable() attribute.
Example: Photo Archive
As an example of the Prototype pattern, we'll extend the Photo Group application from Chapter 3, Structural Patterns: Composite and Flyweight to include archiving using the following new commands:
Archiveset
Retrieve set
Display Archive
The archive is an additional component at the same level as the album. The action associated with the two new commands is very simple:
case "Archive" : archive = point.Share(parameter,archive); break;
case "Retrieve" : point = archive.Share(parameter,album); break;
and this is the Share method in the Composite class:
public IComponent <T> Share (T set, IComponent <T> toHere) {
IPrototype <IComponent <T> prototype=
this.Find (set) as IPrototype <IComponent <T>;
toHere.Add(copy);
return toHere;
}
Of course, the Prototype Pattern is added as a used namespace as well. Given these changes to the program, the output can look like this (starting from the Display of the constructed composite):
1 Set Album length :2
2 --Set Home length :2
3 ----Dinner.jpg
4 ----Set Pets length :2
5 ------Dog.jpg
6 ------Cat.jpg
7 --Set Garden length :4
8 ----Spring.jpg
9 ----Summer.jpg
10 ----Flowers.jpg
11 ----Trees.jpg
12
13 Find Pets
14 Archive Pets
15 Display Archive
16 Set Archive length :1
17 --Set Pets length :2
18 ----Dog.jpg
19 ----Cat.jpg
20
21 Find Album
22 Remove Home
23 Find Album
24 Remove Garden
25 Display
26 Set Album length :0
27
28 Retrieve Pets
29 Display
30 Set Album length :1
31 --Set Pets length :2
32 ----Dog.jpg
33 ----Cat.jpg
Lines 14 and 15 show that the Pets set is archived. In lines 21-24, we remove everything from the album; we then copy the contents back from the archive in lines 28-33. Because we used a deep copy, we could copy Pets repeatedly, or, as the name of the method suggests, aim to share it with other programs. The full listing of the Photo Archive program is in the Appendix.
Use
The Prototype pattern co-opts one instance of a class and uses it as a "breeder" for all future instances. Designs that make heavy use of the Composite and Decorator patterns often can benefit from the Prototype pattern as well. Prototypes are useful when object initialization is expensive and you anticipate few variations on the initialization parameters. In this context, the Prototype pattern can avoid expensive "creation from scratch," supporting cheap cloning of a preinitialized prototype. However, cloning via serialization is not particularly cheap itself, so it is worth considering shallow cloning if you are absolutely sure you have a flat, single-level data structure.
There are different situations where the Prototype pattern would be used:
Say you have loaded a set of objects into memory and choose to copy by means of user input. When implementing user input, typically there are only a few choices of objects. These are known as the prototypes, and they are identified within the program by matching them up with the admissible input keys (such as strings, integers, or characters). The reason for using prototyping and not instantiation is to reduce the overhead when creating objects that have heavyweight constructors.
There are composite structures in the program and parts of them need to be copied, perhaps for archiving. Which part to copy will normally be identified by user input. The parts will all be the same (or within the same hierarchy, as in the Composite pattern), and the copy will become the prototype, which should not be altered. An example of this scenario is the Photo Archive application.
Use the Prototype pattern when... |
---|
You want to:
|
Because:
|
Consider using this pattern:
|
Exercises
Community networking systems such as Facebook support groups that people can join. Each group has a title, administrative members, a group type (open/closed), and a list of related groups. Otherwise, a group operates just like an ordinary page. Reorganize the SpaceBook application (Example 2.3, "Proxy pattern theory code") so that SpaceBooks and different sorts of SpaceGroups are prototypes.
Write a simple system that manages external courses offered by the Department of Tourism Management (e.g., Scenic Spots, Historic Sites, Local Cuisine, Meeting the People, and so on). Identify the attributes that each course should have-length, price, topics, etc.-and set them all up as prototypes with a manager. Then, create a little test program that enables users to select a course, get a copy of it, and fill in details to enroll for the course.
Factory Method Pattern
Role
The Factory Method pattern is a way of creating objects, but letting subclasses decide exactly which class to instantiate. Various subclasses might implement the interface; the Factory Method instantiates the appropriate subclass based on information supplied by the client or extracted from the current state.
Illustration
Consider a high-class grocery store in London that stocks avocados all year round. It relies on a buyer to ensure that avocados arrive regularly, no matter what the time of year. The buyer sources the best avocados and supplies them to the store. The buyer is operating as a Factory Method, returning Kenyan, South African, or Spanish avocados depending on the time of year. Although the produce is labeled, the storekeeper is not particularly interested in the source of the products. Figure 5.2, "Factory Method pattern illustration-avocado sourcing" shows what might happen at different times of the year.
Figure 5.2. Factory Method pattern illustration-avocado sourcing
Design
The client declares a Product variable but calls a FactoryMethod to instantiate it. This defers the decision as to which particular product to create. In the UML diagram in Figure 5.3, "Factory Method pattern UML diagram", there are two choices: ProductA and ProductB.
Figure 5.3. Factory Method pattern UML diagram
The list of players is:
IProduct
The interface for productsProductA and ProductB
Classes that implement IProductCreator
Provides the FactoryMethodFactoryMethod
Decides which class to instantiate
The design of this pattern enables the decision-making about which product to be instantiated to be handled in one place. If the client knew about all the options, the decisions would be dispersed in its code. As it is, the client need only be concerned with getting products; it does not even have to bind in all the different subclasses of products. Of course, the client can have more than one creator, for different types of products.
Quiz: Match the Factory Method Pattern Players with the Avocado Illustration
To test whether you understand the Factory Method pattern, cover the lefthand column of the table below and see if you can identify its players among the items from the illustrative example (Figure 5.2, "Factory Method pattern illustration-avocado sourcing"), as shown in the righthand column. Then check your answers against the lefthand column.
Client | Shopkeeper |
Creator | Buyer |
ProductA | Supplier of avocados from Spain |
ProductB | Supplier of avocados from South Africa |
IProduct | Supplying avocados |
Implementation and Example: Avocado Sourcing
A simple program implementing the avocado supplier example is shown in Example 5.3, "Factory Method pattern example code". Each of the avocado-producing countries has a class, and they all implement IProduct so that they can ship avocados to the buyer. Depending on the month, the FactoryMethod (lines 31-38) will choose to supply either ProductA (avocados from South Africa) or ProductB (avocados from Spain). There is also a third option, a DefaultProduct, which is selected when avocados are not available (in this case, in month 3). The important line is line 46, which shows a product being created from a factory (the buyer) without the client knowing the product's class.
Example 5.3. Factory Method pattern example code
1 using System;
2 using System.Collections;
3
4 class FactoryPattern {
5
6 // Factory Method Pattern Judith Bishop 2006
7
8 interface IProduct {
9 string ShipFrom( );
10 }
11
12 class ProductA : IProduct {
13 public String ShipFrom ( ) {
14 return " from South Africa";
15 }
16 }
17
18 class ProductB : IProduct {
19 public String ShipFrom ( ) {
20 return "from Spain";
21 }
22 }
23
24 class DefaultProduct : IProduct {
25 public String ShipFrom ( ) {
26 return "not available";
27 }
28 }
29
30 class Creator {
31 public IProduct FactoryMethod(int month) {
32 if (month >= 4 & month <=11)
33 return new ProductA( );
34 else
35 if (month == 1 || month == 2 || month == 12)
36 return new ProductB( );
37 else return new DefaultProduct( );
38 }
39 }
40
41 static void Main( ) {
42 Creator c = new Creator( );
43 IProduct product;
44
45 for (int i=1; i<=12; i+) {
46 product = c.FactoryMethod(i);
47 Console.WriteLine("Avocados "+product.ShipFrom( ));
48 }
49 }
50 }
51
52/* Output
53 Avocados from Spain
54 Avocados from Spain
55 Avocados not available
56 Avocados from South Africa
57 Avocados from South Africa
58 Avocados from South Africa
59 Avocados from South Africa
60 Avocados from South Africa
61 Avocados from South Africa
62 Avocados from South Africa
63 Avocados from South Africa
64 Avocados from Spain
65 */
Use
The Factory Method pattern is a lightweight pattern that achieves independence from application-specific classes. The client programs to the interface (in this case, IProduct) and lets the pattern sort out the rest.
A particular advantage of the Factory Method pattern is that it can connect parallel class hierarchies. If each hierarchy contains a FactoryMethod, it can be used to create instances of subclasses in a sensible way.
Use the Factory Method pattern when... |
---|
Consider using instead . . . .
|
Exercises
Extend the avocado sourcing example to source different seasonal products such as artichokes, asparagus, and grapes. Create them as prototypes and extend the Factory Method to work with more than one class.
Set up a system to draw circles, squares, and lines. Create a Factory Method that instantiates one of these classes at random and uses it to draw a shape of random size on the screen. Run the program to see what interesting shapes you get.
A company has a web site to display test results from a plain text file. The company recently purchased a new computer that produces a binary data file, and it has another new machine on the way that will possibly produce a different data file. The company is also considering switching to an XML format. Write a system to deal with such changes. The web site just needs data to display; your job is to provide the specified data format for the web site.
Singleton Pattern
Role
The purpose of the Singleton pattern is to ensure that there is only one instance of a class, and that there is a global access point to that object. The pattern ensures that the class is instantiated only once and that all requests are directed to that one and only object. Moreover, the object should not be created until it is actually needed. In the Singleton pattern, it is the class itself that is responsible for ensuring this constraint, not the clients of the class.
Illustration
Any class or subsystem that defines a data bank or provides access to applications, devices, or windows would benefit from the Singleton pattern. Take for example the Mac OS X Dock, shown in Figure 5.4, "Singleton pattern illustration-the Mac Dock". The Dock shows all the applications that a user accesses regularly. Those that are open have a little black arrow (Tiger) or white dot (Leopard) under them. In this case, reading from the left, Mac OS X, Preferences, Thunderbird, Firefox, Flickr Upload, Parallels for Windows, and Microsoft Word are open. Skype, Seamonkey, iPhoto, and VPUML are closed; they will be opened only when needed.
Figure 5.4. Singleton pattern illustration-the Mac Dock
In most cases, when an open application's icon is clicked, Mac OS X will immediately pass control to that application and make its window visible on the screen. This is an example of the Singleton pattern: there is one instance of the application, and all accesses go there. The applications themselves then take care of opening different windows or documents. Not all applications implement the Singleton pattern, however. For example, the Parallels virtual machine will start a new virtual machine when its icon is clicked, regardless of whether it is currently open or closed. This behavior is justified in that Parallels can support several operating systems at once, each in its own virtual machine. In contrast, Word, for example, can have many documents open, but they are all attended to by the same instance of the Word application.
Tip
The concept of a Dock does not exist in Windows Vista itself, but there are third-party applications that mimic it.[5]
Design
The Singleton pattern adds functionality by modifying an existing class. The modifications required are:
Make the constructor private and add a private static constructor as well.
Add a private static read-only object that is internally instantiated using the private constructor.
Add a public static property that accesses the private object.
It is the public property that is now visible to the world. All constructor requests for the class go to the property. The property accesses the private static object and will instantiate it if it does not already exist. The UML diagram in Figure 5.5, "Singleton pattern UML diagram" sums all this up.
Figure 5.5. Singleton pattern UML diagram
The visible players in the Singleton pattern are:
Singleton
The class containing the mechanism for a unique instance of itselfInstance
A property for making and accessing an instance of the Singleton class
Quiz Match the Singleton Pattern Players with the Dock Illustration
To test whether you understand the Singleton pattern, cover the lefthand column of the table below and see if you can identify its players among the items from the illustrative example (Figure 5.4, "Singleton pattern illustration-the Mac Dock"), as shown in the righthand column. Then check your answers against the lefthand column.
Singleton | An application icon in the Dock |
Instance | An application |
Implementation
A good implementation of the Singleton pattern in C# relies on the precise interpretation of language rules regarding construction.[6] Given the plan outlined in the previous section, a correct and elegant implementation is presented in Example 5.4, "Singleton pattern theory code".
Example 5.4. Singleton pattern theory code
1 public sealed class Singleton {
2 // Private Constructor
3 Singleton( ) { }
4
5 // Private object instantiated with private constructor
6 static readonly Singleton instance = new Singleton( );
7
8 // Public static property to get the object
9 public static Singleton UniqueInstance {
10 get { return instance;}
11 }
12 }
The constructor in line 3 is private by default. To create an object, the client calls the UniqueInstance property defined in lines 9-11, as in:
Singleton s1 = Singleton.UniqueInstance;
As soon as a class is accessed by the first call on any of its methods, properties, or constructors (and in this case, only the property is callable), its fields are set up. Here, this involves initializing the field called uniqueInstance by invoking the constructor on line 3. For illustrative purposes, we can test that the following two instantiations will refer to the same object:
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
As with all static fields, the initialization is not repeated, and therefore the second call on the Instance property, for s2, returns the same object reference.
The Singleton pattern would also like to offer lazy instantiation-i.e., that the object is not initialized until its first invocation.
Warning
Be aware that in a multithreaded program, different threads could try to instantiate a class simultaneously. For this reason, a Singleton implementation that relies on an if statement to check whether the instance is null will not be thread-safe. Do not use code like that!
To do this, we put the instantiation of the Singleton object in a nested class, SingletonCreator, as in Example 5.5, "Singleton pattern-lazy instantiation" in lines 6-11. The static constructor then falls away.
Example 5.5. Singleton pattern-lazy instantiation
1 public class Singleton {
2 // Private constructor
3 Singleton ( ) { }
4
5 // Nested class for lazy instantiation
6 class SingletonCreator {
7 static SingletonCreator ( ) {}
8 // Private object instantiated with private constructor
9 internal static readonly
10 Singleton uniqueInstance = new Singleton( );
11 }
12
13 // Public static property to get the object
14 public static Singleton UniqueInstance {
15 get {return SingletonCreator.uniqueInstance;}
16 }
17 }
Instantiation of the object will be triggered by the first reference to the static member of the nested class, which occurs in the UniqueInstance property (lines 14-16). This implementation is fully lazy but has all the performance benefits of the previous one. Note that although nested classes have access to the enclosing class's private members, the reverse is not true (hence the need for UniqueInstance to be internal here). That doesn't raise any other problems, though, as the class itself is private by default.
An important part of the Singleton pattern is the initializing of resources in the Singleton constructor. In fact, if no resources are being initialized in the constructor, it usually means that a normal static class should be used rather than a Singleton. An example of such initialization is shown in the next section.
Example: Singleton Façade
There are several patterns that make the implicit assumption that only one instance of a class will be created. The Façade pattern is one of these-there should be only one instance of each Façade. The Singleton pattern can enforce this requirement.
To integrate the Singleton into an existing pattern, we again follow the steps outlined earlier. The revised constructor for the Façade is as follows:
// start Singleton pattern
// private constructors
Façade( ) {
a = new SubsystemA( );
b = new SubsystemB( );
c = new SubsystemC( );
}
static Façade( ) {}
// private object
static readonly Façade uniqueInstance = new Façade( );
// public static property
public static Façade Instance {
get {return uniqueInstance;}
}
// end Singleton pattern
Then, in the client, instead of:
Façade façade = new Façade( );
(which would not compile because the constructor is private now), we use:
Façade façade = Façade.Instance;
If we need to refer to the Façade again from another place in the system, we can declare a new variable and create a new Façade object, but it will still access the same instance.
Use
Many other patterns-for example, the Builder, Prototype, and Abstract Factory patterns-can make use of the Singleton pattern to ensure that only one copy of a class is created. However, the effectiveness of the Singleton pattern relies entirely on developers all using the same rules. Even though implementing a singleton as outlined in this chapter doesn't require much coding effort, it would be nice to be able to reuse the implementation. But if the standard rules are not followed, one developer may use an Instance property and another a Create method, for example, or one may use the nested class while the other doesn't. Even if two developers use the same nested class, they might give the class different names. The result could be a complete mess, even for a simple singleton! A solution to this issue is proposed in the "the section called "Pattern Comparison" section, later in this chapter.
Use the Singleton pattern when ... |
---|
|
Exercises
The community in the SpaceBook application (Example 2.4, "Proxy pattern example code-SpaceBook") was created as static. Rework SpaceBook as a Singleton class.
Consider an international airline with a number of servers situated all over the world that are used to process reservations. At different times of the 24-hour clock, some will be heavily loaded and some lightly loaded. Each server can apply to a load balancer to be added to or removed from the system temporarily as time goes by. Model this system with the load balancer as a singleton.
Pattern Comparison
There is a symbiosis between patterns and languages, and it is dynamic. As this book has shown, the new features of C# 3.0 have made implementing patterns easier. (A quick comparison of the code in this book with standard C# pattern code will confirm this statement.) Two of the patterns discussed in this chapter-the Prototype and Singleton patterns-raise some interesting points about pattern language features.
Implementing the Prototype pattern was quite a challenge 10 years ago, when obtaining a deep copy of an arbitrary data structure meant creating a graph traversal algorithm from scratch. Now, it can all be done with one method call to Serialize, plus some associated setup of streams. This facility is available to all languages in .NET, and Java has a similar mechanism. Thus, the implementation of the pattern as it was originally envisaged has almost disappeared. Nevertheless, its intent remains, and managing prototypes is still very much part of the developer's task.
Considering the Singleton pattern, is there any way in which the language might help to make it reusable?[7] One solution for achieving reusability is to use C# generics, as shown in Example 5.6, "Singleton pattern generic code".
Example 5.6. Singleton pattern generic code
1 using System;
2
3 // Singleton Pattern Judith Bishop Nov 2007
4 // Generic version
5
6 public class Singleton <T> where T : class, new( ){
7 Singleton( ) { }
8
9 class SingletonCreator {
10 static SingletonCreator ( ) {}
11 // Private object instantiated with private constructor
12 internal static readonly T instance = new T( );
13 }
14
15 public static T UniqueInstance {
16 get {return SingletonCreator.instance;}
17 }
18 }
19
20 class Test1 {}
21 class Test2 {}
22
23 class Client {
24
25 static void Main ( ) {
26 Test1 t1a = Singleton<Test1>.UniqueInstance;
27 Test1 t1b = Singleton<Test1>.UniqueInstance;
28 Test2 t2 = Singleton<Test2>.UniqueInstance;
29
30 if (t1a == t1b) {
31 Console.WriteLine("Objects are the same instance");
32 }
33 }
34 }
The Singleton class corresponds to that in Example 5.5, "Singleton pattern-lazy instantiation", with the addition of two generic constraints (see Chapter 6, Creational Patterns: Abstract Factory and Builder) to ensure that the type used for the generic instantiation is a class and has a constructor (line 6). Variables of any class can be created with the same Singleton class, as shown in lines 26-28. Given how neat and tidy this class is, it would be useful to draw it into the language. Perhaps we can look ahead to a new version of C#, where a new language construct-singleton-will define that a class is in fact a singleton. In theory, the language will make sure that the singleton implementation has an Instance property, and the developer will not have to state whether the constructor of the singleton is private-the language will force the constructor to be private. The process of language and pattern convergence is not at an end, so the Singleton construct may well come into the language one day.
[5] * See http://www.stardock.com/products/objectdock/.
[6] * See Jon Skeets's article at http://www.csharphelp.com/archives4/archive670.html.
[7] * Thanks to Alastair van Leeuwen for initiating this discussion and for the generic example.