尽可能避免使用 getter 和 setter
不!!!不要点击生成 getters 和 setters 选项!!!
我喜欢这条规则:“不要使用访问器和修改器”。就像任何好的规则一样,这条规则注定会被打破。但是什么时候打破呢?
首先,让我明确一下我的观点:在考虑了一系列更好的替代方案之后,向面向对象类添加 getter 和 setter 应该是最后的选择。我相信,仔细分析后会得出这样的结论:在大多数情况下,getter 和 setter 都是有害的。
有什么危害?
首先我要指出的是,我所说的“危害”可能根本就没有任何危害。在某些情况下,以下分类是完全合理的:
// Car1.java
public class Car1 {
public Engine engine;
}
不过,请注意,当你看到类似的东西时,你可能会感到胃部收紧、头发竖起、肌肉紧张。
现在,我想指出的是,该类(一个带有公共类成员的公共类)与下面的类(一个带有通过 getter 和 setter 暴露的私有成员的公共类)之间没有任何有意义的功能差异。在Car1.java和Car2.java 这两个类中,我们得到的结果基本相同。
// Car2.java
public class Car2 {
private Engine engine;
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
}
为了说明这一点,我在Car1.java或Car2.java中读取和写入引擎:
// Car1 member read and write
Car1 car1 = new Car1();
logger.debug("Car1's engine is {}.", car1.engine);
car1.engine = new HemiEngine();
// Car2 member read and write
Car2 car2 = new Car2();
logger.debug("Car2's engine is {}.", car2.getEngine());
car2.setEngine(new HemiEngine();
重点是,我能用Car2.java 做的任何事,都能用Car1.java 做,反之亦然。这一点很重要,因为我们被教导说,看到Car1.java
就得小心翼翼。看到那个公共成员,我们就会说,不安全!引擎没有任何保护!任何人都可以为所欲为!啊啊啊啊啊啊啊啊啊啊啊啊啊!
然而,当我们看到Car2.java时,不知为何,我们松了一口气。抱歉,我个人觉得这挺有意思的,因为这两件事的保护措施其实是一样的(根本不存在)。
可能出现什么问题?
以下是直接公开单个私有成员、具有相同名称且不提供其他功能的公共 getter 和 setter 的一些缺点。
Getter 和 Setter 是孤立变化的虚假保险政策
getter 和 setter 的一个优点是,如果需要更改类成员的类型,则可以通过将现有的 getter 简单地从内部类型转换为先前公开的类型,将更改限制在类内部。
// Car2.java, engine changed to motor
public class Car2 {
private Motor motor;
public Engine getEngine() {
return convertToEngine(motor);
}
public void setEngine(Engine engine) {
this.motor = convertToMotor(engine);
}
}
我的问题是,程序员有多少次需要这样做?我从事软件行业这么多年,从来没做过这种事。我一次也没能利用 getter 和 setter 提供的“假保险”政策。
engine
此外,如果从一开始就没有暴露(假设它是私有的或包私有的),那么这个论点就毫无意义了。只需暴露行为,而不是状态,你就永远不需要担心修改实现的灵活性。
这种认为私有成员不应该被暴露的认识,引发了另一个认识:这个论点是同义反复的。Getter和 Setter 暴露了私有成员,并且它们的存在理由就建立在私有成员被暴露这一事实之上。
Getter 和 setter 公开实现细节
假设我只向您提供了我的 Car 对象的以下 API:
_________________________________
| Car |
|---------------------------------|
| + getGasAmount(): Liters |
| + setGasAmount(liters: Liters) |
|_________________________________|
如果你假设这是一辆汽油驱动的汽车,内部会以升为单位记录汽油量,那么你的预测 99.999% 都是正确的。这真的很糟糕,这就是为什么 getter 和 setter 暴露了实现/违反了封装性。现在这段代码很脆弱,很难修改。如果我们想要一辆氢燃料汽车怎么办?我们现在必须把整个 Car 类都扔掉。如果只使用像这样的行为方法就更好了fillUp(Fuel fuel)
。
这就是为什么一些著名的库会留下糟糕的遗留类。你有没有注意到,大多数语言都有一个数据结构,但在 Java 中Dictionary
却被这样调用?实际上是 JDK 1.0 中引入的一个接口,但它存在问题,最终不得不被取代。Map
Dictionary
Map
Getter 和 Setter 实际上可能很危险
我给你讲个关于我朋友的故事。好吗?我说的是朋友!!!
有一天,这位朋友上班,发现全球几十个知名网站的标题和导航都和母公司主网站(而非自己的网站)一样,而且用的都是英式英语。运营团队疯狂地重启了全球数百台服务器,因为这些服务器在最初半个小时左右运行正常。然后(砰!)突然间,事情发生了变化,让整个系统都乱了套。
罪魁祸首是所有这些不同网站都在使用的共享平台深处的一个setter方法。恰好,在这次灾难发生前不久,一小段按计划运行的代码进行了更新,通过调用这个setter方法,改变了决定网站标题和语言的底层值。
如果只有一个 getter,情况可能同样糟糕。至少在 Java 中,从 getter 返回引用类型会将该引用提供给调用者,而调用者可能会以意想不到的方式对其进行操作。让我来演示一下。
public class Debts {
private List<Debt> debts;
public List<Debt> getDebts() {
return debts;
}
}
好吧,这似乎很合理。我需要查看一个人的债务才能给他们一份账单。啊?你说什么?现在就可以添加债务了?妈的!怎么会这样!
Debts scottsDebts = DebtTracker.lookupDebts(scott);
List<Debt> debts = scottsDebts.getDebts();
// add the debt outside scotts debts, outside the debt tracker even
debts.add(new Debt(new BigDecimal(1000000)));
// prints a new entry with one million dollars
DebtTracker.lookupDebts(scott).printReport();
唷!
防止这种情况发生的一种方法是返回一个副本。另一种方法是使用不可变成员。不过,最好的方法是完全不以任何方式暴露该成员,而是将操作该成员的行为放在类内部。这样可以实现完全的实现隔离,并且只需在一个地方进行修改。
当 getter 有意义时
等一下!如果访问器和修改器有这么多缺点,为什么还要使用它们呢?
我确信,仅仅返回类成员的 getter 和 setter 几乎毫无意义。不过,只要你在该方法中确实做了一些事情,你就可以编写一些类似getter/setter 功能的代码。
两个例子:
-
在 setter 中,在根据某些输入更新对象的状态之前,我们会验证输入。输入验证是附加功能。
-
getter 的返回类型是接口。因此,我们将实现与暴露的接口解耦。
瞧,我真正提倡的是对 getter 和 setter 的不同立场和理念。与其说是说永远不要使用访问器和修改器,不如给你列出一些我在使用它们之前尝试过的各种选项:
-
我的“默认”设置是从一个私有 final 成员开始,仅由构造函数设置。没有 getter 或 setter!
-
如果另一个类确实需要访问这个成员,我会思考原因。我会尝试寻找可以公开的行为,并为该行为创建一个方法。
-
如果由于某种原因绝对有必要,那么我会放宽到包私有(在 Java 中)并且仅将成员暴露给同一包中的其他类,但不会进一步暴露。
-
好的,那么数据用例呢?实际上,我可能需要一个对象来跨某种接口边界传递数据(比如传递到文件系统、数据库、Web 服务或其他对象)。我仍然不会费力地处理 getter 和 setter。我创建了一个包含所有包私有成员的类,并将其视为一个属性包并使用它。我尝试将它们限制在应用程序边界的各自的包和层中。
-
我会考虑在公共 API 中为数据用例创建 getter 和 setter,比如说,我正在编写一个库,打算将其用作许多其他应用程序的依赖项。但我只会在用尽所有这些列表项之后才会考虑。
大师的智慧
简短的后记。显然,世界上关于 getter 和 setter 的争论一直存在。重要的是要知道,像罗伯特·C·马丁(绰号“鲍勃叔叔”)这样的“大师”显然是主张避免使用 getter 和 setter 的。在《代码整洁之道》一书的第六章中,马丁写道:
Bean 拥有由 getter 和 setter 操作的私有变量。Bean 的准封装性似乎让一些面向对象纯粹主义者感觉良好,但通常不会带来其他好处。
Josh Bloch 在《Effective Java》 第 14 条中持一种微妙的立场,他略微赞成在公共类中使用 getter 和 setter,而略微反对其他类。他最终基本上说,他真正关心的是可变性,这一点我在上面提到过:
总而言之,公共类永远不应该暴露可变字段。公共类暴露不可变字段虽然危害较小,但仍然值得商榷。然而,有时包私有或私有嵌套类暴露字段(无论是可变的还是不可变的)是可取的。
进一步阅读
以下是一些来自比我聪明的人的有益想法。
文章来源:https://dev.to/scottshipp/avoid-getters-and-setters-whenever-possible-c8m