直接调用变量属性 vs 使用getter/setters - 面向对象编程设计

17
我知道这可能有点主观,但我阅读了谷歌针对 PHP 优化的优化页面,其中建议直接使用变量属性而不需要 getter 和 setter。可以理解这样做可以提高性能,但这真的是一个好的设计实践吗?

他们使用 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;

如果我不再需要 getters/setters,那么这对我的设计流程来说将是一个受欢迎的变化,但如果这样做,还会出现哪些障碍/好处呢?


请注意,此教程有点过时。它可能仍然适用于5.3版本,但在这里,性能并不是重点。我想再次推荐Berry关于getter/setter的文章,但是我注意到你已经知道了它。 - mario
你链接的文章要么是由一位对PHP内部机制无知的人撰写,要么就是非常过时了。里面提供的一些建议实在是毫无根据。这样的文章居然能够冠以谷歌的名义,真是令人痛心。 - NikiC
@Phill Pafford:作者(出于我不知道的原因)认为$x = ...; echo $x使用的内存是直接使用echo ...;的两倍。 - NikiC
@mario @Phill Pafford 谢谢你们,非常感激!没想到我的博客文章这么受欢迎。 :) - Berry Langerak
谷歌的链接现在已经失效。 - henrywright
显示剩余2条评论
7个回答

12

对于我的设计流程来说,这会是一个受欢迎的变化,因为我看到了getter/setter的需求正在减少,但是如果这样做,可能会出现哪些其他障碍/好处呢?

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

class Document
{
    protected $_createdBy;

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

这个技巧只适用于方法。您可以使用__get__set来实现此逻辑,但随着属性的增加,您最终会得到一个庞大且难以维护的switch()块:

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

如果你只是想避免或推迟编写getter和setter方法,可以使用__call魔术方法来捕获遵循getProperty()setProperty()命名约定的方法调用。你可以将所有默认的get/set逻辑放在__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

从执行效率来看,使用魔术方法会导致速度变慢,但您可以通过定义显式的getBar()和setBar()方法来缓解这个问题(因为__call只有在调用未定义的方法时才会被调用)。但是如果某个属性很少被访问,也许您并不关心它的速度。重要的是,稍后添加特殊的get/set方法非常容易,而其他代码则毫不知情。
我从Magento中借鉴了这种方法,并发现它非常适合开发人员使用。当调用不存在的属性的get/set时抛出异常可以帮助您避免因打字错误而导致的虚假错误。将特定于属性的逻辑保留在其自己的get/set方法中可以使代码更易于维护。但是您不必一开始就编写所有的访问器方法,您可以轻松地返回并添加它们而无需重构所有其他代码。
问题是,您想要优化什么?开发人员时间还是代码速度?如果您想要优化代码速度,请确保在围绕它们构建代码之前知道瓶颈在哪里。过早的优化是万恶之源。

感谢分享这些好的观点。我的优化不是关于写更少的代码而是更好的实践方法。如果我需要一个 setter/getter,那么没问题,但如果你只是用它来设置一个值,也许有更有效的编写方式。我不是很喜欢魔术方法,之前用过。我还没有尝试过将魔术方法用于 XMLRPC,因为您需要为所有公共/受保护的函数声明文档块,魔术方法能做到吗? - Phill Pafford
重点是将类属性公开只是另一种具有默认get/set逻辑的方式,但这是由PHP解释器完全在您控制范围之外处理的逻辑。如果您编写了一堆代码,期望公共属性,则稍后返回并更改为使用访问器方法会很麻烦。至于XMLRPC,我想这取决于您使用的处理XMLRPC调用的工具。您能否仅添加docblocks而不需要方法定义? - squirrel

6
这是一种微小的优化。从理论上讲,您可以稍后使用魔术方法(__get和__set)在名称获取/设置上添加逻辑,但实际上并不是非常需要。而且,实际上,只有当您已经将其他所有内容都进行了优化,即使是少数微秒也会增加价值时,这种性能提升才显得重要。在这种情况下,您可以使用其他优化技术,例如将所有包含的PHP文件合并成一个文件、删除类型提示、减少函数参数数量、使用普通函数而不是类。但通常,添加简单的缓存比所有这些微小的优化都能提供10-100倍的性能提升。

4

很抱歉,这是一个模板答案,但我建议以下操作: 如果您的类没有封装问题(强制执行业务逻辑等),通过向其他用户公开此属性来暴露它是完全可以的。


2
您也可以使用__get和__set魔术方法:
class Example
{
    private $allowedProps = array('prop1', 'prop2', 'prop3');
    private $data = array();

    public function __set($propName, $propValue)
    {
        if (in_array($propName, $this->allowedProps))
        {
            $this->data[$propName] = $propValue;
        }
        else
        {
            // error
        }
    }

    public function __get($propName)
    {
        if (array_key_exists($propName, $this->data))
        {
            return $this->data[$propName];
        }
        else
        {
            // error
        }
    }
}

3
就性能而言,这实际上比使用getter和setter要。请参阅此博客文章:http://erosbence.blogspot.com/2011/07/getters-setters-performance.html - chiborg
1
性能的影响可以忽略不计。但是,这仍然比仅将事物公开更好的设计。 - Major Productions

1

起初我很惊讶,就像... wtf。但是在花费几秒钟思考后,我意识到示例在循环中调用了100万次getter函数。当然,如果变量被包装在getter中,我们已经添加了指令,当然会花费更长时间。

在我看来,在大多数情况下,这非常微不足道,因为我还没有遇到一个运行时接近调用100万次getter的脚本。如果您确实需要将性能挤到最后一滴,则了解此优化技术是很好的。


0

这取决于$name是否为公共的。如果不是,您无法直接访问/修改它。权衡的是将类的内部数据元素直接暴露给集成商。这对于某些类可能是可以接受的,但对于其他类则不是。

例如,您可能不希望其他人能够直接在Product类中修改产品的价格。


我会将其设置为protected/private,除非需要将其设置为public。 - Phill Pafford
如果您使用private,则无法访问类外部。这是基本的面向对象编程知识。重点是,他们推荐的优化仅适用于公共属性。 - AJ.

0

我认为这实际上是个人喜好的问题。如果性能真的那么重要,那么我想你已经回答了自己的问题。

然而,在你的第一个例子中,你仍然可以像在第二个例子中一样访问dog::name而不需要getter/setter:$rover->name = 'rover';因为$name是公共的。

如果你特别想隐藏一个类成员,你需要将变量声明为privateprotected,然后就需要一个getter/setter。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接