Tuesday, December 23. 2008Why I Hate SingletonsTrackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
Great advice... I'm totally guilty of using the singleton as a glorified global! Thanks for highlighting the downside of doing so, this will definitely have an effect on my coding practices.
I'm really interested in seeing some bad usage examples. In the project I'm working on we have a lot of singletons. But they also represent physical hardware, which is globally single. I keep reading posts like this, but I'd like to see a legitimate bad code example.
Scenario: You are writing a unit test, and your code calls a singleton to get an instance of the database.
For the unit test, you only need a really small, in memory database, like sqlite, with a bit of prefilled data that you can set up and tear down. So, your code looks like: function foo() { db = DBConnection::instantiate(); results = db.query(...); return results; } and your test looks like assert array(1,2,3) == foo(); Unfortunately, there's no way to change the behavior of your singleton - lets call it DBConnection; unless you start providing methods like 'setForTest()', and then your test becomes DBConnection::setForTest(...) assert array(1,2,3) == foo(); ... and this works fine for a while; until you are writing this for the 15th time and forget to reset the DBConnection back to the original. // First test DBConnection::setForTest(...) assert array(1,2,3) == foo(); // Some other test assert array(1,2,3,4) == bar(); // that's odd! This test works just *fine* when I run it alone, but fails in the suite! Spooky action at a distance! Sure, you can say "Well, don't forget then!", but I would argue that is messy, error prone, and likely to bite you on projects with more than one person. More: http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/ http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html
Designing an application to be able to test it is evil to me
Also Registry seems cool at the first glance but in fact it lead to other problems like variable name collision and to give you my feeling about it : Registry is a politically correct Global-Array (so not so cool at the end).
Designing an application around testing is evil?
Having a whole lot of small, well understood components which can be wired together to produce the outcomes I want seems like a pretty good way to go about it for me, especially when I have to fix the defective bits! If I can replace SlowSuperProductionDatabaseDriver with CannedResultsDriver by simply telling just the component I want to test to swap; that's going to save me huge amounts of time - both in execution of the test and in programming time. Think about it. It's way easier to create an array and tell CannedResultsDriver what to say than it is to * connect * interact with a network * ask the slow production database to insert records for me * optionally: make sure there are no left overs or duplicates from the last time I ran this test, and it failed half way, which would cause this test to fail sometimes * Execute the code, which repeats the interaction with network and ask database for results steps... * Hey, don't forget to go back in and clean up everything If I've got a problem somewhere, and I don't know where; isn't it much better to be able to say "If I give it a 1, it throws an exception, ooh bad code" rather than "maybe its the network cable / dodgy data / mystery panthers / April 1st"...
Don't misunderstand me: Testing is good, component architectures are good and patterns is awfully good also
But if you don't use a pattern because the pattern make the code hard to test even if it is the rigth pattern for the job it's evil.
The design of your business logic / code should be to take a very strict and limited input, throw a tantrum if it doesn't get it, and produce a predictable result.
If you choose to apply a design pattern that makes it harder for your code to run when giving it the input it was designed for, and that produces random or uncertain results... well, quite frankly I've frightened of that design pattern. */foaming at the mouth annoying rant mode* Sorry about that... full moon... I kind of turn...
One of the benefits of unit testing is that it exposes areas of your program that have a high level of coupling. The more coupling you have, the less flexible your code is. So in some senses, designing your code so that it is easy to test can lead to more decoupled code which leads to more flexibility and opportunities for easy re-use. So I would disagree that it is evil to throw out a pattern just because it makes testing easier. Testing is an important aspect of development and one that if it is hard, it is generally ignored (not saying it is right, it is just how it is.)
The opposing argument is that the more decoupled and 'flexible' your code is, generally speaking it is harder to understand because there are more levels of abstraction. I would argue however in the case of singletons you are losing flexibility AND making your code any harder to understand. You cannot look at a class and all of its methods and immediately infer what kind of objects you need to set up before hand.
I understand your arguments but the problem is not just black or white and Singletons are usefull in some cases and I just can't ignore a pattern because it is over or misused.
For exemple let's take a log file. It is needed in the whole program. But it is not a dependency of any componant because it is not part of the execution flow. For me a Singleton is good for that problem and for other of that kind. Is this evil ? I don't think so
Like I said, the single instance aspect of a singleton can be useful. It is just the global access aspect that annoys me.
Using a logging singleton as an example. If you are using a pure singleton and littering your code with Log::getInstance() it does make logging from anywhere relatively simple. However what if you decide to change your logging mechanism, or you want to make your logging mechanism more user configurable. Such as allow logging to either file, syslog, database, or even more than one location etc. You cannot simply extend your log class. Those calls to Log::getInstance() will always be bound to Log. Your only recourse at that point is to rewrite Log to contain the functionality you need, or rewrite Log to no longer be a true singleton. I am hoping everyone understands that this is my real point. Singletons aren't pure evil, they are just sort of evil. If you need to ensure you only have a single instance of an object then they work well. Using them to pull in functionality from what is essentially a global scope, will always limit you.
Well stated. The two links above are also excellent and did in fact provide some inspiration a few months ago for this article. (I'm slow on the draw)
This is great... More and more people are recognizing singletons for what they are: globals. Here's a great talk I that finally convinced me to do away with ALL global state. It is actually possible, and totally worth the effort:
http://www.youtube.com/watch?v=-FRm3VPhseI
At the very least - if you have to have a singleton (they're the right way to handle database connections, file handles, etc), put it in a registry pattern. Singletons are HARD to test, but a registry pattern lets you pick and place 'instances' out of the registry. That way, you only have to have 1 'true' singleton (that for the registry).
Eww @ all pervasive registry.
Why not // Nasty configuration reading $db = new DatabaseConnection($settings_from_somewhere); $foo = new Application(); $foo->setDatabase($db); ... and pass $db down the application further
Even registries can have the same problem of masking dependencies. The creational aspect of singletons while somewhat rigid is not the worst aspect of them. In my opinion you should be passing your dependencies to your methods and objects, not pulling them out of thin air.
Yeah, but it kinda sucks programming wise. I don't know, there is something about agile/iterative programming that makes Singletons very attractive, because you may not know what all database connections, etc, you might need at what level of code - so you end up with some general way to get at them. I'm in a environment that is filled with a glut of singletons, and its pretty deep in the code, so to work around it, in classes that depend on one, we wrap that in a simple function that is easy to mock. Its not super elegant but it works for now.
I'm working on a project right now that allows no need to access the global name space in frameworks like ZFW. I believe that getting rid of access to the global name space is the only way to get real code isolation.
Thatnks for the post Mike
"The class is expensive to create" should be "The object is expensive to create".
So we use this pattern in java. Hopefully my PHP works. To make Singletons testable we combine it with the factory pattern:
class SingletonFactory { private $classToCreate; private static $instance; __construct($classToCreate) { $this->classToCreate = $classToCreate; } public function getInstance(){ if(!isset(self::$instance)){ self::$instance=new $classToCreate; } } } class Inventory { private static $factory; public static getInstance() { return $self::factory->getInstance(); } public static setFactory($factory) { self::$factory = $factory; } } Inventory::setFactory(new SingletonFactory("Inventory")); Then when you want to write a test you just add the following line to your test: Inventory::setFactory(new SingletonFactory("MockInventory")); and you'll be returned your mock object rather than the standard one.
The problem is not a singleton, but the hard link to that singleton. Once this knot is untied, kaboom, you can substitute a mock, and do what you want. And test the singleton separately.
To make it clearer. Any class is a singleton. How do you test a constructor or a factory?
The problem of singleton in PHP is late static binding isn't present yet
The code snippets are missing!!!
Is it 'cos I'm using Firefox 3?
I apologize, this is due to a 'feature' of my blog. I'll get it fixed shortly.
Personally I use singletons as glorified globals on purpose when it makes sense that there should be one object of something that does stuff. Like for example having only one very global template engine object for the whole framework. Why would I need two if I'm only outputing one thing at the end and everything needs to tell that one thing what to output? Makes sense to have a singleton.
Another occurence when I use singletons as glorified globals is when working with javascript and concurrency. Javascript has a nasty tendency to jump into a callback method that suddenly doesn't have a reference to its parent object anymore. Singletons can solve this issue very effectively and in a very bloody simple way too.
Remember single object does not mean that object can only be accessed via a global call. You can still pass a singleton instance down to your lower level code.
I will not argue the simplicity of singletons, they are simple. However it quite often applies that simplicity also brings rigidity with it and in my personal opinion adding a method call or a parameter to your classes and methods to accept an instance of our singleton is normally an acceptable 'complexity' for the flexibility you gain with less restrictive dependencies.
I see your point here, a singleton is indeed often abused as a glorified global. Nonetheless, there are some good reasons to use singletons now and then.
Imagine a standard MVC-structured PHP application with a signed in environment. Of course you are going to use the $_SESSION variable in PHP to store the session information, but what if that is not enough? The $_SESSION variable is but a simple array of data, tying together keys and values. With a nice little singleton class, encapsulating (not the OO-principle) the $_SESSION variable, you gain a lot of flexibility and scalability for your application. So nothing wrong with using a singleton-class here ... Just my two cents of course As a final warning, I read some comments where people use singleton classes to create database-objects or connections. I strongly discourage this practice. Your next assignment will probably be to connect and work with multiple databases and multiple connections.
I think what you are saying actually ties in quite nicely to only utilizing singletons (the getInstance() methods, not necessarly a singleton instance returned from these methods) in your higher level code. For instance I don't see that much of a problem calling something like Session::getInstance() and passing the results into a front controller to be passed to your other controllers. Then individual properties / method results can be passed to models / views that may need it. I see this as a much cleaner, acceptable design as opposed to Session::getInstance() calls littered throughout all of your controllers and ESPECIALLY your models and views.
Also, I fully support your comments regarding database objects. This happened at my office. To solve the issue we essentially converted the singleton into a registry. Still not my favorite way to do it, but if you are stuck with a singleton to begin with it is a doable way to solve the problem quickly.
The loan seem to be important for people, which want to organize their company. As a fact, it's very comfortable to receive a secured loan.
I guess that you have to be awarded by the buy dissertation service for your interesting idea about this good post. Not lots of people would be able to make the same high quality dissertation research proposal.
This is hard a bit to learn a correct information close to this good topicand paper writing service would suggest to buy custom essay papers to get proper facts and thst’s achievable to order custom papers per really small prices!
сценарии, естафеты, игры
|
Paying for the Site
QuicksearchCategoriesArchivesDaily ReadsThe PHP WTF Sebastian Bergmann Wez Furlong Tom Sommer Tobias Schlitt Chris Shiflett Ben Ramsey adam trachtenberg Sklar george schlossnagle Dynamically Typed (Harry Fuecks) John Coggeshall John Lim Marco Tabini PHP Application Tools Planet PHP PHPCommunity.org |

