The article will be useful primarily to developers who are lost in interviews when they hear the question "What are the main differences between a singleton and a static class, and when should you use one and when will the other?". And it will certainly be useful for those developers who, with the word “pattern,” become disheartened or ask to stop expressing themselves :)
What is a static class?
To begin, remember what a static class is and what it is for. In any CLI-compatible language, the following global variable encapsulation paradigm is used:
no global variables. All members, including static ones, can be declared only within a class, and the classes themselves can (
but should not ) be grouped in a namespace. And if earlier it was necessary to imitate the behavior of a static class using a private constructor, then in the .NET Framework 2.0, support for static classes was added at the platform level. The main difference between a static class and a normal, non-static class is that it is impossible to create an instance of this class using the
new operator. Static classes are in fact a kind of namespace - only in contrast to the latter, they are intended to contain static variables and methods and not types.
What is Singleton?
One of the
generating patterns, first described by the
"gang of four" (GoF). Ensures that a class has
only one instance , and provides a
global access point to it. We will not consider this pattern in detail here, its purpose and the tasks it solves - there is a mass of detailed information about it in the network (for example,
here and
here ). I will only note that singletons are thread-safe and not, with simple and delayed initialization.
')
And if there is no difference - why produce more?
So what is the difference between these two entities and when should they be used? I think this is best illustrated in the following table:
| Singleton
| Static class
|
---|
Number of access points
| One (and only one) access point - static field Instance
| N (depends on the number of public members of the class and methods)
|
Class inheritance
| Perhaps, but not always (about this - below)
| Impossible - static classes cannot be instantiated because objects of static classes cannot be instantiated
|
Interface Inheritance
| Perhaps without any restrictions
| Impossible for the same reason that class inheritance is impossible
|
Possibility to pass as parameters
| Perhaps because Singleton provides a real object.
| Missing
|
Monitoring the lifetime of the object
| Perhaps - for example, deferred initialization (or creation on demand )
| Impossible for the same reason that class inheritance is impossible
|
Using an abstract factory to create an instance of a class
| maybe
| Impossible due to the absence of the possibility of creating an instance
|
Serialization
| maybe
| Inapplicable due to lack of copy
|
Let us consider in more detail the above criteria.
Number of access points
Of course, they mean
external access points, in other words, a public contract for interaction between a class and its clients. It is more convenient to illustrate with code:
Singleton in the "canonical" implementation:
public class Session { private static Session _instance;
Static class:
public static class Session {
Class inheritance
With the inheritance of static classes, everything is simple - it is simply not supported at the language level. With Singleton, things are a bit more complicated For ease of use, many developers most often use the following pattern implementation:
public class Singleton<T> where T : class { private static T _instance; protected Singleton() { } private static T CreateInstance() { ConstructorInfo cInfo = typeof(T).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]); return (T)cInfo.Invoke(null); } public static T Instance { get { if (_instance == null) { _instance = CreateInstance(); } return _instance; } } } public class Session : Singleton<Session> { public IUser GetUser() {
And since multiple inheritance is prohibited in C # and in any CLI-compatible language, this means that we will not be able to inherit the Session class from any other
useful class. The way out is to do a singleton control of access to an object instance:
public class Session : CoreObject { private Session() { } public static Session Instance { get { return Singleton<Session>.Instance; } } }
Interface Inheritance
Using interfaces allows you to achieve greater flexibility, increase the amount of reusable code, increase testability, and, most importantly, avoid the
strong connectivity of objects. Static classes do not support inheritance in principle. Singleton, on the other hand, fully supports interface inheritance, since it is a regular class. But it’s worth using this feature only if it is planned to transmit a singleton instance as input parameters in mixed scenarios or broadcast abroad. An example of a mixed script:
Possibility to pass as parameters
For static classes, this is not supported - it is possible to transfer only the type, but in most situations it is useless, except in cases where the reflection mechanisms are used. Singleton is essentially a regular object instance:
Monitoring the lifetime of the object
The lifetime of a static class is limited by the lifetime of the domain - if we created this domain manually, then we indirectly control the lifetime of all its static types. The life time of a singleton we can manage at our will. A striking example is deferred initialization:
public class Singleton<T> where T : class {
You can also add an operation to delete a singleton instance:
public class Singleton<T> where T : class {
This operation is extremely unsafe, since a singleton can store some state and therefore its re-creation may have undesirable consequences for its clients. If, nevertheless, the need for such a method has arisen (which most likely indicates design errors), then you should try to minimize the possible evil from its use - for example, make it closed and call inside the Instance properties under certain conditions:
public class Singleton<T> where T : class {
Using an abstract factory to create an instance of a class
The static class does not support this feature because you cannot create an instance of a static class. In the case of singleton, everything looks simple:
public interface IAbstractFactory { T Create<T>(); bool IsSupported<T>(); } public class Singleton<T> where T : class { private static T _instance; private static IAbstractFactory _factory; protected Singleton(IAbstractFactory factory) { _factory = factory; } public static T Instance { get { if (_instance == null) { _instance = _factory.Create<T>(); } return _instance; } } }
However, in the version with singleton aggregation, you will have to apply a not-quite-beautiful and slightly bulky solution:
public class Session : CoreObject, ISession { private class SessionSingleton : Singleton<Session> { protected SessionSingleton() : base(new ConcreteFactory2()) { } } private Session() : base(new CoreContext()) { } public static Session Instance { get { return SessionSingleton.Instance; } }
Serialization
Serialization is applicable only to instances of classes. A static class cannot have instances, so there is nothing to serialize in this case.
So what use singleton or static class?
In any case, the choice of a solution depends on the developer and on the specifics of the problem he is solving. But, in any case, the following conclusions can be made:
The use of singloton is justified when:- You must inherit classes or interfaces or delegate the construction of objects to the factory.
- Class Instances Required
- It is necessary to control the lifetime of the object (although this is a very rare task for a singleton)
- It is necessary to serialize the object (such a task is hypothetically possible, but it is difficult to imagine usage scenarios)
The use of static classes is advisable when you do not need to implement any of the scenarios listed for a singleton. The main purpose of static classes is still in the grouping of logically similar methods, constants, fields and properties. For example:
System.Math ,
System.BitConverter ,
System.Buffer ,
System.Convert , etc.