Following on from my question on service locators I have decided to use constructor injection instead. Please consider the following code:
<?php
interface IAppServiceRegistry {
public function getDb();
public function getLogger();
}
interface IFooServiceRegistry extends IAppServiceRegistry {
public function getFooBarBazModel();
}
class AppServiceRegistry
implements IAppServiceRegistry, IFooServiceRegistry
{
private $logger;
private $db;
private $fooBarBazModel;
public function getDb() {
// return db (instantiate if first call)
}
public function getLogger() {
// return logger (instantiate if first call)
}
public function getFooBarBazModel() {
if (!isset($this->fooBarBazModel)) {
$this->fooBarBazModel = new FooBarBazModel( $this->getDb() );
}
return $this->fooBarBazModel;
}
}
// Example client classes:
/**
* Depends on db, logger and foomodel.
*/
class Foo {
private $db;
private $logger;
private $fooModel;
public function __construct(IFooServiceRegistry $services) {
$this->db = $services->getDb();
$this->logger = $services->getLogger();
$this->fooModel = $services->getFooModel();
}
}
/**
* Depends on only db and logger.
*/
class BarBaz {
private $db;
private $logger;
public function __construct(IAppServiceRegistry $services) {
$this->db = $services->getDb();
$this->logger = $services->getLogger();
}
}
Then I would add new service factory methods to the registry as the application evolved, creating segregated interfaces wherever logically appropriate.
Is this approach sane?
Although I don’t normally read php, I think I understand most of it. Technically, it looks fine, but then you write
That tends to hurt the idea of loose coupling because instead of a general-purpose Service Locator you now have a specialized Service Locator.
Injecting such a serivce registry into each class works against the Single Responsibility Principle (SRP) because once a class has access to the registry, it is too easy to request yet another dependency than originally conceived, and you will end up with a God Object.
It would be better to inject the required dependencies directly into the classes that need them. Thus, your Foo class’ constructor should take a db, a logger and a fooModel, while the BarBaz class should take only a db and a logger parameter.
The next question may then be: What if I need a lot of different dependencies to perform the work? That would require a truly ugly constructor with lots of parameters, and this goes against other well-known OO practices.
True, but if you need lots of dependencies, you are probably violating the SRP and should try to split your design into finer-grained objects 🙂