3

Although I do find some (apparently old) posts on the topic on the web, I could not find one here at SE. Thought of raising this here to see if what I read is accurate/is all there is to it.

So basically, in trying to structure and document my application, one of the prominent architecture principles used is Singletons. It's a .NET service (.NET Core Worker Service) using some 20 singletons, which represent "manager objects" or modules, usually implementing a system feature added in a given Sprint.

Immediately - and for the first time, I guess - I asked myself why singletons and not static variables.

From googling, the main difference seems to be Singletons allowing lazy loading whilst static variables are loaded/allocated the moment the app initializes.

Are there other meaningful differences, in general - and in a .NET context, in particular ?

Veverke
  • 491

5 Answers5

10

You're pretty much correct - singletons are just a fancy wrapper around a global variable. You can tell this because when you create a singleton, you end up writing code like

class Singleton
{
  private static Singleton _theOneAndOnlyInstance = new Singleton();

public static Singleton Instance => _theOneAndOnlyInstance; }

(maybe with some extra boilerplate if you want lazy initialization).

This is the primary reason the singleton pattern is often criticised, as it in many cases doesn't solve any of the issues caused by using a global variable.

4

Why singletons and not static variables?

It’s a getter.

If you let everything that needs a reference to the ‘manager’ access it directly you wouldn’t be able run code on access without rewriting everything that accesses it.

For example, to change it from greedy loading to lazy loading you now need to run code later, on access, even if only once.

And so, even though you’re happy writing greedy loading code today, you’re stuck writing boiler plate getter code you don’t need. Why? Because later you might want to make it lazy loading. You have to keep accessing code from knowing or caring which you’re doing.

That is, unless you have properties. Like you do in C#. With those you don’t have to write getter code until you need getter code. Something I get jealous of every time I touch Java.

So why singletons and not static when greedy loading is fine? Because it was written in Java. Or by a Java coder that didn’t know any better.

But singletons are evil!

If you’re stuck in some framework with no access to main or any function that’s only called once (hence no composition root) then they are still useful. Even if they are evil globals. Use them and access them sparingly. If anyone gives you crap about them when you’re stuck in this situation ask them for a better idea.

candied_orange
  • 119,268
1

Singletons are supposed to always exist when they are needed. Usually you have a function or getter returning a reference to the singleton, and the first call creates it. From then the singleton exists forever.

Static variables get one thing right: Their constructor is called the first time the variable is accessed. What’s quite bad is that you can assign to a static variable. What’s really, really bad is that when your program exits, destructors for all static variables are called in an unpredictable order. So you can access the static variable after it’s destructor is called. That’s fun. And if you use multi-threading you can use a static variable while it’s destructor is running, for extra fun.

gnasher729
  • 49,096
1

tl;dr: A singleton type enforces that only a single instance can exist at any time, usually by handling instantiation itself and preventing anybody else from creating other instances. That’s its defining feature. If you need that particular enforcement, you need a singleton. If you just need a globally accessible object, a “normal” global (a.k.a. static) variable will do.

The only instance of a singleton type acts a lot like a global variable. You might call it through a function instead of directly, but that’s a detail. Essentially it’s still a globally accessible object.

About lazy loading

Singletons can be both lazy or non-lazy (is there a better word for non-lazy?). Since .NET is foreign to me consider this common singleton implementation in C++.

class Singleton {
public:
    static Singleton& get() {
        static Singleton instance;
        return instance;
    }

private: // private ctors prevent // the rest of the world from instantiating Singleton() = default; // default ctor // ... same for copy ctor etc. };

That’s lazy. instance isn’t constructed until you call Singleton::get() for the first time. Then that object lives until the program terminates. In particular, calling get() again does not create any more instances or replace the existing one.

You can make it non-lazy, too:

// in file: singleton.hpp
class Singleton {
public:
    static Singleton& get() {
        return instance;
    }

private: Singleton() = default; // ... same for copy ctor etc.

static Singleton instance;

};

// in file: singleton.cpp Singleton Singleton::instance;

Now instance is constructed immediately when the program starts and get() returns a reference to the already existing object. Ignore the split into two files. That’s a C++ language detail.

I’d assume that most languages have the features to implement both lazy and non-lazy singletons. In any case singletonness and lazyness are orthogonal concepts.

Instantiation control vs. access scope

When looking at the fundamental nature of singletons and global variables you can say:

  • Singletons are about restricting instantiation.
  • Global variables are about making the same object accessible in a broad-ish scope.

There is a good bit of overlap. A singleton instance that’s not accessible in a relatively broad scope wouldn’t be particularly useful. And a global variable can be a singleton by convention because many different places in the program use that one canonical object.

The similarities continue when considering lifetime. Both singletons and global variables tend to be instantiated early in a program’s life and tend to exist until it terminates.

What I wouldn’t count as a meaningful difference is the usual get() or instance() access function of a singleton. Hide a global variable behind such a function and you have the same thing, including the possibility to do additional work when the function is called.

besc
  • 1,163
  • 6
  • 7
-2

Static objects are singletons, but assigning an object into a static variable isn't the only way to deal with singletons.

One of the cons of working with static things is that they are not easy to mock, which means that the class which is using the static object or function cannot be unit tested properly, without depending on the actual implementation of the object / function.

An example worth 1000 words:

public class MyService {
     public string SayHello() {
        string name = NameClient.SingletonInstance.GetName();
        return "Hello " + name;
     }
}
public class NameClient  {
     private static NameClient _instance;
     public static NameClient SingletonInstance {
         get {
             if (_instance == null) _instance = new NameClient();
             return _instance;
         }
     }
     private NameClient() {}
     public string GetName() {
       //do an http request to get a name
     }
  }

In this example we have a Service class (MyClass) which uses a client (NameClient) to get a value, than it does some business logic on it and returns another value. The NameClient class is a singleton exposed through a static property which does a http request to get the name.

Suppose that you need to unit test the business logic of MyService.SayHello: you can't avoid to perform the http request in the unit test, which is pretty bad.

Dependency Injection helps to deal with this

    public class MyService {
         private readonly INameClient _nameClient;
         public MyService(INameClient nameClient) {
           _nameClient = nameClient;
         }
         public string SayHello() {
            string name = _nameClient.GetName();
            return "Hello " + name;
         }
    }
    public interface INameClient {
         string GetName();
    }
    public class NameClient: INameClient  {
         public string GetName() {
           //do an http request to get a name
         }
    }
    public class Startup {
    ....
         public void RegisterServices(IServiceCollection services) {
             services.AddTransient<MyService>();
             services.AddSingleton<INameService, NameService>();
         }
    ....
    }

In this way MyService doesn't depend on the actual implementation of NameService, you can use mocks for unit tests and most important you leave the responsibility of dealing with the lifecycle of the singleton instance to the IOC container, which should be smart and avoid memory leaks

ddfra
  • 107