允许在PHP中使用抽象工厂模式进行自定义类实例化


Allowing custom class instantiation with the abstract factory pattern in PHP

目前,我使用抽象工厂来指定用于生成请求对象的自定义类名。我这样做的理由是允许我在不更改代码的情况下轻松地扩展核心功能。然而,最近,我对这种方法的有效性产生了一些怀疑。所以我的问题是:

允许工厂实例化任何提交的类名与预期的接口匹配是工厂概念的一种亵渎?我会更好地避免这种情况吗

更新

这里的逻辑是:一方面,现实生活中的汽车工厂(例如)如果没有配备制造这种汽车的机器,就无法制造出汽车。另一方面,下面的代码就像是给同一家汽车工厂一个蓝图,让它制造出最初不打算制造的定制汽车。

另一种选择是传入一个配置对象,指定一个可以与工厂一起使用的自定义类名,并将工厂限制为仅在与配置指定的自定义类名特定匹配的情况下生成自定义类。有什么想法吗?


以及相关代码。。。

<?php
interface AbstractRequestFactory
{
  public function buildRequest($type);
}
class RequestFactory implements AbstractRequestFactory
{
  public function buildRequest($type='http')
  {
    if ($type == 'http') {
      return new HttpRequest();
    } elseif ($type == 'cli') {
      return new CliRequest();
    } elseif ($custom = $this->makeCustom($type)){
      return $custom;
    } else {
      throw new Exception("Invalid request type: $type");
    }
  }
  protected function makeCustom($type)
  {
    if (class_exists($type, FALSE)) {
      $custom = new $type;
      return $custom instanceof RequestInterface ? $custom : FALSE;
    } else {
      return FALSE;
    }
  }
}
// so using the factory to create a custom request would look like this:
class SpecialRequest implements RequestInterface {}
$factory = new RequestFactory();
$request = $factory->buildRequest(''SpecialRequest');

您所拥有的看起来相当不错。拥有工厂的目的是传递一些条件,并让方法返回一个对象,您认为该对象将具有可用于调用代码的相同可调用方法。您通过实现RequestInterface来实现这一假设,因此只要任何自定义请求类实现相同的接口,就不会出现"无法在非对象上调用函数"的情况。

几个建议(只是个人偏好):

  • 我会在buildRequest 中的$type上使用switch/case

  • 我会从makeCustom()返回null或object,否则您将混合返回类型(object和bool)

  • 根据您有多少自定义类型,我实际上会将它们硬编码到switch案例中,只是为了缓解任何混乱。不要误解我的意思,如果你有很多课,你所学的就很好,但你很可能没有。

  • 您有没有考虑过将"在不更改代码的情况下轻松扩展核心功能"部分放入一个抽象的父类中,该父类可以通过自定义类型类进行扩展?

  • 此外,因为工厂创建对象,所以通常将其设置为静态。

示例代码片段:

public static function getRequest($type='http')
{
    switch ($type) {
        case 'http':
            return new HttpRequest();
        case 'cli':
            return new CliRequest();
        case 'myCustom1':
            return new MyCustom1();
        case 'myCustom2':
            return new MyCustom2();
        default: 
            throw new Exception("Invalid request type: $type");
    }
}
$request = RequestFactory::getRequest($type);
// As long as all objects in factory have access to same methods
$request->doSomething();
$request->andDoSomethingElse();
// Otherwise you end up with that feared 'unable to call function on non-object'
$request->iAmASneakyMethodNotEnforcedByAnInterfaceOrAvailableByExtension();    

这是非常主观的,所以以下只是一种意见:

我不会很快用这样的东西。如果工厂只关心少数几个类,那么我就对它们进行硬编码。但如果你有一大套,我认为这是合适的。

假设您正在验证该类是否扩展了适当的接口,我想说您所做的没有错,因为它是故障安全的。使用该工厂方法的代码将显示为干净;我认为这是最重要的事情。

如果你到处都在使用这种技术,那么我会反对。但由于这在实现中是隐藏的,我认为你可以有更多的余地来做一些稍微不合适的事情。

为什么不使用调度数组?即

class RequestFactory 
{
    private static $requests = array(
      'http' => 'HttpRequest',
      'cli' => 'CliRequest',
      'summatelse' => 'Summat'
    );
    public static GetRequest($type)
    {
       if (array_key_exists($type, $requests)) return new $requests[$type];
       else throw new Exception("Invalid request type: $type"); 
    }
}