Singleton is one of the most popular design patterns. Its purpose is to ensure the existence of only one instance of a certain class while also providing global access to it. Here is a brief example for completeness:
class Database
{
private static $instance;
private function __construct()
{}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self;
}
return self::$instance;
}
...
}
// singleton is globally accessible
$result = Database::getInstance()->query('...');
Typical features include:
- A private constructor, preventing the creation of an instance outside the class
- A static property
$instance
where the unique instance is stored - A static method
getInstance()
, which provides access to the instance and creates it on the first call (lazy loading)
Simple and easy to understand code that solves two problems of object-oriented programming. Yet, in dibi or Nette Framework, you won’t find any singletons. Why?
Apparent Uniqueness
Let's look closely at the code – does it really ensure only one instance exists? I’m afraid not:
$dolly = clone Database::getInstance();
// or
$dolly = unserialize(serialize(Database::getInstance()));
// or
class Dolly extends Database {}
$dolly = Dolly::getInstance();
There is a defense against this:
final public static function getInstance()
{
// final getInstance
}
final public function __clone()
{
throw new Exception('Clone is not allowed');
}
final public function __wakeup()
{
throw new Exception('Unserialization is not allowed');
}
The simplicity of implementing a singleton is gone. Worse – with every additional singleton, we repeat the same piece of code. Moreover, the class suddenly fulfills two completely different tasks: besides its original purpose, it takes care of being quite single. Both are warning signals that something is not right and the code deserves refactoring. Bear with me, I’ll get back to this soon.
Global = Ugly?
Singletons provide a global access point to objects. There is no need to constantly pass the reference around. However, critics argue that such a technique is no different from using global variables, and those are pure evil.
(If a method works with an object that was explicitly passed to it, either as a parameter or as an object variable, I call it “wired connection”. If it works with an object obtained through a global point (e.g., through a singleton), I call it “wireless connection”. Quite a nice analogy, right?)
Critics are wrong in one respect – there is nothing inherently bad about
“global”. It’s important to realize that the name of each class and
method is nothing more than a global identifier. There is no fundamental
difference between the trouble-free construction $obj = new MyClass
and the criticized $obj = MyClass::getInstance()
. This is even less
significant in dynamic languages like PHP, where you can “write in PHP 5.3”
$obj = $class::getInstance()
.
However, what can cause headaches are:
- Hidden dependencies on global variables
- Unexpected use of “wireless connections”, which are not apparent from the API of classes (see Singletons are Pathological Liars)
The first issue can be eliminated if singletons do not act like global
variables, but rather as global functions or services. Consider google.com –
a nice example of a singleton as a global service. There is one instance (a
physical server farm somewhere in the USA) globally accessible through the
identifier www.google.com
. (Even clone www.google.com
does not work, as Microsoft discovered, they have it figured out.) Importantly,
this service does not have hidden dependencies typical for global variables –
it returns responses without unexpected connections to what someone else
searched for moments ago. On the other hand, the seemingly inconspicuous
function strtok suffers from a serious
dependency on a global variable, and its use can lead to very hard-to-detect
errors. In other words – the problem is not “globality”, but design.
The second point is purely a matter of code design. It is not wrong to use a “wireless connection” and access a global service, the mistake is doing it unexpectedly. A programmer should know exactly which object uses which class. A relatively clean solution is to have a variable in the object referring to the service object, which initializes to the global service unless the programmer decides otherwise (the convention over configuration technique).
Uniqueness May Be Harmful
Singletons come with a problem that we encounter no later than when testing
code. And that is the need to substitute a different, test object. Let's return
to Google as an exemplary singleton. We want to test an application that uses
it, but after a few hundred tests, Google starts protesting We're
sorry… and where are we? We are somewhere. The solution is to substitute
a fictitious (mock) service under the identifier www.google.com
. We
need to modify the hosts
file – but (back from the analogy to
the world of OOP) how to achieve this with singletons?
One option is to implement a static method
setInstance($mockObj)
. But oops! What exactly do you want to pass
to that method when no other instance, other than that one and only,
exists?
Any attempt to answer this question inevitably leads to the breakdown of everything that makes a singleton a singleton.
If we remove the restrictions on the existence of only one instance, the
singleton stops being single and we are only addressing the need for a global
repository. Then the question arises, why repeat the same method
getInstance()
in the code and not move it to an extra class, into
some global registry?
Or we maintain the restrictions, only replacing the class identifier with an
interface (Database
→ IDatabase
), which raises the
problem of the impossibility to implement IDatabase::getInstance()
and the solution again is a global registry.
A few paragraphs above, I promised to return to the issue of repetitive code in all singletons and possible refactoring. As you can see, the problem has resolved itself. The singleton has died.