0

This question already has an answer here:

Unlike many libraries SDL and OpenGL are designed to provide global resources; you can access them at any time from any class. There are justifications for this: They are written in C, meant to be readily cross-platform and designed to be fast.

They are not object oriented though, and global state is supposed to be a Very Bad Idea normally. It adds dramatically to the number of inputs to every function in your code.

Let's say you're working in C++ or some other object oriented language with one of these libraries. You know what functionality you need. At that point isn't it a good idea to encapsulate what you need into X and Y classes and say "every time you need to work with this library, use X and Y"?

For example, with SDL, it seems like a good idea to have a class which calls SDL_Init in the constructor and cleans everything up in the destructor. This manager class only allows you to create windows and renderers while SDL is active.

A good cause for a singleton perhaps? I'd rather not. The Google Testing Blog gives a good example of a case against singletons: http://googletesting.blogspot.co.uk/2008/08/root-cause-of-singletons.html

So really my questions are 1) if encapsulating part of a library is a good idea or pure madness and 2) how to approach managing global resources without singletons.

EDIT: Paul K's answer here gives a really good, concise introduction to dependency injection, which removes the need for singletons. However it's still unclear how to deal with the state which is unfortunately made global by a library such as OpenGL or SDL. The extensive global state provided by these libraries can't easily be passed by reference unless it is collected together and managed by a wrapper class. Even once this is done the client can still use the library directly.

marked as duplicate by gnat, Greg Burghardt, David Arno, TMN, Kilian Foth Apr 7 '16 at 13:33

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Conveniently, there is a C++ library that does a lot of the same things as SDL (including dealing with all these global resources) and imo is very well-designed, namely SFML. So looking at their APIs may give you some insight. I might write a proper answer later using some of their classes as examples if I find the time. – Ixrec Apr 5 '16 at 12:39
  • 1
    @gnat your second link contains a beautiful answer to part of my question, it also explains what dependency injection is in a relatable way (reference passing). I would choose that as a good answer, along with wrapping up the required features from a globally accessible library into classes. – matt_rule Apr 5 '16 at 12:50
  • Thanks @ixrec I will take a good look at SFML. Qt might also be an option. – matt_rule Apr 5 '16 at 12:51
  • 1
    For those who wish to answer this question or are trying to decide whether to close it as a duplicate of what gnat linked, @matt_rule what parts of your question are not answered by gnat's links? – Ixrec Apr 5 '16 at 12:53
2

A good alternative to singletons is using Dependency Injection as described in Paul K's answer here. Create and initialise a class at the highest necessary scope, then pass it down by reference.

When dealing with existing globally accessible state/resources, my solution would be to: 1) Determine what parts of the global state are needed. 2) Encapsulate these into a set of wrapper classes. 3) Dependency Inject these classes.

AFAIK there isn't a way to prevent the client from accessing the resource directly.

  • Why would it be impossible to prevent clients from accessing those resources? Just don't expose them via the public interface of your (wrapping) library. – Pieter Witvoet Apr 5 '16 at 13:38
  • In C++ at least, once you type #include <GL.h> in a file all of OpenGL's state is accessible to any classes which use that file. – matt_rule Apr 5 '16 at 13:42
  • Using your rendering library (#include <rendering.h>) shouldn't give access to that global state. If someone wants to mess things up then they'll always find a way (#include <GL.h>), but that's their problem, right? I guess I should've qualified my comment with 'with normal use'. – Pieter Witvoet Apr 5 '16 at 13:50
  • My mistake, I wasn't thinking in terms of dependency inversion I guess? But that solves the problem. And it also means what gnat posted earlier was a complete answer. Can I accept that now? – matt_rule Apr 5 '16 at 14:00
0

One of the things that's bothered me for a long time about the debate around the Singleton pattern is that almost no one looks at the original design pattern from GoF. They are talking about a shallow simplification of the pattern. I think this is pretty disrespectful to the authors of a book that's probably one of the most influential texts in software engineering ever. I highly recommend it as a primer for OO design.

What most people miss if that in the original text, if you look at the example code the Singleton definition is abstract. The idea is that you can create different versions of the actual Singleton object and use configuration to load the one you want at runtime. Does that sound familiar? It should because it's essentially a proto-DI in a very narrow scope.

The article you reference about attacks Singletons on the lines that they are global state. Shared state is problematic and globally available shared state even more so. You should absolutely avoid shared state as much as possible. I'm not convinced that it's always possible to avoid and if you need to share state, the Singleton pattern is strategy for encapsulating and managing that sharing of state. If you are using DI to inject shared state all over your program, you still have shared state. DI isn't a solution for avoiding shared state. It's a solution for avoiding coupling and if you build the Singleton as described in the GoF patterns, it also avoids coupling.

  • Are you claiming that the GoF pattern allows for multiple instances of a singleton to exist at once? If so, then that's not a singleton. If the GoF version only allows for one instance during a run of the code, then it still suffers the same global state/testability shortcomings of all other singletons and thus is still an anti-pattern. – David Arno Apr 5 '16 at 16:40
  • I said it's abstract. Most arguments again the Singleton revolve around coupling. The perceived issues with testability are largely related to coupling. What I didn't write above but can add now is that it shouldn't matter to you as a client of the Singleton whether it returns a 'single' global object. If you are building code that depend on that, it's definitely a problem. Unfortunately, I don't have the book handy. The second part of my point here is that DI doesn't prevent you from injecting global state but it seems to be implied that it does by the answers and comments here. – JimmyJames Apr 5 '16 at 17:02
  • No, the issues with testing are related to global state. Running multiple unit tests, especially in parallel, against a stateful global singleton ranges from difficult to impossible. DI avoids the need for global state. Sure you could still have it, but then that would be doing DI wrongly. One can inject a singleton via DI, but its a singleton through convention, not because it follows the singleton pattern of enforcing only one - globally accessible - instance. – David Arno Apr 5 '16 at 21:27
  • I don't disagree and DI has distinct advantages over the factory pattern (which is the only thing that consumers should see the Singleton interface as.) The point is that the need for global state is not created by the use of Singleton. If you need global state, it's because you need global state. Singleton is an approach to managing global state. Essentially what you are saying is that if you use a Singleton in your runtime program, that you aren't allowed to replace it with something that avoids global state in your testing strategy. Where does this rule originate? – JimmyJames Apr 6 '16 at 0:35

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