【问题标题】:Is it really that wrong not using setters and getters?不使用 setter 和 getter 真的有错吗?
【发布时间】:2009-04-30 18:19:15
【问题描述】:

我是 PHP 新手。出于某种原因,在 JAVA 等其他类型的编程语言中,我对每个变量都使用 setter 和 getter 没有问题,但是当我在 PHP 中编程时,可能是因为它非常灵活,感觉有点浪费时间。大多数时候将类属性设置为公共并像那样操作它们感觉更简单。问题是当我这样做时,我觉得我做错了什么并且违背了面向对象的原则。

不使用 setter 和 getter 真的有那么错吗?为什么或者为什么不?你们大部分时间是怎么做的?

【问题讨论】:

    标签: php oop


    【解决方案1】:

    不使用属性访问器的主要问题是,如果您发现以后需要将字段更改为属性——例如,使其成为子类中的计算属性——您将破坏您的客户端API。对于已发布的图书馆,这是不可接受的;对于内部的,只是需要大量的工作来解决问题。

    对于私有代码或小型应用程序,直接使用它可能是可行的。 IDE(或文本编辑器)将让您生成访问器样板并使用代码折叠将其隐藏。可以说,这使得在机械上使用 getter 和 setter 相当容易。

    请注意,某些编程语言具有合成默认字段+getter+setter 的功能——Ruby 通过元编程实现,C# 具有自动实现的属性。 Python 完全回避了这个问题,让您覆盖属性访问,让您将属性封装在需要它的子类中,而不必事先操心它。 (这是我最喜欢的方法。)

    【讨论】:

    • __get__set 如果您需要将属性转换为经过计算而不会破坏b/c 兼容性的东西。
    • “合成”是指“合成”还是“模拟”?
    • @ErikE Synthesise。在 Ruby 中,在调用 attr_accessor :foo 之后,该类实际上将包含方法 def foo; @foo; end 和类似的 setter ——它们不会被 method_missing 和哈希“伪造”。与 C# 相同,编译器将生成一个诚实的私有字段和琐碎的访问器方法。
    • 我只是从未见过这个词——我猜它是英国的。我知道这个词是“合成”。 NP!只是好奇你说的是什么词。
    • @ErikE 哦,你的意思是拼写。这只是我的 ESL 展示。 (我使用-ise,因为这样我就不需要记住-ize 不正确的例外情况。)Wikipedia says avoiding -ise 是美国主义,英国人在任何地方都允许两者,但偏好是您遵循哪种风格指南。
    【解决方案2】:

    getter 或 setter 的意义在于,您仍然可以在一个地方而不是在您想要修改或检索该字段的每个地方为您对字段的修改添加逻辑。您还可以在班级级别控制该领域发生的事情。

    【讨论】:

      【解决方案3】:

      如果我们在这里严格讨论 PHP 而不是 C#、Java 等(编译器将优化这些东西),我发现 getter 和 setter 是一种资源浪费,你只需要代理一个私有字段,什么都不做。

      在我的设置中,我创建了两个蹩脚的类,一个有五个私有字段,由五个 getter/setter 对封装,代理该字段(看起来几乎 完全 像 java 代码,很有趣),另一个带有五个公共字段,并在创建实例后最后调用 memory_get_usage()。带有 getter/setter 的脚本使用了 59708 字节的内存,带有公共字段的脚本使用了 49244 字节。

      在任何显着大小的类库的上下文中,例如网站框架,这些无用的 getter 和 setter 可以加起来形成一个巨大的内存黑洞。我一直在为我的雇主开发一个 PHP 框架(他们的选择,而不是我的。如果我有选择的话,我不会为此使用它,但话虽如此,PHP 并没有对我们施加任何不可逾越的限制),当我重构时使用公共字段而不是 getter/setter 的类库,整个 shebang 最终每个请求使用的内存至少减少了 25%。

      __get()、__set() 和 __call() 'magic' 方法在处理接口更改方面非常出色。当您需要将字段迁移到 getter/setter(或将 getter/setter 迁移到字段)时,它们可以使流程对任何依赖代码透明。使用解释性语言,即使 Eclipse PDT 或 Netbeans 提供了对代码敏感性的相当好的支持,也很难找到字段或方法的所有用法,因此魔术方法对于确保旧接口仍然委托给新接口很有用功能。

      假设我们有一个使用字段而不是 getter/setter 开发的对象,我们想将一个名为“field”的字段重命名为“fieldWithBetterName”,因为“field”不合适,或者不再准确描述用途,或者是完全错误的。假设我们想更改一个名为“field2”的字段,以便从数据库中延迟加载其值,因为最初使用 getter 并不知道它...

      class Test extends Object {
          public $field;
          public $field2;
      }
      

      变成

      class Test extends Object {
          public $fieldWithBetterName = "LA DI DA";
          private $_field2;
      
          public function getField2() {
              if ($this->_field2 == null) {
                  $this->_field2 = CrapDbLayer::getSomething($this->fieldWithBetterName);
              }
              return $this->_field2;
          }
      
          public function __get($name) {
              if ($name == 'field')) {
                  Logger::log("use of deprecated property... blah blah blah\n".DebugUtils::printBacktrace());
                  return $this->fieldWithBetterName;
              }
              elseif ($name == 'field2') {
                  Logger::log("use of deprecated property... blah blah blah\n".DebugUtils::printBacktrace());
                  return $this->getField2();
              }
              else return parent::__get($name);
          }
      }
      $t = new Test;
      echo $t->field;
      echo $t->field2;
      

      (附带说明,“扩展对象”位只是一个基类,我用于几乎所有具有 __get() 和 __set() 声明的东西,当访问未声明的字段时会引发异常)

      您可以使用 __call() 向后退。这个例子很脆弱,但清理起来并不难:

      class Test extends Object {
          public $field2;
      
          public function __call($name, $args) {
              if (strpos($name, 'get')===0) {
                  $field = lcfirst($name); // cheating, i know. php 5.3 or greater. not hard to do without it though.
                  return $this->$field;
              }
              parent::__call($name, $args);
          }
      }
      

      如果 setter 必须做某事,或者如果 getter 必须延迟加载某事,或确保某事已创建,或其他任何情况,PHP 中的 Getter 和 setter 方法是很好的,但如果它们什么都不做,则它们是不必要且浪费的除了代理字段之外,尤其是使用上面提到的一些技术来管理界面更改。

      【讨论】:

        【解决方案4】:

        我可能不会在这个问题上获得很多支持,但个人 getter 甚至更多,所以 setter 对我来说就像一种代码味道。设计应该是行为驱动的,而不是数据驱动的。当然,这只是一种看法。如果您有一个对象依赖于另一个对象的特定数据字段,则这是非常紧密的耦合。相反,它应该取决于该对象的行为,该对象远没有其数据那么脆弱。

        但是,正是由于这个原因,像 getter 和 setter 这样的属性直接从对字段的依赖中提升了一步。它不那么脆弱,并且放松了对象之间的耦合。

        【讨论】:

        【解决方案5】:

        您是否考虑过使用魔术函数__set/__get?使用它们,您可以轻松地将所有 getter/setter 函数合并到 2 个函数中!

        【讨论】:

        • 有点。请记住,只有在您尝试访问不可访问的属性时才会调用 __set 和 __get。这意味着如果您想使用 __get 和 __set ,您需要完全避免定义属性,这会带来一系列令人头疼的问题。 (当然,您不必完全避免定义属性,但是如果您有混合定义的属性和未定义的属性,您最终会混淆和模棱两可,避免混淆和模棱两可是我们(应该)所说的“最佳实践” .
        • @Alan,我认为您需要做的就是将属性定义为“受保护”或“私有”。这使它们无法访问。 php.net/language.oop5.overloading
        • @sirlancelot,您可以将它们定义为私有或受保护,但它们仍可在 some 范围内直接访问。根据您的 setter/getter 中逻辑的复杂性,这可能会很痛苦。
        【解决方案6】:

        有一种方法可以在不实际使用 get/set 函数类的情况下模拟 get/set,因此您的代码保持整洁:

        $person->name = 'bob';
        echo $person->name;
        

        看看this class我已经编码了。

        通常,在使用此类时,您会声明所有属性受保护(或私有)。如果您想在属性上添加行为,例如在“name”属性上说 strtolower() + ucfirst(),您需要做的就是在您的类中声明一个受保护的 set_name() 函数,然后该行为应该被自动拾取。使用 get_name() 也可以做到这一点。

        // Somewhere in your class (that extends my class).
        protected function set_name($value) { $this->name = ucfirst(strtolower($value)); }
        //
        
        // Now it would store ucfirst(strtolower('bob')) automatically.
        $person->name = 'bob';
        

        附: 另一个很酷的事情是你可以组成不存在的字段,例如

        echo $person->full_name;
        

        没有这样的字段(只要有 get_full_name() 函数)。

        【讨论】:

          【解决方案7】:

          如果您在脚本中访问这些变量很多时间,并且如果您经常更新 yoru 类,您应该使用 setter 和 getter,因为但如果您不这样做,当您改进您的类时,您必须更新所有使用此变量的文件。

          你这样做的第二个主要原因是你不应该直接访问变量,因为类结构可能会改变,并且可以以不同的方式提供这些数据。当你从类中获取数据时,你不应该关心这些数据是如何生成的。类有关心这个数据处理,所以你只应该关心你会得到什么。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-01-28
            • 2016-03-13
            • 1970-01-01
            • 1970-01-01
            • 2019-11-26
            • 2012-01-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多