【问题标题】:Calling the variable property directly vs getter/setters - OOP Design直接调用变量属性 vs getter/setter - OOP 设计
【发布时间】:2011-09-07 02:35:38
【问题描述】:

我知道这可能是主观的,但我读了optimization page from Google for 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 的需求正在消失,但这样做可能会遇到哪些其他障碍/好处?

【问题讨论】:

  • 请记住,本教程有些过时了。它可能仍然与 5.3 相关,但性能确实不是这里的主题。我想推荐Berrys post on getters/setters again,但我注意到你已经知道了..
  • 您链接的文章要么是由不了解 PHP 内部原理的人写的,要么已经过时了。至少那里的一些建议是完全错误的。像这样的东西可以带有谷歌的名字真是太可惜了。
  • @mario 是的,我真的很喜欢 Berrys 关于这个主题的帖子,真的试图减少不必要的代码。
  • @nikic 可以理解,我自己在页面上有些不同意的地方,你能详细说明一下吗?
  • @Phill Pafford:作者(我不知道是什么原因)认为$x = ...; echo $x 使用的内存是直接使用echo ...; 的两倍。

标签: php optimization variables getter-setter design-principles


【解决方案1】:

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

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

【讨论】:

  • 好吧,除非需要将其公开,否则我可能会将其设为保护/私有
  • 如果你使用私人,你不能在课外访问。基本的面向对象的东西。关键是,他们推荐的优化只适用于公共属性。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        您还可以使用 __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
                }
            }
        }
        

        【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

          这将是我的一个可喜的变化 我认为需要的设计过程 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 的魔术方法,因为您需要为所有公共/受保护的函数声明一个 docblock,魔术方法可以做到吗?
          • 关键是,公开类属性只是另一种拥有默认获取/设置逻辑的方式,但它的逻辑由 PHP 解释器处理,完全不受您的控制。而且,如果您编写了一堆期望公共属性的代码,那么稍后再返回并更改它以使用访问器方法会很痛苦。至于 XMLRPC,我想它取决于您用来处理 XMLRPC 调用的工具。你可以只添加没有方法定义的文档块吗?
          猜你喜欢
          • 2019-01-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-13
          • 2017-06-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多