通过数组定义类选项是一种不好的实践吗?

6

当我们看到像Dojo、Mootools、jQuery、JS Prototype等Javascript框架时,我们会发现选项通常是通过这样的数组定义的:

dosomething('mainsetting',{duration:3,allowothers:true,astring:'hello'});

在编写PHP类时实现相同的想法是否是一种不良实践?

一个例子:

class Hello {

    private $message = '';
    private $person = '';


    public function __construct($options) {

        if(isset($options['message'])) $this->message = $message;
        if(isset($options['person'])) $this->person = $person;
    }


    public function talk() {

        echo $this->person . ' says: ' . $this->message;
    }
}

常规做法:

class Hello {

    private $message = '';
    private $person = '';


    public function __construct() {}


    public function setmessage($message) {

        $this->message = $message;
    }


    public function setperson($person) {

        $this->person = $person;
    }


    public function talk() {

        echo $this->person . ' says: ' . $this->message;
    }
}

第一个示例的优点是您可以传递尽可能多的选项,而类仅会提取它所需的选项。
例如,从JSON文件中提取选项时,这可能非常方便:
$options = json_decode($options);
$hello = new Hello($options);

这是我经常采取的做法:
$options = json_decode($options);
$hello = new Hello();

if(isset($options['message'])) $hello->setmessage($options['message']);
if(isset($options['person'])) $hello->setperson($options['person']);

这种模式有一个名称吗?您认为这是一种不好的实践吗?

我在示例中保留了验证等内容,以使其简单。


3
我不这么认为,Drupal CMS被广泛使用! - Y.H.
1
如果我没记错的话,JS语法不是被视为数组,而是作为对象的“序列化”表示(例如在JSON中经常使用)。 - Matteo Italia
@scoates:你确定吗?PHP对于“统一构造函数”有自己的定义:http://php.net/manual/en/language.oop5.decon.php - Chris Laplante
@Matteo:上面的代码不是一个对象的“序列化”表示,而是一个对象字面量。换句话说,它是对象本身。JSON 是完全不同的问题,它只是一种用于与服务器通信的字符串格式。 - slebetman
@slebetman:其实我有点知道它(我在JS代码中使用过几次),但作为一名主要的C++开发人员,我不知道它被称为这样;好知道,谢谢。 :) - Matteo Italia
显示剩余2条评论
7个回答

12

有好有坏。

好的一面:

  • 不需要多个方法签名(即重载,在支持的情况下)
  • 保持先前的观点:可以以任何顺序使用参数调用方法
  • 可以动态生成参数,而无需指定每个将出现的参数(例如:根据用户输入动态创建参数数组并将其传递给函数)
  • 不需要像setNamesetThissetThat这样的“模板”方法,尽管您可能仍然希望包括它们
  • 默认值可以在函数体中定义,而不是在签名中(jQuery经常使用这种模式。他们经常使用一个默认值数组来$.extend传递给方法的选项。在您的情况下,您将使用array_merge()

坏的一面:

  • 除非您正确地广告每个选项,否则您的类可能更难使用,因为很少有人知道支持哪些选项
  • 当您预先知道需要传递哪些参数时,需要创建参数数组的步骤更多了一步
  • 除非提供文档或他们有访问源代码的权限,否则用户不总是明显知道默认值的存在

在我看来,这是一种很好的技术。我最喜欢的方面是不需要提供具有不同签名的重载方法,而且签名并不是固定的。


4
指出正确记录文档的必要性加0.5分。同时提到优缺点加0.5分。 - Oswald
好与坏的阵容都很强大!我想要实现这个项目,它有良好的文档,所以不应该会有任何问题。 - DADU

3

这种方法没有问题,特别是当你需要传递许多参数给构造函数时。这还允许你为它们设置默认值,并在构造函数内使用array_merge()(有点像所有jQuery插件都做的那样)。

protected $default_params = array(
    'option1' => 'default_value'
);
public function __construct($params = array()) {
    $this->params = array_merge($this->default_params, $params);
}

如果您想要此“模式”的实时示例,请查看Symfony框架,他们几乎在每个地方都使用它:这里是sfValidatorBase构造函数的示例


3
当您给参数命名时,它被称为“命名表示法”,而不是“位置表示法”,在后者中,参数必须按特定顺序排列。
在PHP中,您可以传递一个“options”参数,以实现与其他语言(如Python)相同的效果,即可以使用真正的命名表示法。这不是一种坏习惯,但通常只有在有充分理由的情况下才会这样做(例如在您的示例或存在许多参数且它们不需要按任何特定顺序设置的情况下)。

1
在不支持命名参数的语言中,这是一种获取命名参数的技巧,如果提到这一点,可以得到加分。直到接触到这个技巧,很少有人知道命名参数的存在。 - slebetman

1
如果有必填选项,它们应该在构造函数的参数列表中。然后您可以添加具有默认值的可选选项。
public function __construc($mandatory1, $mandatory2, $optional1="value", $optional2="value") { }

如果您的所有选项都是可选的,那么创建一个接受数组的构造函数会很有用。使用“普通构造函数”创建对象比较困难:您只需提供所需的选项即可,而使用“普通构造函数”,如果要提供 $optional2,则必须提供 $optional1(甚至将其设置为默认值)。

1

我不会说这是不好的做法,至少如果你信任数据来源的话。

另一个可能性是根据选项数组键动态调用setter,如下所示:

public function __construct($options) {
    foreach($options as $option => $value) {
        $method = 'set'.$option;
        if(method_exists($this, $method)
            call_user_func(array($this, $method, $value);
    }
}

1

我不知道具体的名称,但我真的怀疑这是一种不好的做法,因为通常在想要声明一个小的或快速函数或类属性时使用它。


1
为什么不两者都做呢?使用静态工厂“命名构造函数”既可以有你的构造函数蛋糕,也可以吃掉它:
$newHello = Hello::createFromArray($options); 首先按顺序使用选项创建您的构造函数。然后在同一类中添加这样的静态方法:
public static function createFromArray($options){

    $a = isset($options['a']) ? $options['a'] : NULL;
    $b = isset($options['b']) ? $options['b'] : NULL;
    $c = isset($options['c']) ? $options['c'] : NULL;

    return new Hello($a, $b, $c);
} 

这将使新开发者和IDE感到满意,因为他们仍然可以看到构建对象所需的内容。
我同意这里答案的一般态度,即根据您的需求和哪种方案对您的应用程序更有益,两种方法都是可行的解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接