关于工厂方法的建议

时间:2011-01-07 16:04:31

标签: php design-patterns factory-pattern

使用php 5.2,我正在尝试使用工厂将服务返回给控制器。我的请求uri的格式为www.mydomain.com/service/method/param1/param2/etc。然后我的控制器将使用uri中发送的令牌调用服务工厂。从我所看到的情况来看,我可以在工厂找到两条主要路线。

单一方法:

class ServiceFactory { 
    public static function getInstance($token) { 
        switch($token) { 
            case 'location':
                return new StaticPageTemplateService('location');
                break;
            case 'product':
                return new DynamicPageTemplateService('product');
                break;
            case 'user'
                return new UserService();
                break;
            default:
                return new StaticPageTemplateService($token);
         }
    }
}

或多种方法:

class ServiceFactory { 
    public static function getLocationService() { 
        return new StaticPageTemplateService('location');
    }
    public static function getProductService() { 
        return new DynamicPageTemplateService('product');
    }
    public static function getUserService() { 
        return new UserService();
    }
    public static function getDefaultService($token) { 
        return new StaticPageTemplateService($token);
    }
}

所以,考虑到这一点,我将有一些通用服务,我将传递该令牌(例如,StaticPageTemplateService和DynamicPageTemplateService),它可能会实现另一个工厂方法,就像抓取模板,域对象等。还有一些将是特定服务(例如,UserService),它将是该令牌的1:1而不会被重用。因此,对于少量服务而言,这似乎是一种不错的方法(如果不是,请提供建议)。但是,随着时间的推移,随着时间的推移和我的网站的增长,我最终会有100种可能性。这似乎不再是一个好方法。我只是开始使用或者是否有其他更适合的设计模式?感谢。

更新:@JSprang - 令牌实际上是在uri中发送的,例如mydomain.com/location会想要一个特定于loction的服务,而mydomain.com/news会想要一个特定于新闻的服务。现在,对于其中很多,这项服务将是通用的。例如,许多页面将调用StaticTemplatePageService,其中令牌被传递到服务。该服务反过来将获取“位置”模板或“链接”模板,然后将其吐出来。有些人需要传递令牌的DynamicTemplatePageService,例如“news”,该服务将获取一个NewsDomainObject,确定如何呈现它并将其吐出。其他人,比如“user”将特定于UserService,其中它将具有Login,Logout等方法。因此,基本上,令牌将用于确定需要哪个服务,如果它是通用服务,则该令牌将是传递给那项服务。也许令牌不是正确的术语,但我希望你能达到目的。

我想使用工厂,因此我可以轻松更换我需要的服务,以防我的需求发生变化。我只是担心,在网站变得更大(页面和功能)后,工厂将变得相当臃肿。但我开始觉得我无法摆脱将数组存储在数组中(如Stephen的解决方案)。这对我来说并没有感觉到OOP,我希望能找到更优雅的东西。

6 个答案:

答案 0 :(得分:1)

我认为当您的网站变得庞大时,无法避免此令牌服务映射维护工作。无论你如何实现这个列表,切换块,数组等,这个文件总有一天会变得很大。所以我的意见是避免这个列表并使每个令牌成为服务类,对于那些通用服务,你可以继承它们,就像这样

class LocationService extends StaticPageTemplateService { 
    public function __construct(){
        parent::__construct('location');
    }
}

class ServiceFactory { 
    public static function getInstance($token) { 
        $className = $token.'Service';
        if(class_exist($className)) return new $className();
        else return new StaticPageTemplateService($token);
    }
}

通过这种方式,您可以避免每次添加或更改令牌时编辑工厂类文件,只需更改特定的令牌文件即可。

答案 1 :(得分:1)

我有一个更好的工厂模式解决方案,它允许您添加新服务,而不需要为该特定服务创建新类。概述如下:

对于工厂:

  class ServiceFactory{
    private static $instance = null;
    private static $services = array();
    private function __construct(){
      // Do setup
      // Maybe you want to add your default service to the $services array here
    }

    public function get_instance(){
      if($this->instance){
        return $this->instance;
      }
      return $this->__construct();
    }

    public function register_service($serviceName, $service){
      $this->services[$serviceName] = $service;
    }

    public function get_service($serviceName){
      return $this->services[$serviceName]->get_new();
    }
  }

抽象服务:

  include('ServiceFactory.php');

  class AbstractService{
    public function __construct($serviceName){
      $factory = ServiceFactory::get_instance();
      $factory->register_service($serviceName, $this);
    }

    public function get_new(){
      return new __CLASS__;
    }
  }

然后是具体的服务:

  include('AbstractService.php');

  class ConcreteService extends AbstractService{
    // All your service specific code.
  }

此解决方案使您的依赖项成为一种方式,您只需扩展AbstractService即可添加新服务,无需修改任何现有代码。你使用get_service('news')或你想要的任何一个进入工厂,工厂在$ services数组中查找关联的对象,并在该特定对象上调用get_new()函数,该函数为您提供特定的新实例与之合作的服务。

答案 2 :(得分:0)

以下是我如何做一个单身工厂(为简洁起见删除了评论):

已更新,以更好地满足您的目的。

class ServiceFactory {
    private static $instance;
    private function __construct() {
        // private constructor
    }
    public function __clone() {
        trigger_error('Clone is not allowed.', E_USER_ERROR);
    }
    public static function init() {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }
    public function get_service($name, $parameter) {
        $name .= 'TemplateService';
        return $this->make_service($name, $parameter);
    }

    private function make_service($name, $parameter) {
        if (class_exists($name)) {
            return new $name($parameter);
        } else {
            throw new LogicException('Could not create requested service');
            return false;
        }
    }
}

以这种最简单的形式,只需传递服务的字符串名称:

function whatever() {
    $ServiceFactory = ServiceFactory::init();
    $new_service = $ServiceFactory->get_service('StaticPage', 'location');
    return $new_service;
}

答案 3 :(得分:0)

我不是PHP开发人员,所以我不会尝试显示任何代码,但这就是我要做的。我将实现Strategy Pattern并创建一个IServiceProvider接口。该接口可以具有GetService()方法。然后,您将创建四个新对象:LocationService,ProductService,UserService和DefaultService,所有这些对象都将实现IServiceProvider接口。

现在,在您的工厂中,构造函数将接受IServiceProvider并具有一个公共GetService()方法。调用该方法时,它将使用注入的IServiceProvider的策略。这样可以提高可扩展性,因为每次有新服务时都不必打开Factory,只需要创建一个实现IServiceProvider的新类。

我决定快速在C#中快速模拟它,所以你会有一个例子。我知道这不是你正在使用的语言,但它可能有助于澄清我在说什么。代码如下所示。

public interface IServiceProvider
{
    Service GetService();    
}

public class UserServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class StaticPageTemplateServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class DynamicPageTemplateServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class DefaultServiceProvider : IServiceProvider
{
    public Service GetService()
    {
        //perform code to get & return the service
    }
}

public class ServiceFactory
{
    public ServiceFactory(IServiceProvider serviceProvider)
    {
        provider = serviceProvider;
    }
    private IServiceProvider provider;

    public Service GetService()
    {
        return provider.GetService();
    }
}

答案 4 :(得分:0)

服务工厂实现(带有我们将在具体类中使用的接口):

class ServiceFactory
{
    private static $BASE_PATH = './dirname/';
    private $m_aServices;

    function __construct()
    {
        $this->m_aServices = array();

        $h = opendir(ServiceFactory::$BASE_PATH);
        while(false !== ($file = readdir($h)))
        {
            if($file != '.' && $file != '..')
            {
                require_once(ServiceFactory::$BASE_PATH.$file);
                $class_name = substr($file, 0, strrpos($file, '.'));

                $tokens = call_user_func(array($class_name, 'get_tokens'));
                foreach($tokens as &$token)
                {
                    $this->m_aServices[$token] = $class_name;
                }
            }
        }
    }

    public function getInstance($token)
    {
        if(isset($this->m_aServices[$token]))
        {
            return new $this->m_aServices[$token]();
        }
        return null;
    }
}

interface IService
{
    public static function get_tokens();
}

$ BASE_PATH.'UserService.php':

class UserService implements IService
{
    function __construct()
    {
        echo '__construct()';
    }

    public static function get_tokens()
    {
        return array('user', 'some_other');
    }
}

因此,我们所做的基本上是为任何具体的类实现自行注册所有令牌。只要您的类驻留在$ BASE_PATH中,它们将在实例化时自动由ServiceFactory加载(当然,如果您愿意,可以通过静态方法更改ServiceFactory来提供此功能)。

无需使用大型switch语句提供对具体实现的访问,因为它们在由具体类级别实现的get_tokens()函数构建的内部映射中都有帮助。 token->类关系存储在服务工厂内的1:1映射中,因此如果您因任何原因链接令牌,则需要重构它。

答案 5 :(得分:0)

  

一些将是特定服务(例如,UserService),它将是该令牌的1:1而不会被重用。所以这   对于少量服务似乎是一种好的方法(如果不是,请提出建议)。但是什么时候结束了   时间和我的网站增长,我最终有100个可能性。这似乎不再是一个好方法。我离开了   开始或是否有另一种更适合的设计模式?感谢。

很抱歉,但我想你现在正试图解决你为自己创造的问题。

  

令牌实际上是在uri中发送的,例如mydomain.com/location会想要一个特定的服务   loction和mydomain.com/news需要特定于新闻的服务。现在,对于很多这些,   服务将是通用的。例如,很多页面都会调用StaticTemplatePageService   令牌传递给服务。该服务反过来将获取“位置”模板或   “链接”模板,然后将其吐出来。

有些人已经建议使用依赖注入容器解决整个工厂问题,但我想知道为什么首先需要工厂?您似乎正在编写一个Controller(我猜),它可以为多种不同类型的请求生成响应,并且您尝试在一个类中解决所有。我会确保不同的请求(/ location,/ news)映射到专用的,小的,可读的控制器(LocationController,NewsController)。由于一个控制器只需要一个服务,因此编写,维护和扩展应该更容易。

通过这种方式,您可以在专用,简洁,可读的类中解决依赖关系,而不是一个巨大的God类。这意味着你也不会有数百行的切换问题,你应该将“location”映射到LocationController,将“news”映射到NewsController等等。现在很多PHP框架都使用了FrontController,而我想象一下这也是你的方式。

PS:为了确保NewsService实际进入NewsController,我建议使用依赖注入容器。它让你的生活更轻松;)