问题
I have a MVC application with a Domain Model well defined, with entities, repositories and a service layer.
To avoid having to instantiate my service classes inside my controllers, and thus, mess my controllers with logic that does not suit they, I created a helper that acts as a sort of Service Locator, but after reading a bit, I realized that many devs:
- http://blog.tfnico.com/2011/04/dreaded-service-locator-pattern.html
- http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
- http://underground.infovark.com/2010/06/18/the-service-locator-pattern-is-the-new-global-variable/
- http://www.andyfrench.info/2011/05/service-locator-anti-pattern_17.html
Say that the Service Locator is actually an anti-pattern. But I think my implementation is not an anti-pattern.
The reason they consider the Service Locator an anti-pattern, is because it hide dependencies, however, I inject the only dependency (the Entity Manager, and this dependency probably will not change, because it is in the signature of the Service interface) required by a service class, at the time I instantiate the Service Locator.
Here is my code:
<?php
namespace App\Controller\Action\Helper;
use Zend_Controller_Action_Helper_Abstract as Helper,
Doctrine\ORM\EntityManager;
/**
* Service Locator Helper
* @author JCM
*/
class Service extends Helper {
/**
* The actual EntityManager
* @var \Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* Services Namespace
* @var string
*/
private $ns;
/**
* @param EntityManager $entityManager
* @param string $ns The namespace where to find the services
*/
public function __construct( EntityManager $entityManager, $ns )
{
$this->entityManager = $entityManager;
$this->ns = $ns;
}
/**
* @param string $serviceName
* @param array $options
* @param string $ns
*/
public function direct( $serviceName, array $options = array(), $ns = null )
{
$ns = ( (!$ns) ? $this->ns : $ns ) . '\\';
$class = $ns . $serviceName;
return new $class( $this->entityManager, $options );
}
/**
* @param EntityManager $entityManager
*/
public function setEntityManager( EntityManager $entityManager )
{
$this->entityManager = $entityManager;
}
/**
* @return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* @param string $name
*/
public function __get( $name )
{
return $this->direct( $name );
}
}
Registering the Action Helper with the Front Controller:
//inside some method in the bootstrap
HelperBroker::addHelper( new App\Controller\Action\Helper\Service( $entityManager, '\App\Domain\Service' ) );
And how I use this Helper in my controllers:
//Some Controller
$myService = $this->_helper->service( 'MyService' ); //returns an instance of the class App\Domain\Service\MyService
$result = $myService->doSomethingWithSomeData( $this->getRequest()->getPost() );
//etc...
- My implementation is correct?
- It really is an anti-pattern?
- What are the possible problems that I might face?
- How can I refactor my code to eliminate this anti-pattern, but continue with the functionality?
回答1:
I don't think that what you've built does implement the service locator pattern. If you had so, there would be a global "registry" somewhere.
What I see is basically a factory class (App\Controller\Action\Helper\Service
) with dependencies, which are injected via the ctor. So your class does not know where its dependencies came from and it's also not responsible for creating them (which is a good thing!).
Correct me if I'm wrong. :)
BTW: That's also the reason why you should not pass around your dependency injection container. It turns into a service locator.
来源:https://stackoverflow.com/questions/9011787/why-the-service-locator-is-a-anti-pattern-in-the-following-example