使用魔术方法的OOP PHP,getter和setter

时间:2011-07-07 02:45:45

标签: php oop

有哪些策略可用于为PHP类的私有成员创建访问器和更改器?这个建议是否合适:http://cormacscode.wordpress.com/2009/01/22/read-only-object-variables-in-php-using-magic-methods/

<?php
class classWithReadOnlyVar
{
    private $readOnlyVar;

    public function __get($varName)
    {
        return $this->$varName;
    }

    public function __set($varName,$varValue)
    {
    }
}

如果某些成员需要私有,公共或受保护的财产方法会怎样?

3 个答案:

答案 0 :(得分:6)

首先,__get__set等定义为public,否则您无法拥有它们。应该明智地使用魔术方法,因为调用魔术方法所需的时间大约是调用类方法的三倍。

class A {
   public function __get($name) { ... }

   public function __getValue() { ... }     // <== is faster

}

通常(通常,最好),您将拥有您的班级成员privateprotected(从不public)并拥有访问者和变异器来封装它们。这些访问者和增变器可以具有任何可见性,这取决于用户可以对成员做什么。您也可以通过仅为成员声明访问器来创建不可变类,这些访问器仅在构造函数中初始化。

因此,您的示例类应该是

class classWithReadOnlyVar {
   private $readOnlyVar;

   public function getReadonlyVar() {
     return $this->readOnlyVar;
   }

}

并且不应该使用魔术方法。

可能有很多理由要避免使用魔术方法:

  1. 他们打破了代码完成
  2. 他们在运行时速度较慢
  3. 他们使重构和维护有点(很多)更难/更复杂
  4. 你不能拥有protected魔法

  5. 班级成员

    他们的可见性应该是privateprotected,这一切都取决于您是否希望通过继承访问它们。它们永远不应该是public因为这打破了OO范式。

    受保护成员的示例:

    class A {
        protected $_name = 'A';
    
        public function getName() { return $this->_name; }
    }
    
    class B {
        protected $_name = 'B';   // getName() will not return 'B'
    }
    

    $_name没有protected,这是不可能的,也无需重新定义访问者)

    <强>访问者

    他们应该是protectedpublic。拥有private访问者毫无意义;一个类应该直接访问它的成员。如果该成员需要处理,则该类将知道何时调用访问者。

    <强>存取器

    他们应该是protectedpublic。对于访问者来说,拥有private mutator是没有意义的......除非在处理需要在内部完成时非常罕见。如果类成员没有访问者,则应该有一个mutator。如果没有能够以某种方式获得值,那么设置值就没有意义。

答案 1 :(得分:4)

拥有一个无效的__set方法是无稽之谈。它给人以错误的印象,即属性存在,并且当它们确实不存在时可以访问。只需省略空方法,当您尝试修改不可修改的属性时,PHP将正确抛出错误。

此外,任何访问者方法只能是public,其他任何内容都没有意义。 protectedprivate访问器方法没有意义,因为唯一能够访问它们的实体也可以直接访问这些属性。


以下评论中的讨论摘要:

  • 您希望隐藏代码类外的属性,因为该代码不可靠并且可能会破坏您班级的状态
  • 类中的代码可以合理地预期在其自身属性方面表现正常,因此不需要从类本身保护类的属性
  • 一个类总是可以直接访问它的所有属性,提供getter和setter根本不能确保保护(就像它从类外访问一样)

答案 2 :(得分:2)

使用魔法作为__get()__set()是一个好主意 。第一个原因是代码没有清楚地传达其意图。其次,客户必须知道哪些字段可用于使用魔术方法。这违反了Demeter法则,该法则规定模块不应该知道它操纵的对象的内部。

应该为访问者,变更器和谓词命名,并以getsetis为前缀。

例如:

MyClass
{
    /** @var int */
    private $myField;

    /** @return int */
    protected function getMyField()
    {
        $this->myField;
    }
    /** @param int $myField */
    protected function setMyField($myField)
    {
        $this->myField = $myField;
    }
}

你应该尽量避免创建公共mutators,因为外部函数很容易使用它们,就像程序程序使用数据结构一样,从而操纵对象的内部状态,称为功能羡慕

类的方法应该只对它们所属的类的字段和方法感兴趣,而不是其他类的字段和方法。当一个方法使用另一个对象的访问器和mutator来操作该对象中的数据时,它羡慕该对象的范围。所以我们想要消除Feature Envy,因为它将一个类的内部暴露给另一个类。然而,有时,Feature Envy是一个必要的恶魔,例如,在一个对象的方法需要另一个对象的数据作用的情况下,并且将该对象的方法移动到持有数据的对象中将违反面向对象设计的原则。

如果其他类不应该使用它们,为什么我们应该有访问器和更改器?好吧,使访问器和mutator受到保护允许派生类访问父项的私有字段,但仍然保护对象内部状态免受外部窥视和戳戳。

但是,我们难道不能将该字段设置为受保护,从而消除了该类的多余方法吗?我们当然可以,但是你没有可能屏蔽该字段被子类设置。

在PHP中,我们甚至可以说一个类使用私有mutators是有意义的,所以我们有机会在字段设置之前键入检查值。

例如:

MyClass
{
    /** @var int */
    private $myField;

    /** @return int */
    protected function getMyField()
    {
        $this->myField;
    }
    /** @param int $myField */
    private function setMyField($myField)
    {
        if (!(is_int($myField) || null === $myField)) {
            throw new \InvalidArgumentException(sprintf(
                '%s expects $myField to be integer; received %s.'
                __METHOD__,
                gettype($myField)
            ));
        }
        $this->myField = $myField;
    }
}

如果$myField不是整数或null,则抛出异常。然而,也可以认为这样的代码是对自己的编码不信任的标志。无论如何,可以轻松地将私有mutator更改为protected以允许对字段进行子类操作,并且可以将访问器方法设置为private或者删除以防止子类读取字段。