使用类getter还是类属性?(PHP)


use class getters or class properties? (PHP)

当涉及到在内部使用属性时,编写OOP类的最佳实践是什么?

考虑以下类;

<?php
Class Foo
{
    /**
     * @var null|string
     */
    protected $_foo;
    /**
     * @return null|string
     */
    public function getFoo()
    {
        return $this->_foo;
    }
    protected function _doSomething()
    {
        $foo    = $this->_foo;
        $result = null;
        // ...
        return $result;
    }
}

正如你所看到的,我在_doSomething()中使用属性_foo,尽管子类可以覆盖getFoo(),返回一个计算值,而不是存储回_foo;这是一个缺陷。

我该怎么办?

  1. 将getter标记为final,在内部使用属性(没有额外的函数调用,强制最终开发人员使用_foo作为属性,因为它是受保护的)
  2. 在内部使用getFoo(),将_foo标记为私有(额外的函数调用)

两个选项都是防水的,但是我非常担心所有额外的函数调用,所以我倾向于使用选项1,而选项2将更"真正的OOP"。

也可以阅读http://fabien.potencier.org/article/47/pragmatism-over-theory-protected-vs-private,它也建议选项2:/

另一个相关问题;如果一个属性有一个setter,那么这个属性应该是私有的,强制最终开发人员在子类中使用它,还是应该是一个不成文的规则,程序员负责设置一个有效的属性值?

第二种方法,正如您所说,是根据OOP更正确的方法。你也说对了,调用一个方法比访问一个属性作为变量要花费更多的CPU周期。然而,这在大多数情况下属于微优化的范畴。它不会对性能产生明显的影响,除非所讨论的值被大量使用(例如在循环的最内部部分)。最佳实践倾向于支持正确的而不是性能最好的,除非性能确实因此而受到影响。

对于简单的变量,在内部使用getter并不是很明显,但是如果您正在处理从外部数据源(如数据库)填充的属性,则该技术将发挥作用。使用getter允许您以惰性方式从DB中获取数据,即按需而不是在需要之前。例如:

class Foo
{
    // All non-relevent code omitted
    protected $data = NULL;
    public class getData ()
    {
        // Initialize the data property
        $this -> data = array ();
        // Populate the data property with a DB query
        $query = $this -> db -> prepare ('SELECT * FROM footable;');
        if ($query -> execute ())
        {
            $this -> data = $query -> fetchAll ();
        }
        return ($this -> data);
    }
    public function doSomethingWithData ()
    {
        $this -> getData ()
        foreach ($this -> data as $row)
        {
            // Do processing here
        }
    }
}

现在使用这种方法,每次调用doSomethingWithData时,结果都是调用getData,而getData又执行数据库查询。这是浪费。现在考虑下面这个类似的类:

class Bar
{
    // All non-relevent code omitted
    protected $data = NULL;
    public class getData ()
    {
        // Only run the enclosed if the data property isn't initialized
        if (is_null ($this -> data))
        {
            // Initialize the data property
            $this -> data = array ();
            // Populate the data property with a DB query
            $query = $this -> db -> prepare ('SELECT * FROM footable;');
            if ($query -> execute ())
            {
                $this -> data = $query -> fetchAll ();
            }
        }
        return ($this -> data);
    }
    public function doSomethingWithData ()
    {
        foreach ($this -> getData () as $row)
        {
            // Do processing
        }
    }
}

在这个版本中,你可以随时调用doSomethingWithData(以及getData),你永远不会触发多个数据库查找。此外,如果从未调用getData和doSomethingWithData,则不会执行数据库查找。这将带来很大的性能优势,因为数据库查找是昂贵的,应该尽可能避免。

如果你在一个可以更新数据库的类中工作,它确实会导致一些问题,但这并不难解决。如果一个类更新了它的状态,那么你可以简单地对setter进行编码,使它们在成功时为关联的状态null。这样,数据将在下次调用getter时从数据库中刷新。