当涉及到在内部使用属性时,编写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;这是一个缺陷。
我该怎么办?
- 将getter标记为final,在内部使用属性(没有额外的函数调用,强制最终开发人员使用_foo作为属性,因为它是受保护的)
- 在内部使用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时从数据库中刷新。