构造函数相对于太多的get和set


constructor versus too freaking many gets and sets

我有一个记录类,有18个属性。

在该类可以提交给数据库之前,所有18个属性必须具有验证过的数据。

因为我是OOP-ifying一个工作过程的web应用程序,我做了这种反向。

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

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

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

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

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

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

然后,你可以做数组循环:

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 /* ... */ );

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

这就是为什么我通常更喜欢有getter和setter的类。是的,它需要更多的输入,但有了getter和setter,用户可以很容易地看到他们可以获取和设置哪些属性,而且getter和setter对IDE的自动完成很友好。

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

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方法来验证对象中的所有属性,以便您可以在将其插入数据库之前调用该方法。

可以将它们链接到构造函数。

$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);

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

在理想的情况下,对象应该允许您使用构造函数或setter来指定值。为了简化操作,您可以提供一个构造函数,该构造函数只接受18个值中的一个子集,并将其余的值设置为默认值。但前提是存在有意义的默认值,而实际情况可能并非如此。

如果一个对象有很多属性,而你又不想把它们都包含在构造函数中,你可以使用setter返回对象本身,使代码更容易读:

MyClass obj = (new MyClass())
    .setName(name)
    .setLocation(location)
    ...
    .setPhone(phone)
同样,如果在获得有效对象之前需要验证所有值,则可以使用构建器对象来执行相同的操作。基本上,您创建一个构建器对象,设置值,然后告诉构建器创建实际的对象。然后,它可以在构造对象的实际实例之前对所有值进行验证(包括使用多个字段的验证)。
MyClass obj = (new MyClassFactory())
    .setName(name)
    .setLocation(location)
    ...
    .setPhone(phone)
    .construct();