3

想象一下你有这门课

class Ai1ec_Less_Parser_Controller {
    /**
     * @var Ai1ec_Read_Variables_Startegy
     */
    private $read_variable_strategy;
    /**
     * @var Ai1ec_Save_Variables_Strategy
     */
    private $write_variable_strategy;
    /**
     * @var Ai1ec_Less_Variables_Collection
     */
    private $less_variables_collection;
    /**
     * @var Ai1ec_Less_Parser
     */
    private $ai1ec_less_parser;
    /**
     * We set the private variables in the constructor. I feel that there are too many parameters. 
     * Should i use setter instead and throw an exception if something is not set?
     * 
     * @param Ai1ec_Read_Variables_Startegy $read_variable_strategy
     * @param Ai1ec_Save_Variables_Strategy $write_variable_strategy
     * @param Ai1ec_Less_Variables_Collection $less_variables_collection
     * @param Ai1ec_Less_Parser $ai1ec_less_parser
     */
    public function __construct( Ai1ec_Read_Variables_Startegy $read_variable_strategy,
                                 Ai1ec_Save_Variables_Strategy $write_variable_strategy,
                                 Ai1ec_Less_Variables_Collection $less_variables_collection,
                                 Ai1ec_Less_Parser $ai1ec_less_parser ) {

    }
}

我需要设置这些变量,所以我在构造函数中设置它们(但看起来参数太多)。另一种选择是使用 setter 来设置它们,然后在一个方法中如果没有像这样设置所需的变量之一,则抛出异常

public function do_something_with_parser_and_read_strategy() {
  if( $this->are_paser_and_read_strategy_set === false ) {
     throw new Exception( "You must set them!" );
  }
}  
private function are_paser_and_read_strategy_set () {
  return isset( $this->read_variable_strategy ) && isset( $this->ai1ec_less_parser );
} 

你认为这两种方法中的一种更好吗?为什么?

4

6 回答 6

2

你的类是不可变的吗?如果是这样,那么通过构造函数拥有 100% 的成员人口通常是最好的方法,但我同意如果你有超过 5 或 6 个参数,它可能会开始看起来很难看。

如果您的类是可变的,那么使用带有必需参数的构造函数没有任何好处。通过访问器/修改器方法(也称为属性)公开成员。

工厂模式(如@Ray 所建议的那样)可以提供帮助,但前提是您有各种类似的类 - 对于一次性然后您可以简单地使用静态方法来实例化对象,但您仍然会有“太许多参数”的问题。

最后一种选择是接受一个带有字段的对象(每个参数一个字段),但要小心使用这种技术——如果某些值是可选的,那么只需使用方法重载(不幸的是 PHP 不支持)。

我会坚持你正在做的事情,只有在出现问题时才将其更改为其他内容。

于 2012-07-17T00:12:35.207 回答
1

类命名 Controller 以某种方式反映了MVC,或者一般来说 - 任何负责处理顺序的机制。

无论如何,数据对象类往往有很多字段——这是他们的责任。依赖于许多其他对象的常规对象可能会丢失一点。

正如我所见,有四个对象:读取、保存、解析和提供集合接口。为什么一个人有不同的读写接口?这不能合二为一吗?Parser 本身就是一个库,因此可能没有理由在任何地方组合它,尽管它可以自己使用 reader/writers,并作为回报提供集合。因此,解析器有可能接受 reader 的参数并返回一个集合对象吗?

那更多的是关于具体情况。一般来说 - 对方法有许多参数(或由不同域的其他对象初始化对象内的许多字段)表明存在某种设计缺陷。

主题可能是这篇关于构造函数初始化的文章——它建议使用构造函数内初始化。请务必跟进重点:

如果在构造函数中有很多协作者要提供怎么办?与任何大型参数列表一样,大型构造参数列表是CodeSmell

正如Ray所写的那样 - 可以使用 setter 进行初始化,并且也有相关文章。就我的观点而言——我认为 Martin Fowler 确实很好地总结了这些案例。

于 2012-07-18T09:00:41.170 回答
1

没有“更好”的方法。但这里有几件事情你必须考虑:

  • 构造函数不被继承
  • 如果类需要太多的对象,它负责太多

这可能会影响您选择类实现的接口类型。

一般的经验法则是这样的:

如果参数对于类的功能是必需的,则应通过构造函数注入。

例外情况是,如果您使用工厂初始化实例。工厂从不同的类构建实例是很常见的,其中一些实现相同的接口和/或扩展相同的父类。那么通过setter注入共享对象就更容易了。

于 2012-07-18T18:29:43.743 回答
0

使用调用 setter 的工厂而不是使用一组参数的构造函数来创建对象要灵活得多。查看构建器和工厂模式。

访问未完全构建的对象时抛出异常是好的!

于 2012-07-17T00:10:30.043 回答
0

任何有超过 2 个(有时是 3 个)参数的函数,我总是传递一个数组,所以它看起来像:

public function __construct(array $options = array()) {

    // Figure out which ones you truly need
    if ((!isset($options['arg1'])) || (mb_strlen($options['arg1']) < 1)) {
        throw new Exception(sprintf('Invalid $options[arg1]: %s', serialize($options)));
    }

    // Optional would look like
    $this->member2 = (isset($options['arg1'])) && ((int) $options['arg2'] > 0)) ? $options['arg2'] : null;

    // Localize required params (already validated above)
    $this->member1 = $options['arg1'];
}

传递一系列选项允许未来的增长,而无需更改函数签名。但是它确实有它的缺点,因为该函数必须本地化数组的所有元素以确保访问不会引发警告/错误(如果数组中缺少元素)。

在这种情况下,工厂解决方案不是一个好的选择,因为您仍然面临将值传递给工厂的问题,以便它可以使用正确的值初始化对象。

于 2012-07-17T00:24:34.273 回答
0

“构造函数参数过多”的标准解决方案是构建器模式。您的控制器类本身仍将有一个长构造函数,但客户端可以在构建器上使用 setter,而后者随后将调用长构造函数。

如果你只在一个或两个地方构建你的控制器对象,那么创建一个构建器甚至都不值得。在这种情况下,只需坚持使用您当前的代码。

于 2012-07-18T09:25:44.660 回答