The Singleton Class Cluster
IntroductionThe Singleton is one of the most widely used design patterns, although typical implementations tend to have a few shortcomings. In this article I will first suggest an optimal implementation of the standard Singleton design pattern, and then proceed to the reason for this article's title, the Singleton Class Cluster, which is an attempt to combine the 'Singleton' and 'Class Cluster' design patterns to allow polymorphism in Singletons. If you don't know (or don't understand) either the Singleton or Class Cluster design patterns, don't worry, I will explain to the best of my abilities. The sample implementation is written in C++, but all of the theories should apply equally well to any other Object-Oriented language. The SingletonThe intent of the singleton is to ensure there is only one instance of the singleton at any one time, and to provide a unified access point to that instance. AT its simplest, a singleton can resemble the class below class Singleton { public: Singleton &getInstance() { static Singleton my_singleton; return my_singleton; } private: Singleton(); Singleton(const Singleton &s); Singleton &operator = (const Singleton &s); }; This satisfies the basic requirement, that there be only one instance, and that the instance be accessible through the getInstance() function. I have also placed the constructor, copy constructor, and assignment operator in the private scope so that a user can not inadvertently create additional instances. However, there still are a few problems, chiefly that we have no control over when the singleton is created or destroyed, it is created the first time the getInstance() function is called, and it is not destroyed until program termination. Improved SingletonThe improved version of the singleton allows us to control creation and destruction. I have also made it a templated super-class, so that it need not be re-implemented for each singleton. template <typename T> class Singleton { protected: Singleton() {} virtual ~Singleton() {} static T *my_singleton; public: static void create() {my_singleton = new T();} static void destroy() {delete my_singleton; my_singleton = NULL;} static T *getPtr() {return my_singleton;} static T &getRef() {return *my_singleton;} private: Singleton(const Singleton &s); Singleton &operator = (const Singleton &s); }; It is used by as a superclass: class Derived : public Singleton<Derived> { // rest of class }; And the following definition is placed into each file that defines a singleton, such as 'Derived.cpp': template <> Derived *Singleton<Derived>::my_singleton = 0; This satisfies all of my needs, and due to C++'s multiple-inheritance, any singleton class can also inherit from other classes. This implementation also allows singletons to be re-created, as it may be necessary to destroy and then recreate a singleton, for instance a Renderer singleton when the display resolution changes. Some sort of error checking (probably compile time assert statements) should be added, to ensure that the users does not try to call create() more than once before they call destroy(), but I leave that up to the reader. The Class ClusterThe essence of the Class Cluster design pattern is to have a public abstract superclass, that delegates responsibility to one of a group of private subclasses. The other classes should never see those subclasses, allowing them to pretend that they are actually the superclass. For instance, Take my engine's renderer classes: class Renderer { // declare the abstract methods }; class OpenGLRenderer : public Renderer { }; class DirectXRenderer : public Renderer { }; At runtime, the engine determines which of DirectXRenderer or OpenGLRenderer to create (perhaps based on a configuration file). There is an entry point in the engine, getRenderer() that returns the renderer, and other classes never have to know wether they are rendering to DirectX or to OpenGL. Finally, The Singleton Class ClusterReturning to the Renderer example in the last section; in my engine there can only ever be one instance of Renderer at a time, perfect for a singleton, right? Unfortunately, this doesn't work with our current Singleton design pattern; we could declare Renderer to be a singleton, but Renderer is an abstract class, so it couldn't actually ever be created, in addition, if we declared one of the subclasses to be the singleton, we would have lost both the unified entry point, and the ability to decide which subclass to use at runtime. Not good. All right, so here comes the magic. Remember the Singleton we created in section 2? It had a create() static function: static void create() {my_singleton = new T();} Now, is there any reason why we can't use a class derived from T instead of T? Such as: static void create() {my_singleton = new ClassDerivedFromT();} but obviously there is no point putting this change in the templated superclass, as it would still be determined at compile time. But what if we overloaded create() in the Renderer class? Try this: class Renderer { static void create(); }; class OpenGLRenderer { }; class DirectXRenderer { }; class SoftwareRenderer { }; void Renderer::create() { if (ConfigurationManager->GraphicsAPI == "OpenGL") my_singleton = new OpenGLRenderer(); else if (ConfigurationManager->GraphicsAPI == "DirectX") my_singleton = new DirectXRenderer(); else // If there is no API available my_singleton = new SoftwareRenderer(); } Neat, huh? And there is, of course, no reason why you couldn't load the renderers dynamically form DLLs (shared objects, frameworks, etc.), and then present a dialog to the user to ask which one they would prefer. What is more, if for instance the user is running the DirectX renderer, and they suddenly decide they prefer OpenGL, all the engine has to do is destroy the singleton, and then create it again, with the other renderer. ConclusionSo now we have a design pattern that satisfies all our requirements, a cross between a singleton and a class cluster, that provides flexibility, code-reuse, and a certain coolness factor. Enjoy. Tristam MacDonald
Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|