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
_______________________________________________
New York PHP User Group Community Talk Mailing List
http://lists.nyphp.org/mailman/listinfo/talk

http://www.nyphp.org/show-participation

Reply via email to