PHP面向对象编程(OOP),使用魔术方法实现getters和setters。

5

1
我不理解这个问题:“如果一些成员需要私有、公共或受保护的属性方法怎么办?” - deceze
假设您有一个私有成员 $foo(所有成员都应该是私有的,对吧?),并且您想要一个访问器来访问成员 $foo,但只希望该类能够访问它。因此,您需要一个 private function get_foo(),这是一个私有属性。 - StackOverflowNewbie
1
我不明白为什么要有一个私有访问器函数,如果你想在类内部使用这个属性,你可以使用 $this->property。Getter和setter是用来在变量的值从类外设置时获取其值的。如果你真的需要一个私有函数,为什么要使用魔术函数?你可以给它取一个别的名字。 - Shameer
3个回答

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. 您无法拥有受保护的魔术方法
  5. 等等。

类成员

它们的可见性应为privateprotected,这完全取决于您是否希望通过继承进行访问。 它们永远不应该是public,因为这会破坏面向对象编程范例。

受保护成员的示例:

class A {
    protected $_name = 'A';

    public function getName()return $this->_name; }
}

class B {
    protected $_name = 'B';   // getName() will not return 'B'
}

(如果没有$_name被保护,这是不可能的,也不需要重新定义访问器)

访问器

它们应该是protectedpublic。拥有一个private的访问器没有意义;一个类应该直接访问它的成员。如果成员需要处理,那么当调用访问器时,类会知道何时调用或不调用。

变异器

它们应该是protectedpublic。对于访问器来说,拥有一个private的变异器没有意义……除非极少数情况下需要在内部进行处理。如果一个类成员没有访问器,那么它就不应该有一个变异器。而且,没有办法在不获取值的情况下设置值是没有意义的。


所有的类成员都应该是私有的,不是吗? - StackOverflowNewbie

4
拥有一个什么也不做的__set方法是无意义的。它会让人误以为属性存在并且可以被访问,而实际上却不能。只需省略空方法,PHP就会在您尝试修改不可修改的属性时正确地抛出错误。
此外,任何访问器方法只能是public,其他任何访问器方法都没有意义。因为唯一能够访问它们的实体也可以直接访问属性。
以下是下方评论中的讨论摘要:
  • 你想隐藏来自类外部的代码对属性的访问,因为这些代码不可靠,可能会破坏类的状态
  • 类内的代码可以合理地期望其自身属性的行为正确,因此没有必要保护类的属性免受类本身的影响
  • 类始终可以直接访问其所有属性,提供getter和setter并不能确保保护(像从类外部访问时那样)

私有和受保护的访问器用于引用当前对象中的成员是有意义的。 - evan
@deceze,私有访问器确实没有意义,但受保护的访问器可能有用。我在Zend Framework的一些地方看到过这种情况,在那里有几个子类和访问器需要为基类实现不同的受保护处理... - Yanick Rochon
@deceze - 将访问器和修改器作用域定义为私有、受保护或公共的,这是基本的面向对象编程概念,对吧?我不确定我是否理解你的意思。 - StackOverflowNewbie
1
但是,如果两者都是“private”,那么$this->getFoo()$this->foo之间有什么区别呢?为什么一个类需要访问器来访问自己的属性?也许对于受保护的访问器和子类来说有一种情况,但在实践中我几乎没有遇到过。 - deceze
1
@Stack 我同意 privatesetters 可以确保数据的一致性。但是如果你只是返回原始值,那么 privategetters 就没有任何意义了。它只会使你的代码变得臃肿,并且对任何事情都没有保护作用。 - deceze
显示剩余12条评论

2
不推荐使用 __get()__set() 这样的魔术方法,首先这段代码并没有清晰地传达其意图。其次,客户端必须知道可用的字段才能使用魔术方法。这违反了洛米特法则(Law of Demeter),该法则指出模块不应该知道其操作对象的内部细节。
访问器、修改器和谓词应该以它们的值命名,并加上 getsetis 前缀。
例如:
MyClass
{
    /** @var int */
    private $myField;

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

尽量避免创建公共 mutator(变异器),因为外部函数会诱使将其用作过程式程序使用数据结构的方式来操纵对象的内部状态,这被称为 特性嫉妒
类的方法只应关注所属类的字段和方法,而不是其他类的字段和方法。当一个方法使用另一个对象的访问器和 mutator 来操纵该对象内部的数据时,它就会 嫉妒 该对象的范围。因此,我们想要消除特性嫉妒,因为它会向另一个类公开该类的内部机制。然而,特性嫉妒有时是必要的,例如,在一个对象的方法需要另一个对象的数据来进行操作的情况下,将该对象的方法移动到持有数据的对象中会违反面向对象设计原则。
那么如果其他类不应该使用访问器和 mutator,为什么我们还要拥有它们呢?嗯,将访问器和 mutator 设置为受保护的,允许派生类访问父类的私有字段,但仍然可以防止外部偷窥和操纵对象的内部状态。
但是,我们不能将字段设置为受保护吗?这样做确实可行,但你就无法屏蔽子类设置字段的可能性。
在 PHP 中,我们甚至可以认为让一个类使用私有 mutator 是有意义的,这样我们就有机会在将值设置到字段之前进行类型检查。
例如:
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或删除以防止子类读取该字段。


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