Excellent wrap up, Stereofrog.

Originally Posted by
b1ind
Can you please explain this a bit more?
I'm unsure if you mean to explain how I use callbacks, or why an interface makes for an inflexible solution? I'll try to answer both, since they are connected.
Now, when I said that an interface made the solution rigid, I was being imprecise. It's not the interface, which is rigid, it's the object oriented style. Eg. the contract which gets explicit with an interface, but still exists when there isn't a formal interface.
Assuming that you wanted to be able to plug factories into the assembler at runtime, you could go an object oriented route, and define a factory interface:
PHP Code:
interface Factory
{
function createInstance($class, $locator);
}
Then extend the assembler to hold a map of factories, instead of hardcoding the call to CreateInstance() in:
PHP Code:
class Assembler
{
...
protected $factories = Array();
function register($className, Factory $factory) {
$this->factories[$className] = $factory;
}
function create($className) {
return $this->factories[$class]->createInstance($className, $this);
}
}
You now have a container with factories attached dynamically (at runtime). That gives you great flexibility, however this implementation would require you to write a new class for each component you want to have a factory for. That's rather bureaucratic, so we need something a bit more smooth.
These factories have a simple protocol -- In fact they have just one method. This is a pattern, which is known as a command object, and it is the object oriented variant of a lambda. As long as it's stateless, it can be replaced with a delegated function call instead. The advantage then, is that you can group multiple such factory function together in a single class, instead of having multiple classes, each with one method. Behold:
PHP Code:
class Assembler
{
...
protected $factories = Array();
function register($className, $factory) {
$this->factories[$className] = $factory;
}
function create($className) {
assert(is_callable($factory));
return call_user_func($this->factories[$class], $className, $this);
}
}
And in case the usage isn't totally obvious, here goes:
PHP Code:
class MyFactory
{
function CreateUserGateway($class, $locator) {
return new UserGateway(
$locator->get('DatabaseConnection'),
$locator->get('IdentityMap'));
}
function CreateDatabaseConnection($class, $locator) {
return new DatabaseConnection();
}
function CreateIdentityMap($class, $locator) {
return new IdentityMap();
}
}
$locator = new Assembler();
$factory = new MyFactory();
$locator->register('UserGateway', Array($factory, 'CreateUserGateway'));
$locator->register('DatabaseConnection', Array($factory, 'CreateDatabaseConnection'));
$locator->register('IdentityMap', Array($factory, 'CreateIdentityMap'));
$users = $locator->get('UserGateway');
Or in a functional style:
PHP Code:
$locator = new Assembler();
$locator->register(
'UserGateway',
create_function(
'$class, $locator',
'return new UserGateway(
$locator->get("DatabaseConnection"),
$locator->get("IdentityMap"));'));
$locator->register(
'DatabaseConnection',
create_function(
'$class, $locator',
'return new DatabaseConnection(...);'));
$locator->register(
'IdentityMap',
create_function(
'$class, $locator',
'return new IdentityMap(...);'));
$users = $locator->get('UserGateway');
You might want to look up is_callable, call_user_func and create_function, if you aren't familiar with these lesser known PHP features.
Bookmarks