17

我知道这可能是主观的,但我从 Google 阅读了这个 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;

这在我的设计过程中将是一个可喜的变化,因为我看到对 getter/setter 的需求正在消失,但是这样做可能会出现哪些其他障碍/好处?

4

7 回答 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;
    }
}

该技巧仅适用于方法。您可以使用__getand__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的方法调用。您可以将所有默认的 get/set 逻辑放入其中,并且永远不要再触摸它:getProperty()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

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

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

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

于 2011-06-02T14:52:55.373 回答
6

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

于 2011-06-02T13:56:15.813 回答
4

恐怕是一个样板的答案,但我会建议以下内容:如果通过将此属性公开给其他用户,您的类没有封装问题(强制执行业务逻辑等),那么这样做是完全可以的。

于 2011-06-02T13:51:04.110 回答
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
        }
    }
}
于 2011-06-02T14:02:58.043 回答
1

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

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

于 2011-06-02T14:03:15.553 回答
0

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

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

于 2011-06-02T13:49:27.513 回答
0

我想说这真的是个人喜好问题。如果性能真的那么重要,那么我认为您回答了自己的问题。

但是,在您的第一个示例中,您仍然可以dog::name在没有 getter/setter 的情况下访问,就像您在第二个示例中所做的那样:$rover->name = 'rover';因为$name是公共的。

如果您特别想隐藏类成员,则需要声明变量private,或者protected然后需要一个 getter/setter。

于 2011-06-02T13:54:37.633 回答