PHP中的值对象与关联数组

Posted

技术标签:

【中文标题】PHP中的值对象与关联数组【英文标题】:Value objects vs associative arrays in PHP 【发布时间】:2011-01-04 15:25:56 【问题描述】:

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

让我们看一下这个例子(PHP):

function makeAFredUsingAssoc()

    return array(
        'id'=>1337,
        'height'=>137,
        'name'=>"Green Fred");

对比:

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

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

$myFred = makeAFredUsingAssoc();
return $myFred['naem']; // notice teh typo here

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

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

【问题讨论】:

【参考方案1】:

我使用 OOP 语言已经超过 10 年了。 如果您了解对象的工作方式,您会喜欢它。 继承、多态、封装、重载是OOP的关键优势。 另一方面,当我们谈论 PHP 时,我们必须考虑到 PHP 并不是一种功能齐全的面向对象语言。 例如,我们不能使用方法重载或构造函数重载(直截了当)。

PHP 中的关联数组是一个非常好的特性,但我认为这会损害 php 企业应用程序。 当您编写代码时,您希望获得干净且可维护的应用程序。

您对关联数组的另一种看法是您不能使用智能感知。

所以我认为如果你想编写更简洁和更易于维护的代码,你必须在提供 OOP 功能时使用它。

【讨论】:

【参考方案2】:

正确的值对象的好处是,没有办法实际制作无效的值对象,也无法更改存在的值对象(完整性和“不变性”)。只有 getter 和类型提示参数,没有办法在可编译的代码中搞砸,显然你可以很容易地使用可延展数组来做到这一点。

或者,您可以在公共构造函数中进行验证并引发异常,但这提供了一种更温和的工厂方法。

class Color

    public static function create($name, $rgb) 
        // validate both
        if ($bothValid) 
            return new self($name, $rgb);
         else 
            return false;
        
    
    public function getName()  return $this->_name; 
    public function getRgb()  return $this->_rgb; 

    protected function __construct($name, $rgb)
    
        $this->_name = $name;
        $this->_rgb = $rgb;
    
    protected $_name;
    protected $_rgb;

【讨论】:

【参考方案3】:

想了很久,我自己的回答。

优先考虑值对象而不是数组的主要特点是清晰

考虑这个函数:

// Yes, you can specify parameter types in PHP
function MagicFunction(Fred $fred)

    // ...

function MagicFunction(array $fred)


意图更清晰。函数作者可以强制执行他的要求。

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

调用者和被调用者之间存在契约。使用值对象,这个合约可以写成经过语法检查的代码:

class Fred

    public $name;
    // ...

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

// IMPORTANT! You need to specify 'name' and 'age'
function MagicFunction(array $fred)


【讨论】:

【参考方案4】:

像这样一个简单的类:

class PersonalData 
    protected $firstname;
    protected $lastname;

    // Getters/setters here

与数组相比几乎没有优势。

    不可能出现一些错别字。 $data['firtsname'] = 'Chris'; 将起作用,而 $data->setFirtsname('Chris'); 将引发错误。

    类型提示: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());
    
    

    我们可以在设置/获取操作之前添加一些额外的代码,例如验证或日志记录:

    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;
    
    我们可以利用 OOP 的所有好处,例如继承、多态性、类型提示、封装等等...... 如前所述,我们所有的“结构”都可以从提供CountableSerializableIterator 接口实现的基类继承,因此我们的结构可以使用foreach 循环等。 IDE 支持。

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

【讨论】:

我特别同意#2。它启发了我在下面的答案。 我认为使用数组可以更好地完成“验证和记录”,因为您可能需要一起验证事物而不是单独验证。【参考方案5】:

老实说,我都喜欢他们。

哈希数组比创建对象要快得多,而且时间就是金钱! 但是,JSON 不喜欢哈希数组(这看起来有点像 OOP OCD)。 也许对于多人项目,定义明确的类会更好。 哈希数组可能会占用更多 CPU 时间和内存(对象具有预定义的数量),但很难确定每种情况都适用。

但真正糟糕的是考虑过多使用哪一个。就像我说的,JSON 不喜欢哈希。糟糕,我使用了一个数组。我现在得修改几千行代码。

我不喜欢它,但似乎上课是更安全的方式。

【讨论】:

json_encode(array) 有什么问题?【参考方案6】:

根据用例,我可能会使用或。该类的优点是我可以像使用类型一样使用它,并在方法或任何自省方法上使用类型提示。如果我只想从查询或其他东西中传递一些随机数据集,我可能会使用数组。所以我想只要 Fred 在我的模型中有特殊含义,我就会使用一个类。

附注: ValueObjects 应该是不可变的。至少如果您参考 Eric Evan 在领域驱动设计中的定义。在 Fowler 的 PoEA 中,ValueObjects 不一定必须是不可变的(尽管有人建议),但它们不应该具有身份,这显然是 Fred 的情况。

【讨论】:

【参考方案7】:

让我向你提出这个问题:

$myFred['naem'] 这样的错字和像$myFred->naem 这样的错字有什么不同?在这两种情况下仍然存在相同的问题,并且它们都错误。

我喜欢在编程时使用KISS(保持简单,愚蠢)。

如果您只是从方法返回查询的子集,只需返回一个数组。 如果您将数据作为 public/private/static/protected 变量存储在您的某个类中,最好将其存储为 stdClass。 如果您稍后将其传递给另一个类方法,您可能更喜欢Fred 类的严格类型,即public function acceptsClass(Fred $fredObj)

如果要将其用作返回值,则可以像创建数组一样轻松地创建一个标准类。在这种情况下,您可以不太关心严格类型。

$class = new stdClass();
$class->param = 'value';
$class->param2 = 'value2';
return $class;

【讨论】:

任何人都喜欢使用 stdClass 而不是 assoc 的任何原因。大批?它们的功能在我看来几乎相同。副会。数组语法更加清晰和简单。 @kizz - 主要是当您需要存储对象数组以便于迭代时,数据库抽象层如何返回对象数组(每个对象代表一行)。 一个stdClass can be iterated over just like an array。在关联数组上使用stdClass 没有直接价值,您将无法访问所有酷炫的array functions。是的,访问公共 property 时仍然可能发生拼写错误,但其他示例使用公共 methods,如果出现拼写错误,则无法正确编译。如果你打算使用一个值对象,你至少应该以不变性为目标。【参考方案8】:

哈希专家:它能够处理设计时未知的名称-值组合。

【讨论】:

上课也可以。如果您只是实例化一个类,然后执行类似 $class->madeup = 'hello'; 之类的操作回声$类->化妆;它会回显 'hello' :) (当然,当使用 php 类时,这在其他语言上不确定) 嗯...你是对的。这将在所有允许在运行时修改类的动态语言中得到支持。有使用哈希的专业人士......【参考方案9】:

表面上,这两种方法是等价的。但是,在使用类时,您可以获得大多数标准 OO 好处:封装、继承等。

另外,请看以下示例:

$arr['naem'] = 'John';

完全有效,可能是一个很难找到的错误。

另一方面,

$class->setNaem('John');

永远不会工作。

【讨论】:

+1。一旦你摆脱了公共成员变量,类的优势就变得明显了。 我很好奇有人扩展了这个答案,这也与编写可测试代码 (PHPUnit)、使用其中一个的优点/缺点有关?【参考方案10】:

当返回值代表应用程序中的实体时,您应该使用对象,因为这是 OOP 的目的。如果您只想返回一组不相关的值,那么它就不是那么明确了。但是,如果它是公共 API 的一部分,那么声明的类仍然是最好的方法。

【讨论】:

我非常努力地尝试并没有想到我想要返回一组不相关值的实际场景。也许在读取 CSV 时,但 PHP 的 fgetcsv() 无论如何都会给出一个数字数组,所以...【参考方案11】:

我更喜欢像您的第二个示例中那样具有硬编码属性。我觉得它更清楚地定义了预期的类结构(以及类上所有可能的属性)。与第一个示例相反,它归结为始终记住使用相同的键名。有了第二个,您总是可以返回并查看类,只需查看文件顶部即可了解属性。

你会更好地知道你在第二个中做错了——如果你尝试echo $this->doesntExist,你会得到一个错误,而如果你尝试echo array['doesntExist'],你不会。

【讨论】:

其实你会得到一个NOTICE of PHP Notice: Undefined index: doesntExist 抱歉,对,您会收到通知,但有时这比页面彻底中断要难得多。这可能会被忽视一段时间(没有双关语)。

以上是关于PHP中的值对象与关联数组的主要内容,如果未能解决你的问题,请参考以下文章

PHP数组遍历详解

PHP数组简介

PHP数组简介

php:如何通过键更新关联数组中的值

PHP 数组中的每个ID键关联一个值,可不可以关联两个值?

与 PHP 中的数值数组相关联