6

我一直在尝试掌握 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() {
        // ...
    }
}

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

4

6 回答 6

1

所以,我有一个想法,使用方法重载来做我认为你想做的事。

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
于 2014-02-24T15:27:40.517 回答
1

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

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

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

相反,您似乎想要实现一种称为“简单工厂”的东西,其中将实例化过程封装到一个类中。

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

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

出于这些原因,我的选择是第三个选项

于 2013-04-12T11:47:48.637 回答
1

要创建真正松散耦合的代码,您可以使用 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();
于 2014-03-17T20:36:37.700 回答
0

设计模式不限于 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 语句是以对软件工程师友好的方式扩展选项数量的最佳选择。

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

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

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

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

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

于 2014-02-27T09:24:22.090 回答
0

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

于 2013-04-10T15:04:36.720 回答
0

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

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

于 2013-04-10T15:19:53.180 回答