40

(此问题使用 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同样会导致错误,这是真的。然而,有一个正式的课程对我来说感觉更僵硬,但我不能真正证明它是合理的。

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

4

11 回答 11

32

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

另外,请查看以下示例:

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

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

另一方面,

$class->setNaem('John');

永远不会工作。

于 2010-01-13T13:41:46.737 回答
30

像这样一个简单的类:

class PersonalData {
    protected $firstname;
    protected $lastname;

    // Getters/setters here
}

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

  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());
    }
    
  3. 我们可以在设置/获取操作之前添加一些额外的代码,例如验证或日志记录:

    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;
    
    }
  4. 我们可以使用 OOP 的所有好处,如继承、多态、类型提示、封装等等……
  5. 如前所述,我们所有的“结构”都可以继承自一些为或接口提供实现的基类Countable,因此我们的结构可以使用循环等。SerializableIteratorforeach
  6. IDE 支持。

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

于 2010-10-05T10:44:56.417 回答
8

经过一段时间的思考,这是我自己的答案。

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

考虑这个函数:

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

相对

function MagicFunction(array $fred)
{
}

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

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

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

class Fred
{
    public $name;
    // ...
}

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

// IMPORTANT! You need to specify 'name' and 'age'
function MagicFunction(array $fred)
{
}
于 2010-10-05T12:17:02.657 回答
6

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

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

于 2010-01-13T13:58:41.817 回答
3

让我向你提出这个问题:

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

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

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

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

$class = new stdClass();
$class->param = 'value';
$class->param2 = 'value2';
return $class;
于 2010-01-13T13:43:31.190 回答
2

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

于 2010-01-13T13:45:15.510 回答
1

正确值对象的好处是没有办法实际制作无效的值对象,也无法更改存在的值对象(完整性和“不变性”)。只有 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;
}
于 2011-04-16T03:41:34.740 回答
1

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

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

另一个认为您对关联数组松散的想法是您不能使用智能感知。

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

于 2012-01-29T19:02:10.450 回答
1

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

于 2010-01-13T13:40:38.343 回答
1

老实说,我喜欢他们两个。

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

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

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

于 2010-10-05T10:20:59.787 回答
0

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

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

于 2010-01-13T13:40:10.713 回答