【问题标题】:Reflection API in Factory pattern工厂模式中的反射 API
【发布时间】:2016-01-03 11:51:59
【问题描述】:

通常我们使用工厂方法来处理创建对象的问题,而不必指定将要创建的对象的确切类。

假设我们有 5 种不同的记录器实现。以下是如何使用传统工厂方法实现的伪代码:

class LoggerFactoryMethod extends AbstractFactoryMethod {
function makeLogger($param) {
    $logger = NULL;
    switch ($param) {
        case "mysql":
            $logger = new MysqlLogger;
        break;      
        case "mongo":
            $logger = new MongoLogger;
        break;      
        case "txt":
            $logger = new TxtLogger;
        break;
        case "redis":
            $logger = new RedisLogger;
        break;
        case "memcached":
            $logger = new MemcachedLogger;
        break;
        default:
            $logger = new TxtLogger;
        break;    
    }     
    return $logger;
}

这很好用,但是有许多 if/else(或 switch/code)条件并不那么酷。假设明天我们想再添加一个实现。我们将需要修改此代码并添加新的案例条件。这样,我们可能会打破 SOLID 原则中的 Open/Closed 原则。

相反,使用反射 API 更优雅,并且不需要对新实现进行重大更改。使用反射 API 的相同示例是:

class LoggerFactoryMethod extends AbstractFactoryMethod {
function makeLogger($param) {
    $allowed_implementations = array('mysql','mongo', 'txt', 'redis' , 'memcached');
    $class = new ReflectionClass($param);

    if (!in_array($param, $allowed_implementations) || !($class->IsInstantiable())){
        return null;
    }

    return $class->newInstance();
}

使用反射 API 代替传统的工厂模式(使用 if/else 条件)有什么缺点吗?使用它是一种好习惯吗?

【问题讨论】:

  • 我不会说 PHP,但是“一般来说”(其他语言,反射通常比构造函数更昂贵,所以你会受到资源开销)另外你实际上是在限制你的工厂模式, 因为你假设构造函数都是无操作的,如果你有一些操作数,那么你将需要逻辑来启用它。一般来说,工厂有很长的 if/then/else 或某种动态绑定列表,但 if/then 或 switch stmt 通常更易于处理+更快且更易于调试(以更多物理代码为代价)所以(忽略任何 PHP 都需要权衡 '?')
  • 我同意@mawalker 并且更喜欢传统的开关实现。当您想解决简单条件时,简单的条件语句没有错。由于 $allowed_implementations 而添加新的记录器实现时,您建议的改进工厂方法也需要修改。
  • 正如@iluwatar 指出的那样,如果您想变得更加灵活并支付反射成本,只需提取$allowed_implementations 集合,以便将其配置为系统/应用程序属性,所以你的工厂总能找到它。
  • 1.不确定 PHP 的反射是否过于昂贵,因为它是解释性语言。它可以比编译语言更容易(更便宜)进行反射。 2. 反射不需要 $allowed_implementations。如果我们可能想做一些过滤,这只是作为示例。

标签: php oop design-patterns reflection factory


【解决方案1】:

我对您的用例有一个建议,虽然它不能解决您的问题,但这是您可以为您的工厂采用的另一种方法。

假设您有一个配置文件来存储您要使用的实现:

# path/to/config.php
<?php
return [
    'mysql' => Some\Namespace\MysqlLogger::class,   
    'mongo'=> Some\Namespace\MongoLogger::class,
    'txt' => Some\Namespace\TxtLogger::class,
    'redis' => Some\Namespace\RedisLogger::class,
    'memcached' => Some\Namespace\MemcachedLogger::class,
];

您的工厂将采用这些并根据传递的参数创建记录器,并根据配置进行初始化。

class LoggerFactoryMethod extends AbstractFactoryMethod {
protected $logger = [];
public function __construct()
{
    $this->logger = require('path/to/config.php');
}

function makeLogger($param = 'txt') {
    return new $this->logger[$param];
}

以后当你需要添加或删除任何记录器时,你可以在配置列表中添加或删除。 只需尝试为每个记录器实现 LoggerInterface,这样无论您的实现如何都不会破坏您的代码。这里有一个记录器的 PSR 标准接口:PSR-3 Log 跟在这个 PSR-3 Log Description 之后

有了这个,我认为它提供了比 switch 语句更好的实现,并且不需要昂贵的反射。

只有我的两分钱

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    相关资源
    最近更新 更多