我有一个记录类,有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();