ChatGPT解决这个技术问题 Extra ChatGPT

为什么要使用 getter 和 setter/accessor?

使用 getter 和 setter 有什么好处 - 只获取和设置 - 而不是简单地为这些变量使用公共字段?

如果 getter 和 setter 所做的不仅仅是简单的 get/set,我可以很快解决这个问题,但我不是 100% 清楚如何:

public String foo;

比:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

而前者需要更少的样板代码。

@Dean J:与许多其他问题重复:stackoverflow.com/search?q=getters+setters
当然,当对象不需要更改属性时,两者都同样糟糕。我宁愿将所有内容都设为私有,然后在有用时添加 getter,并在需要时添加 setter。
谷歌“访问者是邪恶的”
如果您碰巧正在编写函数式代码或不可变对象,则“访问器是邪恶的”。如果您碰巧正在编写有状态的可变对象,那么它们就非常重要。
告诉,不要问。 pragprog.com/articles/tell-dont-ask

D
Dennis Meng

实际上有很多充分的理由考虑使用访问器而不是直接公开类的字段——不仅仅是封装的论点和使未来的变化更容易。

以下是我知道的一些原因:

封装与获取或设置属性相关的行为——这允许以后更容易地添加附加功能(如验证)。

隐藏属性的内部表示,同时使用替代表示公开属性。

使您的公共接口免受更改 - 允许公共接口在实现更改时保持不变,而不会影响现有消费者。

控制属性的生命周期和内存管理(处置)语义 - 在非托管内存环境(如 C++ 或 Objective-C)中尤为重要。

为属性在运行时更改时提供调试拦截点 - 在某些语言中,如果没有此属性,调试属性更改为特定值的时间和地点可能会非常困难。

改进了与旨在针对属性 getter/setter 操作的库的互操作性 - 想到了模拟、序列化和 WPF。

允许继承者更改属性行为方式的语义,并通过覆盖 getter/setter 方法来公开。

允许 getter/setter 作为 lambda 表达式而不是值传递。

getter 和 setter 可以允许不同的访问级别——例如 get 可能是公共的,但 set 可以是受保护的。


关于最后一点:例如,为什么集合可以受到保护?
这是一个很好的概述。添加一个全面的示例将使其非常棒。
@andreagalle 有很多原因可能是这种情况。也许你有一个不能从外部改变的属性。在那种情况下,您也可以完全删除 setter。但也许您想在子类中更改这种更改阻止行为,然后可以使用 protected 方法。
-1 因为没有点。 1 是开发陷阱 #1:经典的 Getter / Setter 不封装行为。因为他们不表达行为。 Cutomer::setContractEndDate() / Customer::getContractEndDate() 没有行为,它根本不封装任何东西。这是假封装。 Customer::cancelContract() 是一种行为,您实际上可以在其中修改逻辑而不更改接口。如果您通过 getter 和 setter 公开原子属性,它将不起作用。
穿着凯夫拉防暴服、防毒面具和盾牌走到外面有很多很好的理由。你会更安全!您将免受化学和生物威胁。还有很多!因此,每一种只考虑正面或只考虑负面的方法都是不平衡的,因此在实践中会导致失败。因为你要付出,对于“好东西”,价格不是你可以忽视的。 getter 和 setter 的价格是多少?你的时间,你的注意力,你的生活。因此,当您和其他人在创建和维护代码所花费的时间类别中获得更多好处时,请使用它们。
C
ChssPly76

因为从现在起 2 周(月、年)后,当您意识到您的 setter 需要做的不仅仅是设置值时,您还将意识到该属性已直接在 238 个其他类中使用:-)


我正坐着盯着一个 500k 行的应用程序,那里从来不需要它。也就是说,如果需要它一次,它就会开始导致维护噩梦。对我来说足够好。
我只能羡慕您和您的应用程序 :-) 也就是说,这实际上也取决于您的软件堆栈。例如,Delphi(和 C# - 我认为?)允许您将属性定义为一等公民,他们可以在其中直接读取/写入字段,但是 - 如果您需要它 - 也可以通过 getter / setter 方法进行。很方便。唉,Java 没有——更不用说强制你使用 getter / setter 的 javabeans 标准了。
虽然这确实是使用访问器的一个很好的理由,但许多编程环境和编辑器现在提供对重构的支持(在 IDE 中或作为免费插件),这在一定程度上减少了问题的影响。
听起来像是过早的优化
当您想知道是否要实现 getter 和 setter 时,您需要问的问题是:为什么类的用户需要访问类的内部?他们是否这样做并不重要直接或被薄伪层屏蔽——如果用户需要访问实现细节,那么这表明该类没有提供足够的抽象。另见this comment
L
Lightness Races in Orbit

公共字段并不比 getter/setter 对差,它除了返回字段并分配给它之外什么都不做。首先,很明显(在大多数语言中)没有功能差异。任何差异都必须在其他因素上,例如可维护性或可读性。

getter/setter 对的一个经常提到的优点不是。有人声称您可以更改实现并且您的客户端不必重新编译。据说,setter 允许您稍后添加验证等功能,而您的客户甚至不需要知道它。但是,向 setter 添加验证是对其先决条件的更改,违反了之前的约定,很简单,“你可以把任何东西放在这里,然后你可以从 getter 中得到同样的东西”。

因此,既然您违反了合同,那么更改代码库中的每个文件是您应该做的事情,而不是避免。如果你避免它,你就是在假设所有代码都假设这些方法的合同是不同的。

如果这不应该是合同,那么接口允许客户端将对象置于无效状态。这与封装完全相反 如果该字段从一开始就不能真正设置为任何内容,为什么从一开始就没有验证呢?

同样的论点也适用于这些 pass-through getter/setter 对的其他假定优势:如果您后来决定更改正在设置的值,那么您就违反了合同。如果您以超出一些无害修改(如日志记录或其他不可观察的行为)的方式覆盖派生类中的默认功能,则您违反了基类的约定。这违反了 Liskov 可替代性原则,该原则被视为 OO 的原则之一。

如果一个类的每个字段都有这些愚蠢的 getter 和 setter,那么它就是一个没有任何不变量、没有契约的类。那真的是面向对象的设计吗?如果类只有那些 getter 和 setter,它只是一个哑数据持有者,哑数据持有者应该看起来像哑数据持有者:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

将传递的 getter/setter 对添加到这样的类不会增加任何价值。其他类应该提供有意义的操作,而不仅仅是字段已经提供的操作。这就是您可以定义和维护有用的不变量的方式。

客户:“我能用这个类的对象做什么?”设计师:“你可以读写几个变量。”客户:“哦……很酷,我猜?”

使用 getter 和 setter 是有原因的,但如果这些原因不存在,那么以假封装之神的名义制作 getter/setter 对并不是一件好事。制作 getter 或 setter 的正当理由包括经常提到的作为您以后可以进行的潜在更改的事情,例如验证或不同的内部表示。或者该值应该可以被客户端读取但不可写(例如,读取字典的大小),所以一个简单的 getter 是一个不错的选择。但是当你做出选择时,这些理由应该在那里,而不仅仅是你以后可能想要的东西。这是 YAGNI(你不需要它)的一个实例。


很好的答案(+1)。我唯一的批评是,我读了几遍才弄明白最后一段中的“验证”与前几段中的“验证”有何不同(在后一种情况下你抛弃了但在前一种情况下提升了);在这方面调整措辞可能会有所帮助。
这是一个很好的答案,但可惜当前时代已经忘记了“信息隐藏”是什么或它的用途。他们从来没有读过关于不变性的文章,在寻求最敏捷的过程中,从来没有画过状态转换图来定义对象的合法状态是什么,什么不是。
K
Kai

很多人都在谈论 getter 和 setter 的优点,但我想扮演魔鬼的拥护者。现在我正在调试一个非常大的程序,程序员决定让所有东西都成为 getter 和 setter。这可能看起来不错,但它是逆向工程的噩梦。

假设您正在查看数百行代码,然后遇到以下情况:

person.name = "Joe";

在您意识到它是一个 setter 之前,这是一段非常简单的代码。现在,您跟踪该设置器,发现它还设置了 person.firstName、person.lastName、person.isHuman、person.hasReallyCommonFirstName,并调用了 person.update(),它将查询发送到数据库等。哦,那是发生内存泄漏的地方。

乍一看理解本地代码是良好可读性的重要属性,getter 和 setter 往往会破坏。这就是为什么我尽量避免使用它们,并尽量减少使用它们时的作用。


这是反对语法糖的论点,而不是反对一般的二传手。
@Phil 我不同意。可以很容易地编写一个 public void setName(String name)(用 Java)来完成同样的事情。或者更糟糕的是,一个 public void setFirstName(String name) 可以完成所有这些事情。
@tedtanner,当您这样做时,调用方法不再是一个谜,因为它没有伪装成字段访问。
@Phil 我和你在一起。我不相信隐藏方法调用。我只想指出,Kai 的论点不是关于语法糖,而是更多关于设置器(包括那些看起来像方法调用的设置器)不应该有副作用,比如更改对象中的其他数据成员或查询数据库。
@tedtanner 我认为我们对 setter 和 getter 应该做什么以及是否应该像这样隐藏它们有共同的看法,但事实是当它们被隐藏时,它总是语法糖——它总是一个方法调用,on机器。回到 2009 年写答案的时候,以及 2013 年我写评论的时候,有人使用注释和 codegen 或切入点类型的解决方案来做到这一点,这都是非常不标准的。 2022年的今天,好多年没碰Java了😂
y
yegor256

在纯面向对象的世界中,getter 和 setter 是一种可怕的反模式。阅读这篇文章:Getters/Setters. Evil. Period。简而言之,它们鼓励程序员将对象视为数据结构,而这种类型的思维是纯粹的过程性的(就像在 COBOL 或 C 中一样)。在面向对象的语言中没有数据结构,只有暴露行为的对象(不是属性/属性!)

您可以在 Elegant Objects 的第 3.5 节(我关于面向对象编程的书)中找到更多关于它们的信息。


有趣的观点。但在大多数编程环境中,我们需要的是数据结构。以链接文章的“狗”为例。是的,您无法通过设置属性来更改现实世界中狗的体重……但 new Dog() 不是狗。它是保存有关狗的信息的对象。对于这种用法,很自然地能够纠正错误记录的重量。
好吧,我告诉你大多数有用的程序不需要建模/模拟现实世界的对象。 IMO,这根本不是关于编程语言的。这是关于我们编写程序的目的。
现实世界与否,耶戈尔是完全正确的。如果您拥有的是真正的“结构”并且您不需要编写任何按名称引用它的代码,请将其放入哈希表或其他数据结构中。如果您确实需要为其编写代码,则将其作为类的成员并将操作该变量的代码放在同一类中并省略 setter 和 getter。 PS。虽然我大多同意 yegor 的观点,但我开始相信没有代码的带注释的 bean 是一些有用的数据结构——有时也需要 getter,setter 不应该存在。
我被冲昏了头脑——整个答案虽然正确且相关,但并没有直接解决问题。也许它应该说“setter/getter 和公共变量都是错误的”......绝对具体而言,永远不应该使用 Setter 和可写公共变量,而 getter 与公共最终变量几乎相同,并且偶尔是必要的邪恶但两者都不比另一个好。
我从来没有理解过getter/setter。从我在大学上的第一堂 Java101 课程开始,他们似乎鼓励构建缺乏抽象的东西,这让我感到困惑。但是有这种情况:你的对象可能有很多不同的行为。那么您可能不希望全部实现它们。您制作 setter/getter 并方便地告诉您的用户,如果您需要我错过的行为,请自己继承和实现它们。毕竟我给了你访问者。
P
Peter D

有很多原因。我最喜欢的一个是当您需要更改行为或规范您可以在变量上设置的内容时。例如,假设您有一个 setSpeed(int speed) 方法。但是您希望您只能将最大速度设置为 100。您可以执行以下操作:

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

现在,如果您在代码中的任何地方都使用公共字段,然后您意识到您需要上述要求怎么办?寻找公共字段的每一种用法,而不是仅仅修改你的 setter,玩得开心。

我的 2 美分 :)


寻找公共领域的每一种用法应该不难。将其设为私有并让编译器找到它们。
这当然是真的,但为什么要让它比设计的更难。 get/set 方法仍然是更好的答案。
@GraemePerrow 必须全部更改它们是一个优势,而不是问题:( 如果您的代码假定速度可能高于 100 会怎样(因为,您知道,在您违反合同之前,它可能!)(while(speed < 200) { do_something(); accelerate(); }
这是一个非常糟糕的例子!有人应该调用:myCar.setSpeed(157); 并在几行之后 speed = myCar.getSpeed(); 现在...祝您调试愉快,同时尝试理解为什么应该是 157speed==100
T
Thomas Owens

访问器和修改器的一个优点是您可以执行验证。

例如,如果 foo 是公开的,我可以轻松地将其设置为 null,然后其他人可以尝试调用对象上的方法。但它已经不存在了!使用 setFoo 方法,我可以确保永远不会将 foo 设置为 null

访问器和修改器还允许封装 - 如果您不应该在设置后看到该值(也许它在构造函数中设置然后由方法使用,但不应该被更改),那么任何人都不会看到它。但是,如果您可以允许其他类查看或更改它,您可以提供适当的访问器和/或修改器。


p
pppery

取决于你的语言。你已经标记了这个“面向对象”而不是“Java”,所以我想指出 ChssPly76 的答案是依赖于语言的。例如,在 Python 中,没有理由使用 getter 和 setter。如果您需要更改行为,您可以使用一个属性,该属性包含一个围绕基本属性访问的 getter 和 setter。像这样的东西:

 class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)

是的,我在答案下方的评论中说了很多。 Java 不是唯一使用 getter / setter 作为拐杖的语言,就像 Python 不是唯一能够定义属性的语言一样。然而,重点仍然存在——“财产”不是同一个“公共领域”。
@jcd - 一点也不。您通过公开您的公共字段来定义您的“接口”(公共 API 在这里会是一个更好的术语)。一旦完成,就没有回头路了。属性不是字段,因为它们为您提供了一种机制来拦截访问字段的尝试(如果已定义,则将它们路由到方法);然而,这只不过是 getter / setter 方法的语法糖。它非常方便,但不会改变底层范式 - 暴露字段而无法控制对它们的访问违反了封装原则。
@ChssPly76——我不同意。我拥有与属性一样多的控制权,因为我可以在需要时将它们设为属性。使用样板 getter 和 setter 的属性与原始属性之间没有区别,只是原始属性更快,因为它使用底层语言,而不是调用方法。在功能上,它们是相同的。唯一可能违反封装的方法是,如果您认为括号 (obj.set_attr('foo')) 本质上优于等号 (obj.attr = 'foo')。公共访问是公共访问。
@jcdyer 尽可能多的控制是的,但没有那么多的可读性,其他人经常错误地认为 obj.attr = 'foo' 只是设置变量而没有发生任何其他事情
@TimoHuovinen 假设 obj.setAttr('foo') “只是设置变量而没有发生任何其他事情”,这与 Java 中的用户有何不同?如果它是一个公共方法,那么它就是一个公共方法。如果您使用它来实现一些副作用,并且它是公开的,那么您最好能够依靠一切正常工作就好像只发生了预期的副作用(使用 all 其他实现细节和其他副作用、资源使用等,隐藏在用户的关注点之外)。这与 Python 完全没有什么不同。 Python 实现该效果的语法更简单。
S
StackedCrooked

谢谢,这真的澄清了我的想法。现在这是(几乎)10 个(几乎)不使用 getter 和 setter 的好理由:

当您意识到您需要做的不仅仅是设置和获取值时,您可以将字段设为私有,它会立即告诉您直接访问它的位置。您在那里执行的任何验证都只能是上下文无关的,而这种验证在实践中很少见。您可以更改正在设置的值 - 当调用者向您传递一个他们 [震惊恐怖] 希望您按原样存储的值时,这绝对是一场噩梦。您可以隐藏内部表示 - 太棒了,所以您要确保所有这些操作都是对称的,对吧?您已将公共界面与工作表下的更改隔离开来——如果您正在设计一个界面并且不确定直接访问某些内容是否可行,那么您应该继续设计。一些库期望这一点,但不是很多——反射、序列化、模拟对象都可以在公共字段中正常工作。继承此类,您可以覆盖默认功能 - 换句话说,您不仅可以隐藏实现,还可以使其不一致,从而真正混淆调用者。

我要离开的最后三个(N/A 或 D/C)...


我认为关键的论点是,“如果你正在设计一个界面并且不确定直接访问某些东西是否可以,那么你应该继续设计。”这是 getter/setter 最重要的问题:它们将类简化为(或多或少)公共字段的容器。然而,在真正的 OOP 中,对象不仅仅是数据字段的容器。它封装了状态和算法来操纵该状态。该声明的关键在于状态应该被封装并且只能由对象提供的算法进行操作。
J
Jorge Aguilar

好吧,我只想补充一点,即使有时它们对于您的变量/对象的封装和安全性是必要的,但如果我们想编写一个真正的面向对象程序,那么我们需要 STOP OVERUSING THE ACCESSORS,因为有时我们在没有必要的时候非常依赖它们,这几乎就像我们将变量公开一样。


H
Haakon Løtveit

编辑:我回答这个问题是因为有很多学习编程的人在问这个问题,而且大多数答案在技术上都很有能力,但如果你是新手,它们就不太容易理解。我们都是新手,所以我想我会尝试一个对新手更友好的答案。

两个主要是多态性和验证。即使它只是一个愚蠢的数据结构。

假设我们有这个简单的类:

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}

一个非常简单的类,它包含多少液体,以及它的容量是多少(以毫升为单位)。

当我这样做时会发生什么:

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;

好吧,你不会指望它会起作用,对吧?您希望进行某种健全性检查。更糟糕的是,如果我从未指定最大容量怎么办?哦,亲爱的,我们有问题。

但还有另一个问题。如果瓶子只是一种容器呢?如果我们有几个容器,所有容器都装有容量和数量的液体怎么办?如果我们可以创建一个接口,我们可以让程序的其余部分接受该接口,瓶子、塑料桶和各种东西都可以互换使用。那不是更好吗?由于接口需要方法,这也是一件好事。

我们最终会得到类似的东西:

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
}

伟大的!现在我们只需将 Bottle 更改为:

public class Bottle extends LiquidContainer {
  private int capacityMl;
  private int amountFilledMl;

  public Bottle(int capacityMl, int amountFilledMl) {
    this.capacityMl = capacityMl;
    this.amountFilledMl = amountFilledMl;
    checkNotOverFlow();
  }

  public int getAmountMl() {
    return amountFilledMl;
  }

  public void setAmountMl(int amountMl) {
     this.amountFilled = amountMl;
     checkNotOverFlow();
  }
  public int getCapacityMl() {
    return capacityMl;
  }

  private void checkNotOverFlow() {
    if(amountOfWaterMl > capacityMl) {
      throw new BottleOverflowException();
    }
}

我将把 BottleOverflowException 的定义作为练习留给读者。

现在请注意这是多么强大。我们现在可以通过接受 LiquidContainer 而不是 Bottle 来处理代码中的任何类型的容器。这些瓶子处理这类东西的方式各不相同。你可以让瓶子在它改变时将它们的状态写入磁盘,或者保存在 SQL 数据库或 GNU 知道的其他东西上的瓶子。

所有这些都可以有不同的方式来处理各种糟糕的事情。瓶子只是检查,如果它溢出它会抛出一个 RuntimeException。但这可能是错误的做法。 (有一个关于错误处理的有用讨论,但我故意在这里保持非常简单。评论中的人可能会指出这种简单方法的缺陷。;))

是的,似乎我们从一个非常简单的想法转变为快速获得更好的答案。

另请注意,您不能更改瓶子的容量。它现在一成不变。您可以通过将 int 声明为 final 来执行此操作。但如果这是一个列表,您可以清空它,向其中添加新内容,等等。你不能限制接触内脏。

还有第三件事不是每个人都解决过:getter 和 setter 使用方法调用。这意味着它们看起来像其他地方的普通方法。 DTO 和其他东西没有奇怪的特定语法,而是到处都有相同的东西。


N
Nick stands with Ukraine

我知道这有点晚了,但我认为有些人对表演感兴趣。

我做了一点性能测试。我写了一个类“NumberHolder”,它拥有一个整数。您可以使用 getter 方法 anInstance.getNumber() 读取该整数,也可以使用 anInstance.number 直接访问该数字。我的程序通过两种方式读取了 1,000,000,000 次。该过程重复五次并打印时间。我得到以下结果:

Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(时间1是直接方式,时间2是getter)

你看,吸气剂(几乎)总是快一点。然后我尝试了不同数量的周期。我没有使用 100 万,而是使用了 1000 万和 10 万。结果:

1000 万次循环:

Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

1000 万次循环,时间几乎相同。以下是 10 万(10 万)个周期:

Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

同样具有不同数量的循环,getter 比常规方式快一点。我希望这对你有所帮助。


调用函数来访问内存,而不是简单地加载对象的地址并添加偏移量来访问成员,会产生“显着”的开销。无论如何,VM 都有可能对您的吸气剂进行平面优化。无论如何,上述开销不值得失去 getter/setter 的所有好处。
Q
Qantas 94 Heavy

我们使用 getter 和 setter:

可重用性

在编程的后期执行验证

Getter 和 setter 方法是访问私有类成员的公共接口。

封装咒语

封装的口头禅是使字段私有而方法公开。

Getter 方法:我们可以访问私有变量。 Setter 方法:我们可以修改私有字段。

即使 getter 和 setter 方法没有添加新功能,我们也可以改变主意稍后再制作该方法

更好的;

更安全;和

快点。

在任何可以使用值的地方,都可以添加返回该值的方法。代替:

int x = 1000 - 500

利用

int x = 1000 - class_name.getValue();

通俗地说

https://i.stack.imgur.com/2lgj0.png

假设我们需要存储此 Person 的详细信息。此 Person 具有字段 nameagesex。这样做涉及为 nameagesex 创建方法。现在如果我们需要创建另一个人,就必须重新创建 nameagesex 的方法。

我们可以创建一个带有 getter 和 setter 方法的 bean class(Person),而不是这样做。所以明天只要我们需要添加一个新人,我们就可以创建这个 Bean class(Person class) 的对象(见图)。因此我们重用了 bean 类的字段和方法,这要好得多。


T
Thorbjørn Ravn Andersen

对于 Java 案例,我花了很长时间思考这个问题,我相信真正的原因是:

接口的代码,而不是实现接口只指定方法,而不是字段

换句话说,在接口中指定字段的唯一方法是提供写入新值的方法和读取当前值的方法。

这些方法是臭名昭著的 getter 和 setter....


好的,第二个问题;如果这是一个您不向任何人导出源代码的项目,并且您可以完全控制源代码……您是否从 getter 和 setter 中获得了任何东西?
在任何重要的 Java 项目中,您都需要对接口进行编码,以使事情易于管理和测试(想想模型和代理对象)。如果您使用接口,则需要 getter 和 setter。
M
Mohamed

除非您当前的交付需要,否则不要使用 getter setter 即不要过多考虑未来会发生什么,如果有任何事情要更改,它在大多数生产应用程序、系统中都是一个更改请求。

想简单,容易,需要时增加复杂性。

我不会仅仅因为我认为它是正确的或者我喜欢这种方法而利用对深厚技术知识的企业主的无知。

我编写了没有 getter setter 的大型系统,只有访问修饰符和一些方法来验证 n 执行 biz 逻辑。如果你绝对需要。使用任何东西。


q
quillbreaker

它对于延迟加载很有用。假设有问题的对象存储在数据库中,除非你需要它,否则你不想去获取它。如果对象由 getter 检索,则内部对象可以为 null,直到有人请求它,然后您可以在第一次调用 getter 时获取它。

我在一个交给我的项目中有一个基页类,它从几个不同的 Web 服务调用加载一些数据,但这些 Web 服务调用中的数据并不总是在所有子页面中使用。 Web 服务,为了所有的好处,开创了“慢”的新定义,所以如果你不需要,你就不想进行 Web 服务调用。

我从公共字段转移到 getter,现在 getter 检查缓存,如果不存在则调用 Web 服务。因此,只需稍加包装,就可以阻止许多 Web 服务调用。

因此,getter 使我免于尝试在每个子页面上弄清楚我需要什么。如果我需要它,我会调用 getter,如果我还没有它,它会为我找到它。

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }

那么getter调用setter吗?
我添加了一个代码示例,说明我过去是如何完成的 - 本质上,您将实际类存储在受保护成员中,然后在 get 访问器中返回该受保护成员,如果未初始化则对其进行初始化。
j
jdehaan

到目前为止,我在答案中错过了一个方面,即访问规范:

对于成员,您只有一个用于设置和获取的访问规范

对于 setter 和 getter,您可以对其进行微调并分别定义


J
John Millikin

在不支持“属性”(C++、Java)或在将字段更改为属性(C#)时需要重新编译客户端的语言中,使用 get/set 方法更容易修改。例如,向 setFoo 方法添加验证逻辑不需要更改类的公共接口。

在支持“真实”属性的语言(Python、Ruby,也许是 Smalltalk?)中,获取/设置方法没有意义。


回复:C#。如果您向 get/set 添加功能,是否仍需要重新编译?
@steamer25:抱歉,打错了。我的意思是必须重新编译该类的客户端。
向 setFoo 方法添加验证逻辑不需要在语言级别更改类的接口,但它确实会更改实际接口,也就是契约,因为它会更改前置条件。为什么要让编译器不将其视为一项重大更改?
@R.MartinhoFernandes 如何解决这个“破碎”问题?编译器无法判断它是否中断。当您为他人编写库时,这只是一个问题,但您正在将其作为通用 zOMG 在这里成为龙!
如答案中所述,需要重新编译是编译器可以让您意识到可能的重大更改的一种方式。我写的几乎所有东西实际上都是“他人的图书馆”,因为我不是一个人工作。我编写的代码具有项目中其他人将使用的接口。有什么不同?地狱,即使我将成为这些接口的用户,我为什么要让我的代码达到较低的质量标准?我不喜欢使用麻烦的接口,即使我是编写它们的人。
J
Justin Niessner

OO设计的基本原则之一:封装!

它为您提供了许多好处,其中之一是您可以在后台更改 getter/setter 的实现,但只要数据类型保持不变,该值的任何使用者都将继续工作。


封装 getter 和 setter 提供的东西薄得可笑。请参阅here
如果您的公共接口声明“foo”的类型为“T”并且可以设置为任何内容,那么您永远无法更改它。您不能稍后决定将其设为“Y”类型,也不能强加诸如大小限制之类的规则。因此,如果您的公共 get/set 什么都不做 set/get,那么您将没有获得公共字段无法提供的任何东西,并且使用起来更加麻烦。如果你有一个约束,比如对象可以设置为空,或者值必须在一个范围内,那么是的,需要一个公共的 set 方法,但你仍然可以通过设置这个值来提供一个合同改变
为什么合同不能改?
如果合同发生变化,为什么还要继续编译?
a
abarnert

您应该在以下情况下使用 getter 和 setter:

你正在处理的东西在概念上是一个属性,但是:你的语言没有属性(或一些类似的机制,如 Tcl 的变量跟踪),或者你的语言的属性支持不足以满足这个用例,或者你的语言的(或有时您的框架的)惯用约定鼓励此用例的 getter 或 setter。

您的语言没有属性(或一些类似的机制,如 Tcl 的变量跟踪),或者

您的语言的属性支持不足以满足此用例,或者

您的语言(或有时是您的框架)的惯用约定鼓励此用例的 getter 或 setter。

所以这很少是一个一般的OO问题;这是一个特定于语言的问题,对于不同的语言(和不同的用例)有不同的答案。

从 OO 理论的角度来看,getter 和 setter 是没有用的。你的类的接口是它的作用,而不是它的状态。 (如果没有,你写错了类。)在非常简单的情况下,类的作用只是,例如,表示直角坐标中的一个点,* 属性是接口的一部分; getter 和 setter 只是模糊了这一点。但除了非常简单的情况外,属性、getter 和 setter 都不是接口的一部分。

换句话说:如果您认为您的类的使用者甚至不应该知道您有一个 spam 属性,更不用说能够随意更改它,那么给他们一个 set_spam 方法是您最后要做的事情想要做。

* 即使对于那个简单的类,您也不一定希望允许设置 xy 值。如果这真的是一个类,它不应该有 translaterotate 等方法吗?如果它只是一个类,因为您的语言没有记录/结构/命名元组,那么这实际上不是 OO 的问题……

但是没有人做过通用的面向对象设计。他们正在使用特定语言进行设计和实施。在某些语言中,getter 和 setter 远非无用。

如果您的语言没有属性,那么表示概念上是属性但实际上是计算或验证等的东西的唯一方法是通过 getter 和 setter。

即使您的语言确实具有属性,也可能存在它们不充分或不合适的情况。例如,如果你想允许子类控制属性的语义,在没有动态访问的语言中,子类不能用计算属性代替属性。

至于“如果我以后想改变我的实现怎么办?”问题(在 OP 的问题和接受的答案中以不同的措辞重复多次):如果它确实是纯粹的实现更改,并且您从属性开始,则可以将其更改为属性而不影响接口。当然,除非你的语言不支持。所以这真的是同样的情况。

此外,遵循您正在使用的语言(或框架)的习惯用法也很重要。如果你用 C# 编写漂亮的 Ruby 风格的代码,那么除了你之外,任何有经验的 C# 开发人员都将难以阅读它,这很糟糕。一些语言的约定比其他语言具有更强的文化。Java 和 Python 在惯用 getter 方面处于相反的两端,碰巧拥有两种最强大的文化,这可能不是巧合。

除了人类读者之外,还会有一些库和工具希望您遵守约定,如果您不遵守约定,则会使您的生活更加艰难。将 Interface Builder 小部件挂接到除 ObjC 属性之外的任何东西,或者使用某些没有 getter 的 Java 模拟库,只会让您的生活变得更加困难。如果这些工具对您很重要,请不要与它们抗争。


a
andrers52

从面向对象设计的角度来看,这两种选择都可能通过削弱类的封装而损害代码的维护。如需讨论,您可以查看这篇出色的文章:http://typicalprogrammer.com/?p=23


b
bobobobo

代码进化private 非常适合您需要数据成员保护。最终,所有类都应该是一种“小程序”,它们具有定义明确的接口你不能只搞砸的内部结构。

也就是说,软件开发并不是要设置课程的最终版本,就好像您在第一次尝试时要按一些铸铁雕像一样。当您使用它时,代码更像是粘土。它会随着您的开发而发展,并更多地了解您正在解决的问题领域。在开发过程中,类之间的交互可能超出了它们应有的程度(您计划排除依赖关系)、合并在一起或拆分。所以我认为辩论归结为人们不想虔诚地写作

int getVar() const { return var ; }

所以你有了:

doSomething( obj->getVar() ) ;

代替

doSomething( obj->var ) ;

getVar() 不仅在视觉上很嘈杂,而且给人一种错觉,即 gettingVar() 在某种程度上是一个比实际情况更复杂的过程。您(作为课程作者)如何看待 var 的神圣性对于您的课程的用户来说尤其令人困惑,如果它有一个 passthru setter - 那么看起来您设置这些门是为了“保护”您坚持的东西是有价值的,(var 的神圣性)但即使您承认 var 的保护也没有多大价值,因为任何人都可以进入并且 set var 获得他们想要的任何价值,没有你甚至偷看他们在做什么。

所以我编程如下(假设是“敏捷”类型的方法——即当我编写代码时不知道它会做什么/没有时间或经验来计划一个精心设计的瀑布式界面集):

1) 从具有数据和行为的基本对象的所有公共成员开始。这就是为什么在我的所有 C++“示例”代码中,您会注意到我到处都使用 struct 而不是 class

2) 当对象的数据成员的内部行为变得足够复杂时,(例如,它喜欢以某种顺序保持内部 std::list),编写访问器类型函数。因为我自己编程,所以我并不总是立即设置成员 private,但在类演变的某个地方,成员将被“提升”为 protectedprivate

3) 给出了完全充实并对其内部有严格规则的类(即,他们确切地知道他们在做什么,并且你不能用它的内部来“操”(技术术语)) class 名称、默认私有成员和仅允许成为 public 的少数成员。

我发现这种方法可以让我避免在类演化的早期阶段,当大量数据成员被迁移出去、转移等等时,坐在那里虔诚地编写 getter/setter。


“......一个定义明确的接口,你不能只是在设置器中搞砸”和验证。
G
GeZo

考虑使用访问器的充分理由是没有属性继承。请参见下一个示例:

public class TestPropertyOverride {
    public static class A {
        public int i = 0;

        public void add() {
            i++;
        }

        public int getI() {
            return i;
        }
    }

    public static class B extends A {
        public int i = 2;

        @Override
        public void add() {
            i = i + 2;
        }

        @Override
        public int getI() {
            return i;
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.add();
        System.out.println(a.i);
        System.out.println(a.getI());
    }
}

输出:

0
0
4

P
Pritam Banerjee

Getter 和 setter 用于实现面向对象编程的两个基本方面,它们是:

抽象封装

假设我们有一个 Employee 类:

package com.highmark.productConfig.types;

public class Employee {

    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
      return firstName;
    }
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
         this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName(){
        return this.getFirstName() + this.getMiddleName() +  this.getLastName();
    }
 }

在这里,全名的实现细节对用户是隐藏的,用户不能直接访问,这与公共属性不同。


对我来说,拥有大量没有任何独特功能的 getter 和 setter 是没有用的。 getFullName 是一个例外,因为它做了其他事情。只公开三个变量,然后保持 getFullName 将使程序更易于阅读,但仍然隐藏了全名。一般来说,如果 a.getter 和 setter 我完全可以接受。他们做一些独特的事情和/或 b。你只有一个,是的,你可以有一个公开的决赛等等,但不
这样做的好处是您可以更改类的内部结构,而无需更改接口。比如说,你有一个而不是三个属性 - 一个字符串数组。如果您一直在使用 getter 和 setter,则可以进行更改,然后更新 getter/setter 以知道 names[0] 是名字,names[1] 是中间名,等等。但是如果您只使用公共属性,您还必须更改每个访问 Employee 的类,因为他们一直使用的 firstName 属性不再存在。
@AndrewHows 从我在现实生活中看到的情况来看,当人们更改类的内部时,他们也会更改接口并对所有代码进行重大重构
u
user982042

如果您不需要任何验证,甚至不需要维护状态,即一个属性依赖于另一个属性,那么我们需要在一个属性发生更改时维护状态。您可以通过公开字段而不使用 getter 和 setter 来保持简单。

我认为随着程序的发展,OOP 使事情变得复杂,它成为开发人员扩展的噩梦。

一个简单的例子;我们从 xml 生成 c++ 头文件。标头包含不需要任何验证的简单字段。但仍然像在 OOPS 访问器中一样,我们按照以下方式生成它们。

const Filed& getfield() const
Field& getField() 
void setfield(const Field& field){...} 

这非常冗长,不是必需的。一个简单的

struct 
{
   Field field;
};

足够且可读。函数式编程没有数据隐藏的概念,他们甚至不需要它,因为它们不会改变数据。


S
Sumit Singh

getter 和 setter 方法是访问器方法,这意味着它们通常是用于更改私有类成员的公共接口。您使用 getter 和 setter 方法来定义属性。您可以将 getter 和 setter 方法作为类外部的属性来访问,即使您在类中将它们定义为方法。类之外的那些属性可以具有与类中的属性名称不同的名称。

使用 getter 和 setter 方法有一些优点,例如能够让您创建具有复杂功能的成员,您可以访问类似的属性。它们还允许您创建只读和只写属性。

尽管 getter 和 setter 方法很有用,但您应该注意不要过度使用它们,因为在某些情况下,它们会使代码维护更加困难。此外,它们还提供对您的类实现的访问,例如公共成员。 OOP 实践不鼓励直接访问类中的属性。

当您编写类时,总是鼓励您将尽可能多的实例变量设为私有,并相应地添加 getter 和 setter 方法。这是因为有好几次您可能不想让用户更改您的类中的某些变量。例如,如果您有一个私有静态方法来跟踪为特定类创建的实例数,您不希望用户使用代码修改该计数器。只有构造函数语句应该在调用该变量时递增该变量。在这种情况下,您可能会创建一个私有实例变量,并只允许计数器变量使用 getter 方法,这意味着用户只能使用 getter 方法检索当前值,而无法设置新值使用setter方法。创建不带 setter 的 getter 是使类中的某些变量只读的简单方法。


a
aditya lath

DataStructure 和 Object 是有区别的。

数据结构应该暴露其内在而不是行为。

一个物体不应该暴露它的内在,但它应该暴露它的行为,这也被称为得墨忒耳法则

大多数 DTO 更多地被认为是一种数据结构,而不是对象。他们应该只公开他们的数据而不是行为。在 DataStructure 中使用 Setter/Getter 将暴露行为而不是其中的数据。这进一步增加了违反得墨忒耳法则的机会。

鲍勃叔叔在他的书清洁代码中解释了得墨忒耳定律。

有一个著名的启发式叫做得墨忒耳定律,它说模块不应该知道它所操作的对象的内部结构。正如我们在上一节中看到的,对象隐藏了它们的数据并暴露了操作。这意味着对象不应通过访问器公开其内部结构,因为这样做是公开而不是隐藏其内部结构。更准确地说,得墨忒耳定律说类 C 的方法 f 应该只调用以下方法: C 由 f 创建的对象 作为参数传递给 f 的对象 保存在 C 的实例变量中的对象 该方法应该不要在任何允许的函数返回的对象上调用方法。换句话说,与朋友交谈,而不是与陌生人交谈。

因此,据此,LoD 违规示例是:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

这里,函数应该调用它的直接朋友的方法,这里是ctxt,它不应该调用它的直接朋友的朋友的方法。但这条规则不适用于数据结构。所以在这里,如果 ctxt、option、scratchDir 是数据结构,那么为什么要用一些行为来包装他们的内部数据并违反 LoD。

相反,我们可以做这样的事情。

final String outputDir = ctxt.options.scratchDir.absolutePath;

这满足了我们的需求,甚至不违反 LoD。

灵感来自 Robert C. Martin(鲍勃叔叔)的 Clean Code


P
Pete

此外,这是为了“面向未来”您的课程。特别是,从字段更改为属性是 ABI 中断,因此如果您稍后决定需要更多逻辑而不仅仅是“设置/获取字段”,那么您需要中断 ABI,这当然会给任何事情带来问题否则已经针对您的班级进行了编译。


我想改变 getter 或 setter 的行为并不是一个重大的改变。
@R.MartinhoFernandes 并不总是必须如此。 TBYS
J
Jason Baker

另一种用途(在支持属性的语言中)是 setter 和 getter 可以暗示操作是不平凡的。通常,您希望避免在属性中做任何计算上昂贵的事情。


我永远不会期望 getter 或 setter 是一项昂贵的操作。在这种情况下,最好使用工厂:使用您需要的所有设置器,然后调用昂贵的 executebuild 方法。
R
Rakesh Singh

getter/setter 的一个相对现代的优势是可以更轻松地在标记(索引)代码编辑器中浏览代码。例如,如果您想查看谁设置了成员,您可以打开 setter 的调用层次结构。

另一方面,如果成员是公共的,则这些工具无法过滤对该成员的读/写访问权限。因此,您必须艰难地使用该成员的所有用途。


您可以做正确的 clic> 查找成员的用法,就像在 getter/setter 上一样