Good use of a Singleton?

Started by
28 comments, last by Sneftel 15 years, 11 months ago
Greetings! I know that those who know better say, whenever possible, not to use Singletons. I'm not doubting those claims, but what I would like to ask if this would be an exception to the rule. Suppose I'm developing a program and would like to add a logging class to it. I'm going to create the logging class myself as a learning excercise to experiment with different methods. My biggest question is how to easily allow virtually any class to log a message. The simplest (or, should I say, hackish) way would be to make the logging class a Singleton. By this point, those who know better than I would say "Don't do that!". I believe one of the arguments is what I may want more than 1 logger at a latter time, which I think is a good argument. However, what if the Singleton I use isn't the logging class itself, but rather a Logging Factory that will create (or simply return a handle to) the type of logger that is desired. This would allow me to create multiple instances of the same logging class as well as different types of loggers. I don't see why you couldn't claim that you will only ever need a single Logger Factory. This way I maintain the ease of access to a logger from anywhere in my program (via the Logger Factory Singleton) as well as the flexibility of creating as many different types of loggers as I want. Are there any drawbacks to this that I am not seeing? Thanks.
Advertisement
Geez, not with logging again...

Quote:I'm not doubting those claims,


Yes, you are.

Quote:I don't see why you couldn't claim that you will only ever need a single Logger Factory.


<morbo>Singletons do not work that way.</morbo>

The question to answer is: Will the universe implode if you have two factory instances. If answer is yes, then it's justified use of a global. If not, you do not need a singleton.


Quote:This way I maintain the ease of access to a logger from anywhere in my program


Ah, your real problem is access to logging system, not logging itself. Logging system doesn't need a global. If you choose to use it, fine. But have you even considered the alternatives?

For example, passing either the logging "factory" to objects, or passing "application" or "state" to your objects? For example:
struct Application {  ...  LoggingFactory & getLoggingFactory();  ...  MemoryManager & getMemoryManager();};...struct MyFoo {  MyFoo(Application & currentContext)    : logger(currentContext.getLoggingFactory())    , buffer(currentContext.getMemoryManager().allocate(1024))    ...};


Here, if you ever need multiple states within same process, it's no problem. Want concurrency, create multiple applications, each running in its own thread, no locks needed.
If you use a factory to create your loggers, why would the factory itself need to be a singleton? You could just create an instance of the factory when you need it and have it create a logger for you.

Then, however, you probably don't want to use separate logger each time you want to log something -- unless your loggers maintain some global state (via static variables, for instance) that would prevent you from attempting to open the same file multiple times for writing.

The factory could also keep a list of open loggers and just give you an instance of an already opened logger -- but then this would be just a different implementation of singletons.

It really isn't that hard to pass an instance of a logger wherever you want your logging to be done. This method enables you to define an abstract Logger class from which you can then derive further concrete classes -- like StdoutLogger, FileLogger, SocketLogger, ... Your code then doesn't have to know where the output itself will go -- if anywhere. (NullLogger allows you to elegantly implement --quiet flag to surpress any unnecessary output.) Singletons (or a factory of loggers) wouldn't be much of help here -- your client code would still have to do something like Logger* l; if ( !quiet ) l = loggerFactory.getStdoutLogger(); else l = loggerFactory.getNullLogger();. Your client classes shouldn't really care about wheter they should output anything and where -- just give them an object, have them stuff the output into it, possibly specifying how important the message is, and let the logger take care of the details.
The logger is a good example of a good use of a global variable, because a bunch of different classes will want to access it even if it's not technically "their job". It is a good example of a bad use of a singleton, since it restricts users from creating a second logger even when that's really, really what they want to do.
The Singleton pattern ensures there will only ever be a single instance of a class. Most often you do not need this property since in many cases it is absolutely legal to have more than one instance of the same class. And if not you can simply just create only a single instance.

If you need global access to your logger and don't want to pass the instance to every class/function etc. that needs it you can simply use a global variable. Some may now scream about the use of global variables, but IMO justified use of globals is not bad at all. Just look at std::cout etc.; they are globals too.
Like Sneftel said, you can certainly argue that a logger, or a log factory, should be global.

But why prevent me from creating two of them?
What do you gain by adding this constraint? Why not just make it global?
Then I still have the option of using your "default" logger factory, or I can create my own if that's what I need.

Quote:I don't see why you couldn't claim that you will only ever need a single Logger Factory.

Presumably, the reason you have a factory, rather than just calling the log constructor, is that it contains some kind of state, some configuration settings.
And if it does that, then it is conceivable that I might want to create a log based on different configuration settings. Which would imply a second factory.

And singletons are not about "You'll only ever need one". They mean "It is an error to create more than one".
Is there any reason why it would cause problems to have multiple logs or multiple logger factories? If so, you may need to enforce that only one instance can exist. If not, there's no call for a singleton.
A logging utility may not always be a valid candidate for "singletonizing". My project has a client and server side that run within the same application. I would want separate logging for both the server and client side to be able to troubleshoot better.

Quote:Original post by Antheus
Quote:This way I maintain the ease of access to a logger from anywhere in my program


For example, passing either the logging "factory" to objects, or passing "application" or "state" to your objects?


I think everybody agrees with your comment that one SHOULD pass around pointers, but what I always struggle with is where to draw the line. When does passing around pointers become more trouble than it's worth? And when you put everything in an "Application" object, do you not expose too much of your system to classes that should not be able to touch parts of the system?


Personally it bothers me when I have to pass around a lot of utility objects to classes. This means that when I write unit tests for example I have to initialize or stub a lot of classes. There is logging, memory management, but I also have performance utility classes for data throughput and timing.

For now I chose for some of these to be completely static classes, just for ease of use. Especially the performance utilities you don't want to pass around. These are only used in very few classes.
I haven't used these performance utilities in unit tests yet, so it may turn out that having multiple instance of them is required.



One way to ease the pain of passing around a tonne of pointers would indeed be to use the "Application" object that contains all these utilities, but somehow it doesn't "feel" right. It may become very tempting to put classes in that "Application" that are only used by a few classes. For example, you can put the "World" object in the "Application" because all the scene objects use it.



I usually go with the flow of the project and code, do what is easiest at the moment. But what I'm wondering is what is your approach Antheus? Are you very strict in this? Do you pass around all those pointers through all layers of your application? Do you pass around an Application object? Or are you more pragmatic in this?
STOP THE PLANET!! I WANT TO GET OFF!!
Quote:Original post by Antheus
The question to answer is: Will the universe implode if you have two factory instances. If answer is yes, then it's justified use of a global. If not, you do not need a singleton.
Quote:
Logging system doesn't need a global.


The question to answer is: Will the universe implode if you have a single global in your app? What's the taboo about this?!

In my opinion, Logger is a good use of singleton. Logger is so widely used that passing the object around clutters the code to a degree beyond my imagination.

Call me the worst programmer on the planet, but I even justify the use of goto under rare circumstances, let alone singletons. [smile]
Quote:Original post by Ashkan
In my opinion, Logger is a good use of singleton. Logger is so widely used that passing the object around clutters the code to a degree beyond my imagination.

You've just made the most common singleton mistake: Assuming that a singleton is for the purpose of providing global access to an object. Globals and singletons are not created alike: one is for allowing global access, and the other is for restricting creation.
Quote:Original post by Sneftel
Quote:Original post by Ashkan
In my opinion, Logger is a good use of singleton. Logger is so widely used that passing the object around clutters the code to a degree beyond my imagination.

You've just made the most common singleton mistake: Assuming that a singleton is for the purpose of providing global access to an object. Globals and singletons are not created alike: one is for allowing global access, and the other is for restricting creation.


Brilliant! You got me by the balls. You're absolutely correct.

/EDIT: Too bad you changed your post before I reply. Did globals hurt me as a child? [smile]

This topic is closed to new replies.

Advertisement