【问题标题】:constructor versus too freaking many gets and sets构造函数与太多的获取和设置
【发布时间】:2011-10-09 01:02:40
【问题描述】:

我有一个包含 18 个属性的记录类。

在将该类提交到数据库之前,所有 18 个属性都必须具有经过验证的数据。

因为我正在 OOP 化一个工作的过程 web 应用程序,所以我进行了这种倒退。

首先,我介绍了修改现有记录的工作流程。当时,将所有 18 个属性都放入 __construct 方法并避免大量的 setter 是有意义的。一个单独的loader 类处理 dbase 业务,可以返回单个对象或记录对象数组。一切正常。

但是是时候解决新的记录创建工作流了,突然我需要实例化一个空记录,除了我的记录构造函数是一个需要 18 个参数的饥饿野兽... p>

...所以你剥离了构造函数?但是,每次我想使用现有记录时,我都必须添加 18 个 setter 并调用它们...

似乎没有太大的改进! :-/

真正的程序员如何处理这个问题? (我只是个小爱好者……)

【问题讨论】:

  • 如果你使用魔法方法,你不需要 18 个不同的 setter。您甚至可以拥有一个空对象,并在您想要设置全部或一次设置一些属性时保持它非常整洁。看看我的回答,并根据需要进行扩展。

标签: php oop class constructor


【解决方案1】:

任一默认参数都是一个选项,但如果您只想使用第一个和最后一个,则必须填写大量空值。

再一次,你可以做数组循环:

private $prop1;
private $prop2;
// more properties here.

function __construct( array $props ) // `array` here is for type-hinting.
{
    foreach( array( 'prop1', 'prop2' /*, all of the props for this object */
             as $property )
    {
        // basically, this will assign all of the properties as they exist in the
        // props array
        if( isset( $props[ $property ] ) )
            $this->$property = $props[ $property ];
    }
}

或者,如果您想保留旧的构造函数签名:

function __construct( $prop1, $prop2 = NULL, $prop3 = NULL /* ... */ ) 
{
    if( is_array( $prop1 ) )
    {
         $this->array_prop_assignment( $prop1 );
    }
    else
    {
        $args = func_get_args();
        // this ensures that anything which is passed to the constructor
        // will go to the "new" old constructor
        call_user_func_array( array( $this, 'param_prop_assignment' ), $args );
    }
}

function param_prop_assignment( $prop1, $prop2 /* ... */ )
{
    //your old constructor can go here.
}

function array_prop_assignment( array $props )
{
    // foreach example above would go here.
}

新版本还为您提供了以下选项:

$k = new DataClass(); // no idea what the real class name is.
$k->param_prop_assignment( 1, 2, 3 /* ... */ );

【讨论】:

  • 这看起来很聪明,但它基本上不会管任何 IDE 提示吗?用户必须了解该类的内部和外部才能正确实例化它...
  • 是的,会的。我提供了一种可以提供帮助的替代方法。原来提交后才想到这个
【解决方案2】:

在理想情况下,您的对象将允许您使用构造函数或设置器 指定值。您可以做的简化事情是提供一个构造函数,它只接受 18 个值的子集,并将其余值设置为默认值。但这假设存在有意义的默认值,但情况可能并非如此。

【讨论】:

  • 我不会有一个看起来像这样的构造函数:__construct($param = '', $param1 = '', $param2 = '', + 15 more) 吗?还是我误会了你?
  • 也许吧。或者您可以将单个数组传递给它,就像 cwallenpoole 建议的那样。
【解决方案3】:

如果您有一个对象的很多属性并且您不想将它们全部包含在构造函数中,您可以使用 setter 返回对象本身以使代码更具可读性:

MyClass obj = (new MyClass())
    .setName(name)
    .setLocation(location)
    ...
    .setPhone(phone)

同理,如果在拥有有效对象之前需要验证所有值,则可以使用构建器对象来做同样的事情。基本上,您创建一个构建器对象,设置值,然后告诉构建器创建实际对象。然后,它可以在构造对象的实际实例之前对所有值进行验证(包括使用多个字段的验证)。

MyClass obj = (new MyClassFactory())
    .setName(name)
    .setLocation(location)
    ...
    .setPhone(phone)
    .construct();

【讨论】:

    【解决方案4】:

    您可以将它们链接到构造函数。你在哪里做这个...

    $Record = Record()->Name('Mark')->Location('A-Town, NY')->Phone('123-345-6789');

    您可以通过创建一个与您的类同名的函数来返回您的类的新实例。

    function Record() {
        return new Record;
    }
    
    class Record {
        private $Name;
        private $Location;
        private $Phone;
    
        public function __get($property) {
            return (isset($this->$property)) ? $this->$property : FALSE;
        }
    
        public function &__call($property, $arguments)
        {
            if (property_exists($this, $property))
                $this->$property = array_shift($arguments);
            return $this;
        }
    }
    
    $FilledRecord = Record()->Name('Mark')->Location('A-Town')->Phone('123-456-7890');
    $EmptyRecord = Record();
    
    print_r($FilledRecord);
    print_r($EmptyRecord);
    

    如果您需要验证一些数据,您可以稍后添加该功能。

    【讨论】:

    • 您在课堂上正确使用了 __get 和 __set,但您的示例: Record()->Name('Mark')->Location('A-Town, NY')->Phone ('123-345-6789');根本行不通。看起来您可能想要__call($meth,$args){ if( !count( $args ) ) return $this->$meth; else $this->$meth = $args[ 0 ]; return $this;} 这将使$this->Name(); 返回名称,$this->Name("name")->Phone(); 设置名称并返回电话号码。另外,不要使用函数的名称与类的名称相同。它有危险的不稳定风险(如果它运行的话)。
    • @cwallenpoole,你说得对,我忘了我必须使用 __call 魔术方法来做到这一点。感谢代码审查! (我已经在我的名为 PRISM 的项目中完成了这个,这就是我首先实现这个想法的地方。)
    • 我摆脱了 -1,但说真的......拥有一个函数和一个同名的类充其量是令人迷惑的,如果需要任何形式的反射,它确实会引发问题(尽管它不一定保证它们)。
    • 我听很多人说new 关键字是语法噪音,但这是为什么它存在的重要部分。 new 关键字告诉解析器和程序员我在做什么。如果我在语句之前没有 new 关键字,那么我正在调用该类,如果我没有,那是因为我正在调用一个函数。我觉得只要你小心一点就可以了。我在生产环境中使用它,并且没有报告任何问题(世界各地许多开发人员使用的代码,各种技能水平)。我也不觉得这会影响反射。
    • 另外——在 __call 和 __get 之前你不需要&(如果你试图访问一个不存在的属性,它实际上会导致 __get 出现问题——FALSE 不可能绑定到引用)。
    【解决方案5】:

    在任何类型的函数中填充 18 个参数(包括构造函数)都是不好的。从现在开始几个月甚至几天后,当您查看代码时,您将永远不会记住正确的顺序。此外,正如您所经历的那样,当您需要扩展课程时,这很困难。

    这就是为什么我通常更喜欢有 getter 和 setter 的类。是的,它需要更多的输入,但是使用 getter 和 setter 用户可以轻松查看他们可以获取和设置的属性,而且 getter 和 setter 是 IDE 的自动完成友好型。

    现在,下一个问题是您不想在从数据库中读取现有记录时一直调用 setter?你说你有一个Loader 类,你不能把所有的调用集中在Loder 类中吗?

    class Loader{
      public function getMyObject(){
        $dbData = $this->getDataFromDB();
        $myObj = $this->createMyObjectFromDbData($dbData);  
        return $myObj;
      }
    
      private createMyObjectFromDbData($dbData){
        $myObj = new MyObject();
        /* 18 setter calls */
    
        return $myObj;
      }
    }
    

    因此,当您想使用现有技术时,只需致电 Loader.getMyObject();

    如果您不想在createMyObjectFromDbData 中输入所有 18 个 setter 调用,那么只要您的 setter 遵循一些命名约定,您就可以这样做

    for ($dbData as $columnName => $columnValue){
      $propertyName = $columnName;
      $propertyValue = $columnValue;
      $myObj->set{$columnName}($propertyValue); /* you can't do this in java */
    }
    

    您可能还想添加一个validate 方法来验证对象中的所有属性,这样您就可以在提交它以插入数据库之前调用该方法。

    【讨论】:

      猜你喜欢
      • 2013-03-01
      • 1970-01-01
      • 2017-04-04
      • 2023-01-14
      • 1970-01-01
      • 2011-04-11
      • 2020-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多