[nycphp-talk] Are Singletons Really Evil?
Gary Mort
garyamort at gmail.com
Mon Mar 19 11:38:05 EDT 2012
Yay... Spring is here, blood starts flowing, mind starts stuttering.
So I figured I'd ask others opinions on if singleton's really are
"evil". It seems to me that "singletons are evil" is part of the a
classic programmer pattern: a lot of programmers get burned by
something, they declare it evil and propose something 'new'[I beleive
the evolution is towards 'dependency injection'], and a new paradigm
takes control for a while..
The issue I see though is that in the end, all that is really being done
is a new methodology is being created to solve two age old issues:
globals and configuration information. At the end of the day, it's not
the method that is in itself "evil", but rather the lack of unified
programming discipline and initial failures discovered.
Back when I started programming[30 years ago] and as I progressed,
'object oriented' code wasn't a system and everyone used globals.
Venerable good coding practices were to document your functions - to
specify what these functions required, what global variables they might
need, what variables they set, etc.
Yet time after time, coders do not follow this practice. They do not
document these things. And so we get "globals are evil"...instead of
"programmers are lazy"... This leads to new methodology's..and these
new methods "work" for a while, not because they are great, but because
they are "new" so everyone follows the same rules. And then over time,
"programmers are lazy" and these new systems fall down. At which point
a "new, better" system is made. And of course, over time problems are
encountered, and another "new system" is created.
In the end, we have a bunch of competing methodologies and the strongest
argument for any one of them is that the others are "evil"...or more
precisely, the programmer making the argument has gotten burned and
doesn't want to be burned again.
So that bring us to "why are singleton's evil?"...and the only reason I
see come up over and over is "dependency injection".. but when I view
examples of dependency injection "failure" I find that the failure is a
failure of implementation, not a failure in the concept of Singleton's.
The classic example of singleton failure goes something like this:
Given a singleton object[say a database object: $db =
MyDBClass::getInstance();] you are 'stuck' with a single database
connection. If you want to have multiple database connections[say for
an order database and a data warehouse - you can't simply call $db =
MyDBClass:getInstance(); and $dw = MyDBCLass::getInstance(); and
magically get 2 different database connections. Even worse is that you
can't 'unit test' classes which use the database object because you
can't have a test/mock database object.
Now, this may well have been true for PHP when people first started
using singleton's. But today there are at least 3 ways, without using
dependency injection, that I can think of off the top of my head to
solve this problem.
For unit testing:
----
class MockDB extends DB {
// implement DB functions as mock functions
}
// create the testing database
$testDB = new MockDB();
// use the reflection class to change the instance
$changeDB = new ReflectionClass('DB');
$changeDB->setStatisPropertyValue('instance',$changeDB);
----
To override the instance in live code, one could also use the reflection
class - or one can create a subclass just to change the instance:
Class datawarehouseDB extends DB {
public static getInstance() {
if (get_class(self::$instance) != *|__CLASS__) {
$datawarehouseDB = new datawarehouseDB();
self::$instance = $datawarehouseDB;
|* }
return parent::getInstance();
}
}
The above code will make sure that the current default db instance is a
datawarehouseDB object.
You can also change your getInstance code to allow passing in an override:
public static getInstance($instance = null, $force = false) {
// only allow overriding instance if the instance class is ourself or
one of children
if (is_a($instance, __CLASS__*|) {
// as added protection, use a force flag so that if the instance
// has already been set, the new code must explicitly FORCE override
// ie: make the coder think about what they are doing
if ((self::$instance === null) || ($force === true)) {
self:$instance = $instance;
|* }
}
// now back to our traditional getInstance code
if (self::$instance == null) {
self::$instance = new DB();
}
return self::$instance;
}
I've also seen from time to time where instance can also be used to
refer to an array of instances. IE
public static getInstance($keyword = 'default') {
// now back to our traditional getInstance code
if (!(isset(self::$instances[$keyword]))) {
self::$instances[$keyword] = new DB();
}
return self::$instances[$keyword];
}
}
Now, my point with the above is not to say this is "the way" one should
do things - it is just to point out that with the state of PHP 5.3
today, many of the "singleton's are evil" arguments no longer hold
water. As such, when you inherit a codebase using singleton's which is
having all these traditional problems[unit testing, the need for
multiple objects at times, etc] - I don't see any reason to rush to
replace all the existing calls to getInstance with some new
methodology. Instead you can use the ReflectionClass or any one of a
number of different changes in order to setup unit tests without making
lengthy, invasive code changes.
At the end of the day, I can't think of any decently sized application
which will not at some point need to store/retrieve configuration data
from somewhere and have objects which inter-operate with each other.
The main thing to keep in mind is not the '_____ method is evil' - but
rather to be consistent in using some methodology.
So my question is....what is it I'm missing? What massively obvious
reason is there that makes Singleton's "evil"? Or is it just part of
the 30+ year pattern in programming I've noticed that "newer is better"
where there is reason for change "in general" - but that instead it
depends on the situation/case what the best solution is?
-Gary
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.nyphp.org/pipermail/talk/attachments/20120319/3be49ddf/attachment.html>
More information about the talk
mailing list