直接调用变量属性与 getter/setters - OOP 设计

我知道这可能是主观的,但我从Google的PHP中阅读了这个优化页面,他们建议直接使用变量属性,而不需要getter和setters。可以理解的是,我看到了这方面的性能提升,但这真的是一个好的设计实践吗?

他们使用 getter/setter 的示例:

class dog {
  public $name = '';

  public function setName($name) {
    $this->name = $name;
  }

  public function getName() {
    return $this->name;
  }
}

$rover = new dog();
$rover->setName('rover');
echo $rover->getName();

建议的优化:

$rover = new dog();
$rover->name = 'rover';
echo $rover->name;

这将是我的设计过程中一个受欢迎的变化,因为我看到对getter/setters的需求消失了,但是这样做还可能出现哪些其他障碍/好处呢?


答案 1

这将是我的设计过程中一个受欢迎的变化,因为我看到对getter/setters的需求消失了,但是这样做还可能出现哪些其他障碍/好处呢?

您将失去在特定属性上实现特殊 get/set 逻辑的能力。对于标量属性(字符串,整数,布尔值),也许这没有问题。但是,如果您有一个作为延迟加载的类实例的属性,该怎么办?

class Document
{
    protected $_createdBy;

    public function getCreatedBy()
    {
        if (is_integer($this->_createdBy)) {
            $this->_createdBy = UserFactory::loadUserById($this->_createdBy);
        }
        return $this->_createdBy;
    }
}

该技巧仅适用于方法。你可以使用 和 用于此逻辑,但是当您添加属性时,最终会得到一个大的令人讨厌的块:__get__setswitch()

public function __get($name)
{
    switch ($name) {
        case 'createdBy':
            // blah blah blah
        case 'createdDate':
            // more stuff
        // more case statements until you scream
    }
}

如果只想避免或推迟编写 getter 和 setter,请使用 magic 方法来捕获遵循 和 命名约定的方法调用。您可以将所有默认的 get/set 逻辑放入,并且再也不碰它:__callgetProperty()setProperty()__call

abstract class Object
{
    public function __call($method, $args)
    {
        $key = '_' . strtolower(substr($method, 3, 1)) . substr($method, 4);
        $value = isset($args[0]) ? $args[0] : null;
        switch (substr($method, 0, 3)) {
            case 'get':
                if (property_exists($this, $key)) {
                    return $this->$key;
                }
                break;

            case 'set':
                if (property_exists($this, $key)) {
                    $this->$key = $value;
                    return $this;
                }
                break;

            case 'has':
                return property_exists($this, $key);
                break;
        }

        throw new Exception('Method "' . $method . '" does not exist and was not trapped in __call()');
    }
}

开发的角度来看,这种方法非常快,因为您只需扩展 Object 类,定义一些属性,就可以开始比赛了:

class Foo extends Object
{
    protected $_bar = 12345;
}

$foo = new Foo();
echo $foo->getBar();  // outputs '12345'
$foo->setBar(67890);  // next call to getBar() returns 67890
$foo->getBaz();       // oops! 'baz' doesn't exist, exception for you

执行的角度来看,它很慢,因为魔术方法非常慢,但是您以后可以通过定义显式和方法来缓解这种情况(因为只有在调用未定义的方法时才调用)。但是,如果某个特定的属性不经常被访问,也许你不在乎它有多慢。关键是,以后很容易添加特殊的 get/set 方法,而代码的其余部分永远不会知道其中的区别。getBar()setBar()__call

我从Magento那里抄写了这种方法,我发现它对开发人员非常友好。在为不存在的属性调用 get/set 时引发异常有助于避免由拼写错误引起的幻像错误。将特定于属性的逻辑保存在其自己的 get/set 方法中,使代码更易于维护。但是,您不必在开始时编写所有访问器方法,您可以轻松返回并添加它们,而无需重构所有其他代码。

问题是,您要优化什么?开发人员时间还是代码速度?如果要优化代码速度,请确保在围绕瓶颈构建代码之前知道瓶颈的位置。过早优化是万恶之源。


答案 2

这是某种微优化。从理论上讲,您以后可以使用魔术方法(__get和__set)在name get/set上添加逻辑,但实际上并不需要太多。同样,实际上,只有当您对其他所有内容都进行了优化时,这种性能改进才重要,甚至几微秒即可增加价值。在这种情况下,您可以使用其他优化技术,例如将所有包含的PHP文件合并为一个,删除类型提示,减少函数参数的数量,使用普通函数而不是类。但通常添加简单的缓存比所有这些微优化增加10-100倍的性能提升。


推荐