Is DI the only solution to Singleton and/or static objects?
I have been told that Singletons are hard to test.
- http://misko.hevery.c开发者_StackOverflowom/2008/08/17/singletons-are-pathological-liars/
- http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
I have been told that Static methods/objects are no good either.
- http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
So basically the only solution appears to be dependency injection.
But ... I really can't get used to DI, take this example:
In my framework I have a class that manages SQL. This class (and many other of my framework) uses a singleton Logger to logs message (and many other helpers).
With DI my code would turn to:
global $logger; //> consider i have been instanciated it at the start of my fw
$query = new PreparedQuery($logger);
$query->prepare() etc.
Now that doesn't seem too bad. But consider a page that needs many queries I believe it's pretty redundant to have to write everytime $logger
in the constructor, especially if you consider if PreparedQuery needed many other dependencies in the constructor.
The only solution to avoid singleton that I have found is to use a method (or just a simple function) in the main application that stores every references to this helper objects (Service Locator/Container) but this doens't solve the problem of hiding the dependencies
So in your experience other than DI what is a good pattern to use?
Solution:
For everyone interesetd the creator of PHPunit explains how to solve the Singleton problem (and how to solve static methods testing problem with PHP 5.3)
- (singleton) http://sebastian-bergmann.de/archives/882-Testing-Code-That-Uses-Singletons.html
- (static) http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html
Pretty interesting read if you ask me.
Please, don't use global
.
You need to pass $logger in constructors or pass Service Container instead (also known as Objects manager, Service Locator, Resources Manager).
Variant from Symfony framework http://symfony.com/doc/current/book/service_container.html
You can create your own Objects Manager, and his methods should not be static.
Well, in this case, I would build a builder (or factory) instead. So your factory would inject the dependency for you. That way you can also avoid your globals:
class PreparedQueryFactory {
protected $logger = null;
public function __construct($loggger) {
$this->logger = $logger;
}
public function create() {
return new PreparedQuery($this->logger);
}
}
That way, you do once:
$factory = new PreparedQueryFactory($logger);
Then any time you need a new query, just call:
$query = $factory->create();
Now, this is a very simple example. But you could add all sorts of complex logic if you need. But the point is, by avoiding new
in your code, you also avoid managing the dependencies. So instead, you can pass the factory(ies) around as needed.
The benefit, is that all of this is 100% testable, since everything is injected everywhere (as opposed to using globals).
You can also use a registry (otherwise known as Service Container, or DI Container), but be sure you're injecting the registry in.
Logging is usually the example where static singletons are OK. You don't need to mock your logging anyway, do you?
The above answers give you some ideas. I'll present another one: implement a plugin architecture. The logger the becomes a plugin which you can enable/disable/change whenever you want.
An simplified example:
class Logger implements Observer {
public function notify($tellMeWhatHappened) {
// oh really? let me do xyz
}
}
class Query implements Observable {
private $observers = array();
public function addObserver(Observer $observer) {
$this->observers[] = $observer;
}
public function foo() {
// great code
foreach ($this->observers as $observer) { $observer->notify('did not work'); }
}
}
This removes the Logger away from the constructor. That is what I prefer if it is not essential for the functioning of the object.
In my understanding of Misko Hevery's talks on DI and the new
operator, the problem is you haven't gone far enough in implementing DI.
What Hevery always says is you should not mix business logic with object construction. However, in the two lines of your example, the first ($query = new PreparedQuery($logger);
) constructs an object, and then second ($query->prepare(/* ... */);
) is business logic.
Clearly, the objective of that code is to prepare a query, and instead of worrying about how to build a PreparedQuery
, it should just ask for one in the class constructor. Or if it needs to be able to produce lots of PreparedQueries, it should ask for a prototype (that it will clone whenever it needs a new one) or a factory object. The point is, the fact that the PreparedQuery has a logger is of no concern, and should be taken care of somewhere else.
The principle of "asking for what you need" in the constructor is easy to understand in principle, although I am still trying to work out for myself what it means in practise in various situations, and how to implement it all the way to the top (the "main method" or equivalent). However, I think this principle speaks to the general problem you're having. That new
operator should not be where it is in the first place.
精彩评论