3

我已经使用 php 一段时间了,我一直想知道在类中存储属性的最佳方式是什么。

第一种方法是将数据存储为属性。

class Foo{
    private $id;
    private $name;

    function __get($var){
        return $this->$var;
    }
}

另一种方法是将数据存储在一个数组中,然后使用魔术方法将其作为类中的常规变量检索。

class Bar{
    private $data;

    function __get($var){
        return $this->data[$var];
    }
}

既然这两种方法都会达到相同的目标,那么哪一种更好,为什么?

4

2 回答 2

3

这完全取决于您要达到的目标。如果您希望实现任何接口,将所有属性存储在(关联)数组中将使您的生活更轻松Traversable,尽管也有一些方法可以使用预定义的属性来做到这一点。
如果最好,您的意思是最快的:预先定义每个属性将使您的类更高效,正如我已经解释过
的那样 当然,还有很多原因说明第一种方法更好,我在这里列出了它们

基本上,您需要知道的是 PHP 的对象“重载”很慢(声明属性为O( 1 ),重载属性为O(n))。魔术方法也是如此,例如__getand __set。他们很慢。考虑一下:

class Declared
{
    private $foo = 123;
    public function setFoo($val)
    {
        $this->foo = (int) $val;
        return $this;
    }
    public function getFoo()
    {
        return $this->foo;
    }
    public function __get($name)
    {
        $name = 'get'.ucfirst($name);
        if (method_exists($this, $name))
        {
            return $this->{$name}():
        }
        //return null or throw exception
        throw new RuntimeExcpetion('attempted to access non-existing property using '.$name.' in '.__METHOD__);
    }
    public function __set($name, $val)
    {
        $mname = 'set'.ucfirst($name);
        if (method_exists($this, $mname))
        {
            return $this->{$mname}($val):
        }
        throw new RuntimeException($name.' is an invalid property name');
    }
}

我不仅可以确定在任何时候都不会添加新属性,而且因为我使用的是自定义 getter 和 setter,我还可以确保每个属性的类型都是我想要/期望的。在这个例子中,我需要$foo是一个整数,所以在 setter 中,我将作为参数传递的任何值转换为 int。您也可以使用魔术__set方法来做到这一点,但是您处理的属性越多,该方法就会变得越混乱(大量的 if 和 else)。

现在,将其与此类进行比较:

class NotDeclared
{
    private $data = array('foo' => 123);
    public function __get($name)
    {
        return isset($this->data[$name]) ? $this->data[$name] : null;
    }
    public function __set($name, $value)
    {
        $this->[$data] = $value;
    }
}

现在,这门课看起来确实短了很多,所以我明白为什么这会吸引任何人。然而,让我们在使用方面进行比较:

$declared = new Declared;
$notDeclared = new NotDeclared;
echo $declared->foo;//ok
echo $declared->getFoo();//ok
echo $notDeclared->foo;//ok
$declared->foo = 34;//fine
$declared->setFoo($declared->foo*2);//just fine
echo $declared->fo;//TYPO => exception
echo $notDeclared->fo;//no errors show, but echoes nothing: invisible bug?
//Worse, still: 2 mistakes in one go
$notDeclared->fo = 'Typo, meant foo, but assign string to expected integer property';
$declared->fo = 'asd';//throws excpetion at once
//singe $notDeclared->fo didn't throw excpetions, I'm assuming I reassigned $foo, yet
echo $notDeclared->foo;//ecoes old value

因为我__set通过在用户尝试分配不存在的属性时抛出异常来限制 的使用,所以任何拼写错误都会立即抛出异常。如果你不这样做,你可能最终不得不回溯你的对象的一个​​实例,以便发现一个愚蠢的错字,就像我在这里展示的例子一样fo

更重要的是:您必须考虑何时调用魔术方法:

$instance->newProperty;

幕后发生了什么?

  • 检查对象实例是否有一个名为的公共属性newProperty
  • 检查被调用方法的对象newProperty(语法错误)
  • 检查对象的__get方法
  • 调用__get方法
    1. 查找数据属性 ( $this->data)
    2. 检查数组的newProperty键 ( isset)
    3. 三进制的返回结果(即null)

这意味着$instance->newProperty需要 PHP 查找newPropery,然后查找__get方法,$data然后查找属性,然后查找该newPropery数组中的键,然后返回 null 或值。这是对一个对象的大量搜索。
这就是为什么我总是推荐第一个选项。

于 2013-09-27T07:16:44.370 回答
0

这更像是一个面向对象的范式问题。

魔术 get 函数更多地用作编写 getter 的一种懒惰方式。因此,您不必为每个成员编写 getter。

另一种方法是为每个函数编写一个 getter,例如get_name()get_id()

第二种方法并没有真正的可比性,因为您所做的只是将所有成员放入一个数组中。这无疑会慢一些,因为在某种程度上 php 必须在您调用时遍历您的结构以找到适当的键$this->data[$var]

总之。顶级方法更好,因为它更正确地模拟了您的类应该表示的内容。第二个失去了类的意义,如果你唯一的成员是一个关联数组,为什么还要有一个类呢?

于 2013-09-27T07:02:42.633 回答