PHP中的值对象与关联数组

kiz*_*zx2 38 php associative-array value-objects

(这个问题使用PHP作为上下文,但不仅限于PHP.例如,任何内置哈希的语言也是相关的)

我们来看看这个例子(PHP):

function makeAFredUsingAssoc()
{
    return array(
        'id'=>1337,
        'height'=>137,
        'name'=>"Green Fred");
}
Run Code Online (Sandbox Code Playgroud)

与:

class Fred
{
    public $id;
    public $height;
    public $name;

    public function __construct($id, $height, $name)
    {
        $this->id = $id;
        $this->height = $height;
        $this->name = $name;
    }
}

function makeAFredUsingValueObject()
{
    return new Fred(1337, 137, "Green Fred");
}
Run Code Online (Sandbox Code Playgroud)

方法#1当然更简洁,但它可能很容易导致错误,例如

$myFred = makeAFredUsingAssoc();
return $myFred['naem']; // notice teh typo here
Run Code Online (Sandbox Code Playgroud)

当然,有人可能会争辩说$myFred->naem同样会导致错误,这是事实.然而,正式课程对我来说感觉更加僵硬,但我无法证明这一点.

使用每种方法的优点/缺点是什么?人们何时应该使用哪种方法?

Cro*_*zin 29

像这样一个简单的类:

class PersonalData {
    protected $firstname;
    protected $lastname;

    // Getters/setters here
}
Run Code Online (Sandbox Code Playgroud)

与阵列相比几乎没有什么优势.

  1. 没有可能做出一些错别字.$data['firtsname'] = 'Chris';将会工作,同时$data->setFirtsname('Chris');会抛出错误.
  2. 类型提示:PHP数组可以包含所有内容(包括任何内容),而定义良好的类只包含指定的数据.

    public function doSth(array $personalData) {
        $this->doSthElse($personalData['firstname']); // What if "firstname" index doesn't exist?
    }
    
    
    public function doSth(PersonalData $personalData) {
        // I am guaranteed that following method exists. 
        // In worst case it will return NULL or some default value
        $this->doSthElse($personalData->getFirstname());
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我们可以在set/get操作之前添加一些额外的代码,比如验证或记录:

    public function setFirstname($firstname) {
        if (/* doesn't match "firstname" regular expression */) {
            throw new InvalidArgumentException('blah blah blah');
        }
    
    
    
    if (/* in debbug mode */) {
        log('Firstname set to: ' . $firstname);
    }
    
    
    $this->firstname = $firstname;
    
    Run Code Online (Sandbox Code Playgroud) }
  4. 我们可以使用OOP的所有好处,如继承,多态,类型提示,封装等......
  5. 至于我们所有的"结构"的前面提到的可以从一些基础类,为提供实现继承Countable,SerializableIterator接口,所以我们的结构可以使用foreach循环等.
  6. IDE支持.

唯一的缺点似乎是速度.创建阵列并对其进行操作更快.但是我们都知道,在许多情况下,CPU时间比程序员时间便宜得多.;)


Zac*_*ese 28

在表面下,这两种方法是等价的.但是,在使用类时,您可以获得大多数标准OO优势:封装,继承等.

另外,请看以下示例:

$arr['naem'] = 'John';
Run Code Online (Sandbox Code Playgroud)

是完全有效的,可能是一个难以找到的bug.

另一方面,

$class->setNaem('John');
Run Code Online (Sandbox Code Playgroud)

永远不会工作.

  • +1.一旦摆脱了公共成员变量,该类的优势就变得清晰了. (6认同)
  • 我很好奇有人扩展这个答案,这与编写可测试代码(PHPUnit),使用一个优于另一个的优点/缺点有什么关系? (2认同)

kiz*_*zx2 8

在考虑了一段时间之后,这是我自己的答案.

关于在数组上优选值对象的主要原因是清晰度.

考虑这个功能:

// Yes, you can specify parameter types in PHP
function MagicFunction(Fred $fred)
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

function MagicFunction(array $fred)
{
}
Run Code Online (Sandbox Code Playgroud)

意图更清晰.功能作者可以执行他的要求.

更重要的是,作为用户,我可以轻松查找构成有效Fred的内容.我只需要打开Fred.php并发现它的内部结构.

呼叫者和被呼叫者之间存在合同.使用值对象,此契约可以编写为语法检查代码:

class Fred
{
    public $name;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

如果我使用数组,我只希望我的用户会阅读评论或文档:

// IMPORTANT! You need to specify 'name' and 'age'
function MagicFunction(array $fred)
{
}
Run Code Online (Sandbox Code Playgroud)


Gor*_*don 6

根据UseCase,我可能会使用或.该类的优点是我可以像使用Type一样使用它,并在方法或任何内省方法上使用Type Hints.如果我只是想从查询或其他东西传递一些随机数据集,我可能会使用该数组.所以我想只要弗雷德在我的模型中有特殊意义,我就会使用一个类.

旁注:
ValueObjects应该是不可变的.至少如果您在域驱动设计中引用Eric Evan的定义.在Fowler的PoEA中,ValueObjects不一定必须是不可变的(虽然它是建议的),但它们不应该具有身份,Fred显然就是这种情况.