关于工厂方法的建议

发布于 2024-10-10 20:47:12 字数 2021 浏览 2 评论 0原文

使用 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 并且不会重用。因此,对于少量服务来说,这似乎是一个不错的方法(如果不是,请给出建议)。但是,随着时间的推移和我的网站的发展,我最终会拥有数百种可能性。这似乎不再是一个好方法。我是刚开始还是有其他更适合的设计模式?谢谢。

更新:@JSprang - 令牌实际上是在 uri 中发送的,例如 mydomain.com/location 需要特定于位置的服务,而 mydomain.com/news 需要特定于新闻的服务。现在,对于其中很多服务来说,服务将是通用的。例如,许多页面将调用 StaticTemplatePageService,其中令牌被传递到服务中。该服务反过来将获取“位置”模板或“链接”模板并将其吐出。有些需要 DynamicTemplatePageService,其中令牌被传入,例如“新闻”,并且该服务将获取 NewsDomainObject,确定如何呈现它并将其吐出。其他的,比如“用户”将特定于 UserService,其中它将具有登录、注销等方法。所以基本上,令牌将用于确定需要哪个服务,如果它是通用服务,则该令牌将是传递给该服务。也许令牌不是正确的术语,但我希望您明白目的。

我想使用工厂,这样我就可以轻松地更换我需要的服务,以防我的需求发生变化。我只是担心网站变得更大(页面和功能)之后,工厂会变得相当臃肿。但我开始觉得我无法摆脱将映射存储在数组中(就像斯蒂芬的解决方案)。这对我来说并不感觉是面向对象的,我希望找到更优雅的东西。

Using php 5.2, I'm trying to use a factory to return a service to the controller. My request uri would be of the format www.mydomain.com/service/method/param1/param2/etc. My controller would then call a service factory using the token sent in the uri. From what I've seen, there are two main routes I could go with my factory.

Single method:

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);
         }
    }
}

or multiple methods:

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);
    }
}

So, given this, I will have a handful of generic services in which I will pass that token (for example, StaticPageTemplateService and DynamicPageTemplateService) that will probably implement another factory method just like this to grab templates, domain objects, etc. And some that will be specific services (for example, UserService) which will be 1:1 to that token and not reused. So, this seems to be an ok approach (please give suggestions if it is not) for a small amount of services. But what about when, over time and my site grows, I end up with 100s of possibilities. This no longer seems like a good approach. Am I just way off to begin with or is there another design pattern that would be a better fit? Thanks.

UPDATE: @JSprang - the token is actually sent in the uri like mydomain.com/location would want a service specific to loction and mydomain.com/news would want a service specific to news. Now, for a lot of these, the service will be generic. For instance, a lot of pages will call a StaticTemplatePageService in which the token is passed in to the service. That service in turn will grab the "location" template or "links" template and just spit it back out. Some will need DynamicTemplatePageService in which the token gets passed in, like "news" and that service will grab a NewsDomainObject, determine how to present it and spit that back out. Others, like "user" will be specific to a UserService in which it will have methods like Login, Logout, etc. So basically, the token will be used to determine which service is needed AND if it is generic service, that token will be passed to that service. Maybe token isn't the correct terminology but I hope you get the purpose.

I wanted to use the factory so I can easily swap out which Service I need in case my needs change. I just worry that after the site grows larger (both pages and functionality) that the factory will become rather bloated. But I'm starting to feel like I just can't get away from storing the mappings in an array (like Stephen's solution). That just doesn't feel OOP to me and I was hoping to find something more elegant.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

南七夏 2024-10-17 20:47:12

我认为当您的网站变大时,没有办法避免这种令牌服务映射维护工作。无论你如何实现这个列表、开关块、数组等,这个文件总有一天会变得巨大。所以我的观点是避免这个列表,让每个令牌成为一个服务类,对于那些通用服务,你可以继承它们,这样

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);
    }
}

,你可以避免每次添加或更改令牌时编辑工厂类文件,你只需要更改特定的令牌文件即可。

I think there is no way to avoid this token-service-mapping maintaining work when your site growing large. No matter how you implement this list, switch block, array, etc. this file will become huge someday. So my opinion is to avoid this list and make each token a service class, for those generic services, you can inherit them, like this

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);
    }
}

in this way, you can avoid to edit the factory class file each time a token added or changed, you just need to change the specific token file.

单挑你×的.吻 2024-10-17 20:47:12

我有一个更好的工厂模式解决方案,它允许您添加新服务,而无需执行任何操作,只需为该特定服务创建一个新类即可。概述如下:

对于工厂:

  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() 函数,该函数会为您提供特定对象的新实例合作的服务。

I have a better factory pattern solution that will allow you to add new services without doing anything more than creating a new class for that specific service. Outlined below:

For the Factory:

  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();
    }
  }

An Abstract Service:

  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__;
    }
  }

And then a concrete service:

  include('AbstractService.php');

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

This solution makes your dependencies one way and you can add new services by simply extending the AbstractService, no need to modify any existing code. You call into the factory with the get_service('news') or whichever you want, the factory looks up the associated object in its $services array and calls the get_new() function on that particular object which gets you a new instance of the specific service to work with.

¢好甜 2024-10-17 20:47:12

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

现在,在您的工厂中,构造函数将接受 IServiceProvider 并具有一个公共 GetService() 方法。调用该方法时,会使用注入的IServiceProvider的策略。这提高了可扩展性,因为您不必每次有新服务时都打开工厂,只需创建一个实现 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();
    }
}

I'm not a PHP developer, so I'm not going to attempt to show any code, but here's what I would do. I would implement the Strategy Pattern and create an IServiceProvider interface. That interface could have a GetService() method. Then you would create four new objects: LocationService, ProductService, UserService, and DefaultService, all of which would implement the IServiceProvider interface.

Now, in your factory, the constructor would take in a IServiceProvider and have one public GetService() method. When the method is called, it will use the strategy of the injected IServiceProvider. This improves extensability as you won't have to open the Factory everytime you have a new service, you would just create a new class that implements IServiceProvider.

I decided quick to mock this up quick in C# so you would have an example. I understand this isn't the language you are using, but maybe it will help clarify what I'm saying. Code shown below.

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();
    }
}
紫罗兰の梦幻 2024-10-17 20:47:12

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

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() 函数构建的内部映射。令牌->类关系存储在服务工厂内的 1:1 映射中,因此如果您出于某种原因链接令牌,则需要重构它。

Service factory implementation (with an interface that we'll use in concrete classes):

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');
    }
}

So, what we're doing essentially is self-registering all tokens for any concrete class implementation. As long as your classes reside in $BASE_PATH, they'll automatically be loaded by the ServiceFactory when it's instantiated (of course, you could change ServiceFactory to provide this via static methods if you wanted to).

No need to have a big switch statement providing access to concrete implementations as they're all help in an internal map that's built by the get_tokens() function that are implemented at the concrete class level. The token->class relationship is stored in a 1:1 map within the service factory, so you'd need to refactor this if you're chaining tokens for whatever reason.

心意如水 2024-10-17 20:47:12

这是我如何创建一个单例工厂(为简洁起见,删除了注释):

更新以更好地服务于您的目的。

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;
}

Here's how I do a singleton factory (comments removed for brevity):

Updated to better serve your purpose.

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;
        }
    }
}

In it's simplest form like this, just pass a string name of the service:

function whatever() {
    $ServiceFactory = ServiceFactory::init();
    $new_service = $ServiceFactory->get_service('StaticPage', 'location');
    return $new_service;
}
梦巷 2024-10-17 20:47:12

还有一些是特定服务(例如 UserService),它们与该令牌是 1:1 且不会重用。所以,这个
对于少量服务来说,这似乎是一个不错的方法(如果不是,请给出建议)。但是什么时候结束呢
随着时间的推移和我的网站的发展,我最终会拥有数百种可能性。这似乎不再是一个好方法。我还差得远吗
首先还是有其他更适合的设计模式?谢谢。

很遗憾地说,但我认为您现在正在尝试解决您为自己制造的问题。

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

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

这样,您就可以在专用的、简洁的、可读的类中解决依赖关系,而不是在一个巨大的上帝类中。这意味着您也不会遇到数百行切换的问题,您应该只将“位置”映射到 LocationController,将“新闻”映射到 NewsController 等。现在很多 PHP 框架都使用 FrontController 来实现这一点,我想象一下这也适合你。

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

And some that will be specific services (for example, UserService) which will be 1:1 to that token and not reused. So, this
seems to be an ok approach (please give suggestions if it is not) for a small amount of services. But what about when, over
time and my site grows, I end up with 100s of possibilities. This no longer seems like a good approach. Am I just way off
to begin with or is there another design pattern that would be a better fit? Thanks.

Sorry to say, but I think you're now trying to solving a problem that you've created for yourself.

the token is actually sent in the uri like mydomain.com/location would want a service specific to
loction and mydomain.com/news would want a service specific to news. Now, for a lot of these, the
service will be generic. For instance, a lot of pages will call a StaticTemplatePageService in which
the token is passed in to the service. That service in turn will grab the "location" template or
"links" template and just spit it back out.

Some have already suggested using a Dependency Injection Container to solve the whole factory issue, but I wonder why there is a need for a factory in the first place? You seem to be writing a Controller (I guess), that can generate a response for a multitude of different types of request, and you're trying to solve it all in one class. I'd instead make sure that the different requests (/location, /news) map to dedicated, small, readable controllers (LocationController, NewsController). As one controller needs only one service this should be much easier to write, maintain and expand.

That way, you solve the dependencies in dedicated, concise, readable classes instead of one giant God class. That means you'll have no issues with a switch of hundreds of lines either, you should just map "location" to LocationController, "news" to NewsController, etc. A lot of PHP frameworks these days use a FrontController for that, and I imagine that is the way to go for you as well.

PS: to make sure the NewsService actually makes into the NewsController, I would suggest using a dependency injection container. It makes your life easier ;)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文