如何从对象方法中访问对象属性?

106

在对象方法中,如果不是getter/setter方法,访问对象属性的“纯粹”或“正确”的方法是什么?

我知道在对象外部应该使用getter/setter,但在内部你只需要这样做:

Java:

String property = this.property;

PHP:

$property = $this->property;

或者你会这样做:

Java:

String property = this.getProperty();

PHP:

$property = $this->getProperty();

如果我的Java有点不好,请原谅,已经有一年没有用Java编程了...

编辑:

似乎人们认为我只是在谈论私有或受保护的变量/属性。当我学习面向对象编程时,我被教导要为每个属性使用getter/setter,即使它是公开的(实际上我被告知永远不要将任何变量/属性设置为公开)。所以,我可能从一开始就存在错误的假设。答案中的人们似乎在说你应该拥有公共属性,并且那些属性不需要getter和setter,这与我所学的以及我所说的相反,尽管也许这也需要讨论。这可能是一个不错的话题,可以单独提出来讨论...


这个标签是错的,这个问题不需要基于观点的答案,它只需要对耦合、内聚以及SOLID原则有计算机科学知识的回答。 - Martin Spamer
18个回答

66

这可能会引发宗教战争,但在我看来,如果你正在使用getter/setter,那么你也应该在内部使用它 - 同时使用会导致未来的维护问题(例如,有人向setter添加需要在每次设置属性时运行的代码,而该属性在不调用该setter的情况下被内部设置)。


在Java中,setter方法除了设置属性值之外什么都不做,这是一种错误用法的例子。 - euphoria83
1
@euphoria83 可能是这样,但这并不排除它发生的可能性。 - Greg Hurlman
我同意原回答。但是,请记住,如果您正在类或对象内部设置或获取私有内容,则需要确保getter / setter不会出现循环或冗余的情况,导致代码失效。如果无法使用getter / setter,则直接访问它。(例如,您的getter / setter访问次要(私有/受保护)方法以操作数据。) - Rick Mac Gillis

44

个人而言,我认为保持一致很重要。如果你有 getter 和 setter,应该使用它们。唯一不使用访问器的情况是当访问器有很多开销时。可能会感觉代码过于膨胀,但这样做可以在将来避免很多麻烦。一个经典的例子:

以后,你可能想要更改字段的工作方式。也许它应该实时计算,或者你想使用不同类型的后备存储器。如果直接访问属性,则像这样的更改可能会一下子破坏大量的代码。


28

我对大家一致认为getterssetters没问题感到惊讶。我建议阅读Allen Holub的煽动性文章"Getters And Setters Are Evil"。尽管标题很吸引人,但作者提出了有价值的观点。

如果您为每个私有字段都有gettersetter,那么这些字段就与公共字段一样好用。您几乎无法更改私有字段的类型而不对调用该getter的每个类产生涟漪效应。

此外,从严格的面向对象的角度来看,对象应该响应与其(希望是单一)职责相对应的消息(方法)。绝大多数getterssetters对于它们的组成对象来说都没有意义;Pen.dispenseInkOnto(Surface)对我来说比Pen.getColor()更有意义。

获取器和设置器还会鼓励类的用户请求对象的某些数据,执行计算,然后设置对象中的其他值,也称为过程性编程。最好直接告诉对象要做你一开始打算要做的事情,也就是信息专家习语。

但是,在层之间的边界处,如UI、持久性等方面,获取器和设置器是必要的恶。限制对类内部的访问,例如C++的friend关键字、Java的包保护访问、.NET的internal访问以及Friend Class Pattern可以帮助您将getterssetters的可见性减少到仅限于需要它们的人。


20

这取决于属性的使用方式。例如,假设你有一个学生对象,其中包含一个名字属性。如果该属性还没有被检索出来,你可以使用Get方法从数据库中提取该名字。这样,你就可以减少对数据库不必要的调用。

现在假设你的对象中有一个私有整数计数器,用于计算已经调用过该名字的次数。在这种情况下,你可能不希望从对象内部使用Get方法,因为这会产生无效计数。


如果Student对象是业务/领域对象,则现在混合了基础设施细节。理想情况下,业务/领域对象应仅关注业务/领域逻辑。 - moffdub
如果您在getter中添加某种布尔值,会怎样呢?PHP:public function getName($outsideCall = true){ if($outsideCall){ $this->incrementNameCalled(); } return $this->name; } 然后从对象内部调用get name时,您可以通过以下方式防止它增加:PHP:$name = $this->getName(false); 我是不是有点过头了? - cmcculloh

15

PHP提供了多种处理这个问题的方式,包括魔术方法__get__set,但我更喜欢使用明确的getter和setter方法。原因如下:

  1. 可以在setter(以及getter)中放置验证逻辑
  2. 使用明确的方法可以与Intellisense配合工作
  3. 无需考虑属性是只读、只写还是可读写的问题
  4. 检索虚拟属性(例如计算值)看起来与常规属性相同
  5. 您可以轻松设置一个从未在任何地方定义的对象属性,这样就会未被记录

14

我这样做是不是有点过头了?

也许吧;)

另一种方法是利用一个私有/受保护的方法来实际获取(缓存/数据库等),并使用一个公共包装器对其进行封装以增加计数:

PHP:

public function getName() {
    $this->incrementNameCalled();
    return $this->_getName();
}

protected function _getName() {
    return $this->name;
}

然后从对象本身内部开始:

PHP:

$name = $this->_getName();

通过这种方式,您仍然可以将第一个参数用于其他用途(例如,在此处发送一个标志以指示是否使用缓存数据)。


13

我可能没有理解其中的关键点,为什么要在对象内部使用getter来访问该对象的属性?

如果这样做,getter将调用另一个getter,而此getter又将调用另一个getter。

因此,我认为在对象方法中直接访问属性是更好的选择,特别是因为调用对象中的另一个方法(它只会直接访问属性然后返回它)只是一种毫无意义和浪费的行为(或者我误解了问题)。


我被你评论的同样需求所驱使...而且它已经得到了回答,而不是关闭 ;) - Egg Vans

9
问题不需要基于观点的答案。这是一个计算机科学领域几十年来从高内聚、低耦合和SOLID原则的原理进行了广泛研究的主题。
纯粹主义者正确地阅读,面向对象的方式是最小化耦合,最大化内聚。因此,应该避免两者,并通过使用Demeter法则和Tell Don't Ask方法来实现。
而不是获取对象属性的值,这会使两个类之间紧密耦合,可以将对象作为参数传递,例如。
doSomethingWithProperty() {
    doSomethingWith(this.property);
}

如果属性是原生类型,例如int,请使用一个访问方法,并为问题域命名,而不是编程域。
doSomethingWithProperty(this.daysPerWeek());

这些将允许您保持封装和任何后置条件或依赖不变量。您还可以使用设置器方法来维护任何前置条件或依赖不变量,但是,在使用这种习惯用法时,请不要陷入命名为设置器的陷阱中,回到好莱坞原则进行命名。

9

即使在对象内部,最好使用访问器方法。以下是我立即想到的几点:

  1. 出于维护与从对象外部进行访问的一致性的考虑,应该这样做。

  2. 在某些情况下,这些访问器方法可能不仅仅是访问字段; 它们可能还会执行一些额外的处理(尽管这很少见)。如果是这种情况,直接访问字段意味着您会错过那些额外的处理,如果这些处理始终要在这些访问期间进行,则您的程序可能会出现问题。


9
如果你认为“最纯粹”的意思是“最封装”,那么我通常将所有字段声明为私有的,然后在类内部使用“this.field”。对于其他类,包括子类,我使用getter访问实例状态。

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