NYCPHP Meetup

NYPHP.org

[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