1

I make sure when designing software/firmware to make heavy use of dependency injection so that different pieces of the application are not directly coupled to one another. This also allows me to (potentially) unit test each piece of code using mock dependencies.

However, the dependencies that need to be passed into constructors need to be created somewhere. In other words, the application has to start from nothing at a single point (i.e., main).

I am wondering, is it good practice to represent the application as a whole as a singleton?

class IApplication
{
public:
    virtual int run(int argc, char* argv[]) = 0;
};

class Application
{
public:
    static IApplication& instance();
};

To be used like so:

int main(int argc, char* argv[])
{
    return Application::instance().run(argc, argv);
}

The implementation of the Application class then is in charge of constructing all the root dependencies and those objects which depend on them.

If this is not a good way to start an application, please explain why and provide a better alternative.


Based upon the very helpful answers so far, I thought it would be useful to add some more context. I develop firmware for embedded systems. I can write my application code so that it is abstracted (e.g., via dependency injection and abstract interfaces) from the hardware (i.e., register and memory-mapped addresses). However, at some point, I need to directly access those peripherals to get the abstract drivers to be injected. Those peripherals are at specific addresses in memory that are accessed globally (i.e., they are "singletons"). I would probably write Application to access these global hardware "objects" directly. However, if I do that, creating 2 instances of Application would be incorrect (they both directly access the same hardware), thus I was thinking, Application should only have a single instance and thus be a singleton.

I could get extremely abstract like so:

Platform myPlatform(Processor::instance());
Appplication myApplication(platform);

Where the application runs on any "Platform" (probably an interface) and the Platform runs on a specific processor/hardware of which there is a single instance (i.e., the one executing the code); but this will get a bit inefficient.

6
  • 2
    Other than letting you avoid global variables for dogmatic reasons, what value does this construct give you? Commented Dec 18, 2023 at 20:19
  • 1
    @PhilipKendall I guess that's part of my question. Would the alternative be a single instance of Application as a global variable? If yes, I see a few advantages. 1) A single, know entry point. 2) A well-defined time of initialization/construction for the Application instance (static globals are initialized in an undefined order). Commented Dec 18, 2023 at 20:22
  • @PatrickWright ask a separate question then? It will be closed and opinion-based and too broad and we will rest in peace.
    – Basilevs
    Commented Dec 18, 2023 at 20:45
  • @PhilipKendall for dogmatic reasons, one avoids shared mutable state, not globals.
    – Basilevs
    Commented Dec 18, 2023 at 20:53
  • Compare how ASP does this in C#: learn.microsoft.com/en-us/aspnet/core/fundamentals/… ; there's a single instance of the Application, but it's just a regular stack object. Constructing everything is done with a Builder pattern and dependency injection.
    – pjc50
    Commented Dec 19, 2023 at 14:47

4 Answers 4

8

TLDR: singleton is about visibility as much as about lifetime.

Not for this context

There is no reason to use a singleton in this context. The application class does not expose any readable state so other components have no need to access it.

Not after fixes

The singleton pattern allows access to shared interface to any component of the program, and therefore can't expose any powerful methods controlling lifetime of the whole application or changing fundamental configuration.

Even if your example had a state - the state would be ill defined, as in the sample you provide, any component of your program can call run() method replacing and corrupting the global state.

If you fix the issue by adding protections to or around run() method, you will lose consistency of its behavior as there would be no guarantee whether any particular call of run method is a first one and which set of arguments is actual and in use.

Hypothetically, you could remove run() method from the public interface and leave only const methods available. But then you just get yet another unprotected, untestable interdependency between your components, which are no better than any other read-only mutable singleton. The problem of shared mutable state persists.

Solution

Do not use Singleton pattern. Do not use shared mutable state in general. Create an Application class instance in the program entrypoint.

8
  • Assuming C++, would it be smart to make the Application instance as "static" within main() so that the instance within the entrypoint is not created on the stack and so the instance isn't just a static global? Commented Dec 18, 2023 at 21:07
  • 1
    @PatrickWright the allocation of the Application does not have any impact on the design or behavior of your application, only visibility does. I would not recommend static allocation unless absolutely necessary, as it is less flexible and may cause linking issues.
    – Basilevs
    Commented Dec 18, 2023 at 21:13
  • @PatrickWright specifically in C++, marking locals as static in main is pointless, because calling main is undefined behaviour.
    – Caleth
    Commented Jan 1 at 12:45
  • @Caleth, even if calling main is UB, static locals work just like in any other function and have corresponding lifetime.
    – Basilevs
    Commented Jan 1 at 17:30
  • Yes, that's what I meant. From the pov of the as-if rule, there is no difference adding static to things local to main, there is only ever one instance
    – Caleth
    Commented Jan 1 at 17:54
5

From what you have written about the responsibilities of the shows classes, I think you mixed up the Singleton pattern with the Factory Method pattern (not to be confused with "Abstract Factory"). Your Application class could be just a factory for Application objects and hence should be named ApplicationFactory (instance could be renamed to create). The one IApplication instance can be provided as a reference to a single instance or simply as copy, it is unlikely that it will matter.

Whether you really need an interface/virtual base class IApplication with potentially different implementations (or not) is unclear from your question, so let me assume you don't need it and it can be renamed Application with a non-virtual run method.

This leads to:

class Application
{
public:
    int run(int argc, char* argv[]);
};

class ApplicationFactory
{
public:
     // some complex code to create an "Application" object,
     // maybe complex enough to justify an own class
     static Application create();
};

to be used like this:

int main(int argc, char* argv[])
{
    return ApplicationFactory::create().run(argc, argv);
}

This is structurally almost identical to your original approach, but makes a lot more sense to me.

In case you really need global access to the one Application instance throughout your program (for which your question gives actually no indication), then you can make this also Singleton, of course. For this, add a static instance method to my version of Application, make create a private method of Application and use it there.

class Application
{
    Application create();

public:
    static Application &instance();  // uses create on first invocation

    int run(int argc, char* argv[]);
};

int main(int argc, char* argv[])
{
    return Application::instance().run(argc, argv);
}

I removed the factory class here, and made the factory method create a private method of Application, because this will allow more effective prevention against the creation of multiple Application instances . If you don't need that prevention, you can still let create be part of its own ApplicationFactory class.

1
  • I think this is closer to what I am imagining in my head. To add some context, I develop embedded software.. Traditionally, to access the hardware, you end up essentially accessing globals (i.e., direct memory addresses of peripheral registers). I thought "singleton" because the Application would be accessing these globals, thus two Application objects would conflict (because they access the same globals). Commented Dec 19, 2023 at 14:35
1

Though answers overall wanted you to avoid the Singleton, I have some points.

You have a container, for all those global static things. You want a dependency injection container.

As you make distinction between the application in production/development and the unit testing of the application, you might want to inject different things like mocks for the unit testing. Or development.

There is also the external configuration side. For instance for the multi-document interface, where one server application starts and for instance opens document X. Now a document Y is opened by another instance of the application. It looks whether there is already a (server) instance running an sends "open Y" to that server, then quits. Here is it more a matter of ports, entirely separated inaccessible Application objects. But you want to have an externalized configuration for this "server."

In both cases a dependency injection container seems just fine. However letting the application holding that container is not reversal of control. Take care of real dependency injection, instead of holding global objects like for internationalisation.

My advice is to immediately start with three scenarios at the same time:

  • unit testing for test driven development;
  • development product for the prototype;
  • staging product to show the tester or product owner.

So my point: container == Application is not that weird. But behold specific customisation of globals. By the way, the java Spring framework uses an applicationContext as injection container.

(I have practical successful project experience, but might not be the best advisor on architecture.)

-1

There is never a good reason to use a singleton. A singleton is just a global that involves more code (i.e., more places for bugs to hide). The only reason people use singletons is because they want shared mutable state, are aware of the taboo against globals, and don't understand the reason for it.

If you have shared mutable state, ask yourself if you really need it. If the answer is yes, then just use a global and be done with it. The thing that makes globals reasonably safe is carefully documenting what functions use them and how, not introducing a bunch of unnecessary design pattern cruft.

5
  • The OP wrote they have hardware restrictions where only one application can be run on their hardware platform at a time. Having an Application class where the object represents that application and modeling this as a Singleton seems IMHO to be an abstraction which fits to this specific case.
    – Doc Brown
    Commented Dec 21, 2023 at 9:31
  • @DocBrown I never said they should run multiple copies of the class. I said that instead of writing a class with memory mapped vars and then add code to make sure only one copy of the class can be created and then instantiating it and then calling Application::instance().run(), just have memory mapped globals and call the code normally, because what OP has here is exactly the same as that, only with a bunch of extra lines of code that serve no purpose whatsoever (especially bad on an embedded system, depending on how tight memory is and how good the compiler is at removing all the OO junk).
    – Ray
    Commented Dec 21, 2023 at 15:16
  • class IApplication { public: virtual int run(int argc, char* argv[]) = 0; }; class Application { public: int run(int argc, char *argv[]) { CODE_HERE } static IApplication& instance() { MORE_POINTLESS_CODE_HERE }; int main(int argc, char* argv[]) { return Application::instance().run(argc, argv); } can be replaced by int main(int argc, char **argv) { CODE_HERE } without changing the functionality at all.
    – Ray
    Commented Dec 21, 2023 at 15:17
  • 1
    Your comments say "don't use classes at all, don't use C++, use only C, will save you a lot of boilerplate code and other hassle". That's ok, if you don't like this "OO junk", I won't press you to use it. However, others (like me, and probable the OP) see some value to use classes not just as a syntactical construct, but also as some means for abstraction. To be fair, I see this value especially when programs get larger, for programs with less than 2K-3K lines of code I often prefer a non-OO approach, too.
    – Doc Brown
    Commented Dec 21, 2023 at 16:56
  • 1
    @DocBrown I don't object to using objects when they're the correct approach. They often add significant value. But "OO junk" referred to "OO (or design patterns) for the sake of being OO" (I ran out of characters, so was less clear than I would have liked; I apologize for the confusion). And that's what's happening here. If we don't have multiple instances of an object, why are we defining a class of objects? It's cargo-cult programming; we're using objects in situations where they aren't useful because we don't understand the purpose they're intended to serve.
    – Ray
    Commented Dec 21, 2023 at 19:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.