Most GUI frameworks, and, SAX (Streaming XML) XML parsers use callbacks.
The basic pattern is that you pass an instance of a handler class (or sometimes just a method reference) when you initialize another class. When a specific event occurs the "handler" class is called to deal with the event.
This is typically how you get your code to run when a button on a GUI is pressed.
As there is no right or wrong in this question, I could give an example about how I like to use Dependency Injection in PHP.
I'm using the Dependency Injection Component from Symfony with the LosoDiAnnotationsBundle and extracted everything from the Bundle to use it as standalone as the Dependency Injection Component. I'm not using the rest of the Symfony Framework.
There are two functions in my system which use the Dependency Injection Container. The main function and the dispatcher. The main function (application entry point) retrieves the configured Dispatcher like this:
$dispatcher = $service_container->get('Dispatcher');
$response = $dispatcher->route( $request );
The service called "Dispatcher" is either defined in the global dependency config or annotated in the class itself like this:
/*
* @Service
*/
class Dispatcher {
/**
* @Inject({"@service_container"})
*/
public function __construct($serviceContainer) {
$this->serviceContainer = $serviceContainer;
}
}
The Dispatcher needs this service locator because thats how my routing works. If you use some extra mapping between routes and called methods, you could avoid this.
To inject something into other services I use the @inject
annotation:
/** @Inject({"@SomeLogic", "@SomeGateway"}) */
public function setDependencies(SomeLogic $someLogic, SomeGateway $someGateway) {
$this->someLogic = $someLogic;
$this->someGateway = $someGateway;
}
I like this approach because the configuration of a Service Class is done in the class itself and not in a separate file. Refactoring doesn't involve messing around with lengthy XML or YAML files.
To not harm the performance I run a script everytime I change some annotations and at build time (or in general on every vagrant up). It uses Symfony's ContainerBuilder and PHP Dumper to generate a PHP Lookup file which extends the Base ServiceContainer Class.
The lookup for the example dispatcher would look like this:
protected function getDispatcherService()
{
return $this->services['dispatcher'] = new \Some\Dispatcher($this);
}
This looks like a lot of work upfront, but it clearly separates concerns later on, especially in bigger projects. Classes do what they are supposed to do and need little to no non-domain-logic code at all.
And I try to keep factories to an absolutely minimum. The DI Container itself is one big factory and repository but it is not manually maintained.
EDIT:
My Routing Function looks like this, request_uri is something like Controller/action/...
$path = explode( self::ACTION_SEPARATOR, $request_uri );
if( empty( $path[1] ))
throw new RoutingException( 'Invalid Action' );
$method = ucfirst( $path[1] );
$className = $path[0].'Controller';
$qualifiedClassName = 'Some\\Namespace\\'.$className;
if( !class_exists( $qualifiedClassName ))
throw new RoutingException(sprintf('Invalid controller "%s" for route "%s"', $qualifiedClassName, $route));
if( !is_callable( array( $qualifiedClassName, $method ) ))
throw new RoutingException(sprintf('Invalid Action "%s"', $method));
$controller = $this->serviceContainer->get( $className );
call_user_func( array( $controller, $method ), $message );
Best Answer
There are a couple of compromises that are to be made when using DI frameworks as far as I can see.
The most worrying for me is that your application code is usually spread between a main programming language (like Java) and XML/JSON configuration code (it is code). This means that in the case of problems with your application you need to look in two places. Often the configuration code is not easily related to the main application code.
Of course the configuration is also outside the bounds of what the compiler (and often the IDE) can check for you, meaning that it is much easier to make mistakes in this configuration than if you were writing a main class that handled the wiring. In effect this means that wiring issues are pushed from being compile-time issues to being runtime issues.
As well as splitting the application, using DI Frameworks also often mean that you use
@Inject
or similar within your code, instead of traditional DI techniques (CTOR interface injection in Java/C++, template injection in C++). The downside of this is that you must then use a DI framework with the application. An alternative would be to design your application without expecting a DI framework and then allow the DI framework to re-use the traditional CTOR/setter injection of your classes.The downside to the traditional injection mechanism comes when a complex and properly encapsulation classes demands a number of other relatively complex injections at CTOR time. This is usually solved by incorporating a
Builder
, but it can be a pain.EDIT:
The lads below have mentioned that
Guice
does not need a separate XML/JSON config. My answer really applies to my own usage of Spring.