【问题标题】:Where to implement a factory method?在哪里实现工厂方法?
【发布时间】:2013-04-10 14:40:38
【问题描述】:

我一直在努力掌握 OOP 概念,虽然我确实掌握了其中大多数概念背后的一般概念,但我经常发现自己需要一些有关其实际实施的建议。工厂方法就是其中一种情况。

我正在编写一个 PHP 应用程序来处理来自 web 和命令行界面的请求,所以我想出了以下简单的类继承结构来涵盖这两种类型的请求:

abstract class Request {
}

class HttpRequest extends Request {
}

class CliRequest extends Request {
}

现在我需要一个工厂方法来返回具体的 Request 实例,具体取决于 php_sapi_name() 返回的值:

public function create() {
    if(php_sapi_name() === 'cli')
        return new CliRequest();
    else
        return new HttpRequest();
}

我的问题是:我把它放在哪里?我至少能想到三种可能:

1) 单独类中的静态方法:

class RequestFactory {
    public static function create() {
        // ...
    }
}

2) 单独类中的常规方法(需要先实例化该类):

class RequestFactory {
    public function create() {
        // ...
    }
}

3) 抽象父类中的静态方法:

abstract class Request {
    public static function create() {
        // ...
    }
}

每种解决方案的优缺点是什么,哪些会被认为是“合适的”,为什么?

【问题讨论】:

  • 选项2。其他甚至都不是OOP。
  • 恕我直言,2) 是最好的。但是,您应该将php_sapi_name 作为create 方法的参数传递。
  • 尽管所有方法都可以正常工作,但我会推荐选项 1,它不需要通过 new RequestFactory() 预先实例化工厂类。

标签: php oop factory


【解决方案1】:

所有这些可能性都会按预期工作。我真的没有得到任何“缺点”,因为它实现了,恕我直言,你的封装目标是什么。 现在,我们来看看工厂方法模式的本质:

定义一个用于创建对象的接口,但让创建对象的类 实现接口决定要实例化哪个类。工厂 方法让类将实例化推迟到子类。

我不确定你愿意做的事情是否完全符合这个定义。

相反,您似乎想要实现一个名为“Simple Factory”的东西,其中将实例化过程封装到一个类中。

但是将这种方法直接放入定义“请求”对象接口的抽象类中看起来并不是一个坏主意。

正如 Nicolas 所说,这是 Java、C# 和 Cocoa 领域中相当常见的模式。

由于这些原因,我会选择第三个选项

【讨论】:

    【解决方案2】:

    所以,我有一个想法,用method overloading 做我认为你想做的事。

    class Request {
    
        private $request;
        private $valid = true;
        private $type;
        private $vars;
        private $methods;
    
        public function __construct() {
            $this->type = php_sapi_name() === 'cli' ? 'cli' : 'http';
            if($this->is_cli()) $this->request = new CliRequest();
            else if($this->is_http())  $this->request = new HttpRequest();
            else {
                $this->valid = false;
                return;
            }
            $this->vars = get_class_vars($this->request);
            $this->methods = get_class_methods($this->request);
        }
    
        public function __get( $var ){
            if(!$this->valid) return false;
            if(!in_array($var, $this->vars)) return false;
            return $this->request->$var;
        }
    
        public function __set( $var , $val ){
            if(!$this->valid) return false;
            if(!in_array($var, $this->vars)) return false;
            return $this->request->$var = $val;
        }
    
        public function __call( $meth, $args ){
            if(!$this->valid) return false;
            if(!in_array($meth, $this->methods)) return false;
            return call_user_func_array($this->request->$var, $args);
        }
    
        public function is_cli( ){
            return $this->type == 'cli';
        }
    
        public function is_http( ){
            return $this->type == 'http';
        }
    
    
    }
    
    // Then, when calling the function...
    $request = new Request;
    $request->variable; // will get the variable from the Cli or Http request class
    $request->method("a","b","c"); // Will run the method from the Cli or Http request class
    

    【讨论】:

      【解决方案3】:

      要创建真正松散耦合的代码,您可以使用 Ray.Di 或 Injektor 并执行类似于以下的操作:

      <?php
      
      use Ray\Di\Di\Inject;
      use Ray\Di\Di\Scope;
      
      /**
       * @Scope("Singleton")
       */
      abstract class Request {
      }
      
      class HttpRequest extends Request {
      }
      
      class CliRequest extends Request {
      }
      
      class ARequestConsumer {
          /* @var Request */
          private $request;
      
          public function __construct( Request $request ) 
          {
              $this->request = $request;
          } 
      
          public function foo()
          {
             //...
          }
      }
      
      class Global extends Ray\Di\AbstractModule {
      
          public function configure()
          {
              $this->bind( 'Request' )
                  ->toProvider( 'RequestProvider' );
          }
      } 
      
      class RequestProvider implements \Ray\Di\ProviderInterface {
          /**
           * @return Request
           */
          public function get()
          {
             //.. factory method logic goes here that produces a concrete instance of Request
      
          }
      }
      
      
      
      $injector = Injector::create([new Global]);
      $consumer = $injector->getInstance('ARequestConsumer');
      $consumer->foo();
      

      【讨论】:

        【解决方案4】:

        在父类中使用静态方法对我来说似乎并不是一个糟糕的解决方案。 看看 Java 中的 Calendar 类:有一个 getInstance 方法(实际上有很多),它根据您的语言环境和其他一些条件返回一个 Calendar 实例。

        【讨论】:

          【解决方案5】:

          在这种情况下,我宁愿使用 Base Abstract 类作为实例的创建者,使用静态方法(第三个选项)

          我将使用一个外部类,就像在第一个选项中一样,当我需要创建一些可以破坏封装的依赖项时,比如对不同的实现有不同的依赖项。并且会降低类的可维护性。

          【讨论】:

            【解决方案6】:

            设计模式不限于 OOP,许多 OOP 设计模式的实现都是在考虑内存管理的情况下编写的。

            我来自 Java 世界,在 Java 中您必须使用严格的 OOP 设计模式。很简单,因为 Java 中的一切都是对象。有时您必须创建一个对象和一个方法,即使模式本身实际上并不需要它。 工厂方法设计模式就是这样一个例子。 工厂的实现按接口设计是很好的,但不需要类和方法来实现工厂。 设计模式的实现有时令人困惑的原因是编程语言有时需要设计模式本身并不严格需要的实现。在工厂方法中创建具有方法的类就是这样的例子。

            我的解决方案不是纯粹的 OOP,但 PHP 也不是,从长远来看,我认为这不是 OOP,而是设计模式工厂方法的最佳实现。

            我认为 PHP 的优雅之处在于它结合了两全其美。它提供了可靠的 OOP 设计可能性,但它并没有抛弃过程编程的良好元素。

            您可以像这样简单地创建代码:

            function createRequest($pRequesttype){
              switch($pRequesttype){
                case "cli":
                  $tmp = new CliRequest();
                  break;
                case "http":
                  $tmp = new HttpRequest();
                  break;
                default:
                  $tmp = new DefaultRequest();
              }
              return $tmp;
             }
            

            始终返回一个默认实现来处理请求。 switch 语句是以对软件工程师友好的方式扩展选项数量的最佳选择。

            现在你已经在你的 create 函数中调用了 php_sapi_name。我建议你把它从函数的实现中拿出来。最佳实践是让一个函数只做一项工作,获取请求和处理请求是两个函数。创建一个 createRequest 函数,该函数具有我向您展示的参数。

            回答您的问题: 1、2 还是 3?嗯,实际上是 4 个。 :-) 如果是 1、2 还是 3?

            肯定是1,因为我不想为一个简单的方法加载太多的类,但是为了简化这种情况我提出了解决方案4。

            我不会使用方法 2,因为在某个时刻创建一个类效率不高。我认为这是最佳实践,但不是最佳实际实施。如果使用这个解决方案,那么也请支持带有工厂接口的类。最佳 OOP 设计,但不是最佳实际实现。

            我当然不会使用方法 3,因为抽象类用于抽象数据对象和接口以抽象行为。工厂方法是接口的抽象,而不是抽象类的抽象。

            【讨论】:

            • 我正在查看基于标签 php.ini 的最新问题列表。我不知道这个问题很久以前就被问到了。回答完问题后才看到。 :-)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多