⬆️ ⬇️

Singleton or single class?

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; //   ... public static Session Instance { get { // ... return _instance; } } public IUser GetUser() { // ... } public bool IsSessionExpired() { // ... } public Guid SessionID { get { // ... } } } 




Static class:

 public static class Session { //   1 public static IUser GetUser() { // ... } //   2 public static bool IsSessionExpired() { // ... } // ... //   N public static Guid SessionID { get { // ... } } } 




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() { // ... } public bool IsSessionExpired() { // ... } public Guid SessionID { get { // ... } } } 


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:

 //        ISession public class Session: CoreObject, ISession { private Session() { } public static Session Instance { get { return Singleton<Session>.Instance; } } } //                //     public class VpnSession : ISession { } public interface ISessionManager { ISession GetSession(Guid sessionID); //   ISession,     bool IsSessionExpired(ISession session); } 




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:

 // ... ISessionManager _sessionManager; // ... bool isExpired = _sessionManager.IsSessionExpired(Session.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 { // ... public static T Instance { get { if (_instance == null) { //  " " _instance = CreateInstance(); } return _instance; } } } 


You can also add an operation to delete a singleton instance:

 public class Singleton<T> where T : class { // ... public static T Instance { // ... } //   ! public void RemoveInstance() { _instance = null; } } 


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 { // ... public static T Instance { get { if (!IsAlive) { //    RemoveInstance(); } if (_instance == null) { //  " " _instance = CreateInstance(); } return _instance; } } private void RemoveInstance() { _instance = null; } } 




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; } } } //       public class Session : Singleton<Session> { protected Session() : base(new ConcreteFactory()) { } // ... } 


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:



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.

Source: https://habr.com/ru/post/103681/



All Articles