You have a data type that will be used by several clients. This data type will create and hold a reference to another object that takes a long time to create; this could be a database connection or an object that is made up of many internal objects, which also must be created along with their containing object. Rather than allow this data type to be instantiated many times by many different clients, you would rather have a single object that is instantiated only one time and used by everyone.
The following two code examples illustrate the two
singleton design patterns. The first design
always returns the same instance of the OnlyOne
class through its GetInstance
method:
public sealed class OnlyOne { private OnlyOne( ) {} private static OnlyOne theOneObject = null; public static OnlyOne GetInstance( ) { lock (typeof(OnlyOne)) { if (theOneObject == null) { OnlyOne.theOneObject = new OnlyOne( ); } return (OnlyOne.theOneObject); } } public void Method1( ) {} public void Method2( ) {} }
The second design uses only static members to implement the singleton design pattern:
public sealed class OnlyStaticOne { private OnlyStaticOne( ) {} // Use a static constructor to initialize the singleton static OnlyStaticOne( ) {} public static void Method1( ) {} public static void Method2( ) {} }
The singleton design pattern allows one and only
one instance of a class to exist in memory at any one time. Singleton
classes are useful when you need a single way of accessing a resource
such as a database connection or a file on a network. Many times,
manager objects are created as singletons. For example, an object
pool manager most likely would be a singleton; this allows a single
access point to the pool for the entire application. Several examples
of the singleton class can be found in the FCL, such as the
System.Diagnostics.Trace
,
System.Diagnostics.Debug
, and
System.IO.Path
classes, to name a few.
The OnlyOne
class implements the singleton pattern
by using a static field of the same type as its containing
class—the OnlyOne
type, in this
case—and a common access point called
GetInstance
. The GetInstance
method is called by the client code to obtain the one and only
instance of the OnlyOne
class. If there are no
instantiated OnlyOne
classes, a new one is created
and returned. Once the OnlyOne
object is created
for the first time by the GetInstance
method, a
reference to it is placed in the theOneObject
static field. This field contains a reference to the only
OnlyOne
object running in the application. Note
that because static fields do not cross application domain
boundaries, a single instance of the OnlyOne
object is created for each application domain in a process. On
successive calls to GetInstance
, the
OnlyOne
object referenced by
theOneObject
field is returned. All access to the
OnlyOne
object is performed on the object returned
by the GetInstance
method.
The default constructor for this class has its accessibility made
private to prevent code from accidentally creating an instance of
this class. Setting the default constructor to private and
disallowing any nonprivate constructors in this class is critical to
a good singleton pattern. If code were to accidentally create a
second or third class of this type, your application code would then
have more than one access point to a resource, or you might have more
than one manager type object managing a set of objects. When setting
the default constructor to private, or if there is no default
constructor in your class, you should mark the class with the
sealed
keyword. This keyword prevents any class
from inheriting from this class. If a class were to inherit from this
class, and if this new class did not provide an explicit constructor,
a default constructor would be provided by the compiler. This default
constructor is written to automatically call the base
class’s default constructor. If the base
class’s default constructor is private, it is
inaccessible to its subclasses. The compiler will catch this type of
error, but it makes the code more readable and maintainable if the
sealed
keyword is used to mark a singleton class.
The OnlyStaticOne
class implements the singleton
pattern in a much different way. This class makes exclusive use of
static members to allow only one access point to this
class’s members (the use of the word
“instance” would be misleading
here—static members do not operate on an actual instance of an
object, but rather on the type itself). Similar to the previous
singleton pattern example, this class is also marked as sealed, and
it has a private default constructor.
There are advantages and disadvantages to using each of these
implementations of the singleton design pattern. The advantages of
using the OnlyOne
style are:
Converting the class into a nonsingleton class can be done easily. To
do this, eliminate the sealed
keyword on the
class, make the constructor public, and remove the
GetInstance
method and the
theOneObject
field.
Modifying this pattern to allow a fixed number of instances of this type of object to be instantiated is fairly easy to do. The code to do this is shown here:
public sealed class OnlyThree { private OnlyThree( ) { count++; } private static OnlyThree[] anObject = new OnlyThree[3]; private static int count = 0; private static int lastObjReturned = 0; private const int maxInstances = 3; public static OnlyThree GetInstance( ) { if (count < maxInstances) { OnlyThree.anObject[count] = new OnlyThree( ); lastObjReturned = count; } else { if (lastObjReturned == 1) { lastObjReturned = maxInstances; } else { lastObjReturned--; } } return (OnlyThree.anObject[lastObjReturned -1]); } public void Method1( ) {} public void Method2( ) {} }
Subclassing and overriding/hiding members of this class are possible.
Note that you must remove the sealed
keyword
first.
The disadvantages to this style are:
The code is a bit more complex than simply using all static members.
You must always obtain an object reference to the one instance of
this class through the GetInstance
method.
If coded incorrectly, this type of singleton could allow more than one of these objects to be created. For instance, this could happen if the default constructor’s accessibility was not set to private and you forgot to write the constructor in this class.
The advantages to using the OnlyStaticOne
style of
the singleton class are:
It is easy to read and therefore easy to maintain.
It is easy to write.
It is easy to use since there is no GetInstance
method to call.
Making the default constructor (or any other constructor) nonprivate would have little impact on the use of this class, since instantiating a class with no instance members other than an instance constructor is of little use.
The disadvantages to this style are:
This style may be more difficult to convert to a regular nonsingleton class if future requirements demand it.
A single instance of the OnlyOne
object is created
for each application domain in a process. If your process will be
hosting more than one application domain, you should consider using
the first style of singleton class.
Subclassing this class requires more work than the previous singleton style.