是否应该使用getter和setter方法?

27

好的,这件事情正在困扰着我,我开始觉得这完全取决于个人选择而不是一种更有效或更好编写代码的特定方式:在PHP项目中是否应该使用getter/setter方法?到目前为止,我读过的答案相互矛盾,也不完全适用于PHP,因为它不是一种编译语言。例如,看看Stack Overflow上的这个问题(“为什么要使用getter和setter?”)。其中提出了很多使用它们的好理由,但所有子评论都提到了访问器是邪恶的,应该避免使用,穿插其中的是一些更令人不安的评论,认为它们应该被完全避免,因为它会“搞乱你的代码”。

我只得到了相互矛盾的答案,没有一个与解释PHP环境相关。能否有人阐明一下为什么/为什么不应该在PHP中使用它们以及他们背后的理由?如果我们可以简单地将属性定义为私有或受保护的,是否真的很重要,反正:

封装getter和setter提供的保护微不足道

...引用自“ sbi ”(为什么要使用getter和setter?)

就个人而言,我仍然不理解:

Class Foo {
    public $bar;

    function setBarType($val) {
       $this->bar = $val;
    }
}

$fee = new Foo();
$fee->setBarType(42);

比这个更好:

Class Foo {
    public $bar;
}

$fee = new Foo();
$fee->bar = 42;

4
在你的第一个例子中,你有可能在赋值之前检查$val是否符合要求,如果有任何错误就抛出异常、触发用户错误或者直接返回setter函数(推荐使用第一种方式),这其实是封装的关键所在。请注意,在翻译过程中不得改变原文意思,也不需要提供解释或额外内容。 - Leri
@Leri,这确实很有道理。但是这是否意味着我必须为类中的每个属性都有get/set方法,如果我有50个不同的属性会发生什么? - marked-down
3
拥有50个不同属性的类很可能违反SRP原则。不过,这是开发者的选择。例如,我从不为DTO使用getter/setter,而为DomainObjects使用它们,因为我只使用DTO来映射数据(大多数情况下使用反射),而在DomainObjects中,我需要确保拥有正确的数据。 - Leri
阅读《面向对象的软件构造》一书。 - Shaheer
7个回答

22
你提供的博客文章以一个至关重要的句子开始(已加粗):
“你代码中每一个 getter 和 setter 都代表了不能很好地封装,并且会引起不必要的耦合。”
“封装”是面向对象编程中最重要的概念。它实际上是将复杂性包装在类内部,以达到隐藏复杂性的目的。在理想情况下,当你使用一个类时,你不应该知道类的任何内部工作方式或状态。有些人(像这篇博客作者)会认为,拥有getter和setter已经透露了太多有关类内部的信息。在他们看来,一个类应该只有能让我们告诉对象“做什么”的方法,而不需要考虑它是如何工作或处于什么状态。使用setter并不是“告诉对象去做某事”,而是从外部干扰对象的状态。
不要这样做:
$a = myObject();

// Querying object state, making a decision outside the object, and changing its state
if ($a->getWarbleFizz() < 42) {
    $a->setYourWarbleFizzTo(42);
}

// Gee, I hope I made the right decision...
$a->nowDoSomethingUseful();

你应该像这样编写代码:
$a = myObject(42);
$a->doStuff();

或者这个:

$a = myObject();
$a->doStuff(42);

相关阅读:讲故事,而不是询问


链接已失效:https://web.archive.org/web/20200426154726/https://pragprog.com/articles/tell-dont-ask - Matthew

15
因为如果设置值的实现方式发生改变(例如转移到数据库),您不需要更改调用者。另一个例子是,在设置值之前,您可能需要对其进行一些检查。
拥有一个方法可以让您拦截该变量的设置/获取,即使在看起来不需要它时也这样做可以使您的代码更易于更改。
像C#和最近版本的JavaScript这样的语言允许您拦截属性的读取和写入,因此您可以在支持它的语言中使用属性。
对象监视程序

一些语言允许你拦截所有JavaScript的Object.watch或者无法访问的属性使用PHP的__get。这使得你可以实现getter和setter,但是由于它们为每个属性访问创建的开销,会导致性能下降。本答案讨论了getter和setter的其他问题。最佳实践:PHP魔术方法__set和__get

Getter和Setter还可以,但是……

仅仅制作样板式的getter和setter要好一些,但几乎就像公共属性一样糟糕。如果任何人都可以更改您对象的状态(特别是多个属性),那么它将不会封装良好。http://cspray.github.io/2012/05/13/stop-calling-them-getters-setters.html


好的,但是假设我从数据库中获取了大量信息,可能是10个不同变量的信息,我是必须为每个变量都有getter/setter方法,还是只需要为它们从数据库中传递过来的数组设置呢?因为如果是前者,我看到代码会变得非常复杂。 - marked-down
这取决于你,这超出了这个问题的范围,而且我不能对假设性问题发表评论。另外,stackoverflow.com不是询问此类问题的地方。程序员交流是用于更开放式问题的平台。 - Ruan Mendes
你也可以在PHP中拦截属性的读取和写入:http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members - Marek
@Marek 这不是同一件事情,它不会被调用已经存在的属性。它也非常慢,应该谨慎使用,因为你链接的页面解释了许多错误。这更像是JS中的Object.watch,而不是getter和setter。 - Ruan Mendes
@Juan 这被称为无法访问的属性,在使用getter和setter时假定这些属性已声明为私有/受保护。 - Marek
@Marek 这是真的,我补充了关于使用魔术属性来实现getter和setter的信息。 - Ruan Mendes

8
使用getter和setter的一个很大的好处是,每当您需要进行更改时,您只需修改getter和setter即可。
我将用一个例子来解释:
protected $date;
public function getDate(){
    return $this->date;
}
public function setDate($date){
    $this->date = $date;
}

想象一下,有一个原因使日期总是需要增加一天。你需要搜索整个项目,找到访问类成员的地方。

但是通过使用getter和setter,您可以将代码更改为:

protected $date;
public function getDate(){
    return $this->date;
}
public function setDate($date){
    $date = new DateTime($date);
    $date->modify('+1 day');
    $this->date = $date;
}

5
一个面向对象编程的原则是封装。一个类负责包含在该类中的所有变量。
设置公共变量会破坏这个原则。
创建访问器(setter)也会间接地破坏这个原则,因为它给了外部改变那个值的机会。
获取器的优点是,调用方法不需要关心如何检索数据。它只知道它将得到期望的数据,仅此而已。尊重封装原则。
设定器的优点是,类有机会在应用之前检查新值。在完美的世界中,不需要访问器,因为一个类可以完全管理自己的所有变量。在现实世界中,有时您需要从外部设置或获取值。
处理完美世界的最佳方式是仅将访问器限制在必须修改的非常少量的变量上。如果可能的话,请检查设置的值。
关于您的示例,第二种解决方案更好,因为您在php堆栈中节省了一层。您的访问器是无用的,因为您的变量本来就是公共的(所以没有封装),并且不执行任何附加检查。但是,在可能的情况下,您应该使用访问器来检查数据。如果您的应用程序不需要在任何其他地方使用数据,则不要使用任何访问器。

仅限于极少数必须修改的变量,以限制访问者的访问。但是,即使对于不会被修改的属性,我也必须使用getter方法来访问对象中的每个属性,这样至少就要求1个getter方法对应1个属性吧? - marked-down
如果这些数据需要从外部访问,那么一定要使用访问器。调用代码不需要知道如何检索数据。通过使用getter,您可以从数据库、文件或其他地方获取数据。如果直接使用变量,则无法更改检索数据的方式而不进行重大重构。 - Atrakeur

4

选择取决于您。PHP 5 的其他重要特性包括魔术 getter/setter:

http://www.php.net/manual/en/language.oop5.overloading.php#object.set http://www.php.net/manual/en/language.oop5.overloading.php#object.get

这种方法的神奇之处在于,您可以像以下示例一样自由地决定从哪里获取/设置变量,而无需事先声明它们:
Class Foo {
    private $data = array();

    function __set($name,$val) {
       $this->$data[$name] = $val;
    }
    function __get($name) {
       if (array_key_exists($name, $this->data)) {
        return $this->data[$name];
    }
    else {
        return null;
    }
}

嘿,现在就像这样自动发生了一些事情:

$fee = new Foo(); $fee->bar = 42;


2
魔法属性非常慢,如果可以的话,请避免使用它们。 - Ruan Mendes

4
如果在像PHP这样不安全的类型语言中不使用getter和setter,那么你如何确保对象属性的类型是正确的?可能我完全没有理解要点,但如果需要从对象外部访问属性,我认为使用访问器仍然是最好的选择......此外,正如Juon Mendes所提到的:某些语言提供截获属性本身被访问时的选项。PHP也可能会引入这个功能。你甚至可以有一个公共setter和一个受保护的getter:
class TimePeriod {
    protected $Seconds = 3600;

    public $Hours {
        get { return $this->Seconds / 3600; }
        set { $this->Seconds = $value * 3600; }
    }

    // This property is read only as there is no setter
    public $Minutes {
        get { return $this->Seconds / 60; }
    }

    /* public getter, protected setter */
    public $Milliseconds {
        get { return $this->Seconds * 1000; }
        protected set { $this->Seconds = $value / 1000; }
    }
}

为了节省其他人的时间,提到的“这也可能适用于PHP”的2012/2013提案被拒绝了,投票失败了。 - imme

2

在 IT 技术中,使用 getter 和 setter 是一种良好的实践方法,也可以提高代码可读性。除了在设置时检查值之外,还有另一个例子,考虑调用 getMoviesList()。现在这个获取方法可以以任何方式实现,它可以从本地数据库获取电影列表,从在线网络服务获取电影列表等等...因此,做出决策的代码可能在这个函数内部。无论您从哪里调用它,都不需要关心位置和获取方式,只需要获取电影列表即可。


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