您当前的位置: 首页 > 

Peter_Gao_

暂无认证

  • 0浏览

    0关注

    621博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

避免getter/setter 中的那些糟糕设计

Peter_Gao_ 发布时间:2021-08-06 11:07:59 ,浏览量:0

不要生成 getter 和 setter!

不!!!!不要点击生成 getter 和 setter 选项!!!

我喜欢这条规则:“不要使用访问器和修改器。” 像任何好的规则一样,这条规则注定要被打破。但当?

首先,让我清楚我在说什么:在考虑了一系列更好的替代方案之后,向 OO 类添加 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).

诸如此类的事情就是著名图书馆拥有糟糕的遗留类的原因。你有没有注意到大多数语言都有一个Dictionary数据结构,但它是Map在 Java 中调用的?Dictionary实际上是JDK 1.0中引入的一个接口,但是它有问题,最终不得不被替换为Map.

getter 和 setter 实际上可能很危险

给你讲一个关于朋友的故事。好的?我说的朋友!!!

有一天,这位朋友上班,发现世界各国的几十个知名网站都有母公司主网站(不是他们自己的)的页眉和导航,而且都是英式英语。运营团队疯狂地重新启动了全球数百台服务器,因为在这些服务器运行的前半小时左右,一切正常。然后(砰!)突然之间会发生一些事情,使整个事情变得横向。

罪魁祸首是所有这些不同站点都在使用的共享平台深处的setter 方法。一小段按计划运行的代码碰巧最近在这次惨败中更新,通过调用这个 setter 改变了确定站点标题和语言的基础值。

如果你只有一个吸气剂,事情可能同样糟糕。至少在 Java 中,从 getter 返回引用类型为调用者提供了该引用,现在它可以由调用者以意想不到的方式进行操作。让我示范一下。

public class Debts {
  private List debts;

  public List getDebts() {
    return debts;
  }
}

好吧,这似乎很合理。我需要能够看到一个人的债务才能给他们一个声明。嗯?你说什么?现在可以加债了吗?拉屎!那是怎么发生的!

Debts scottsDebts = DebtTracker.lookupDebts(scott);
List 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 和 setter 几乎没有任何意义。但是,只要您实际在该方法中执行某些操作,您就可以编写一些接近getter/setter 功能的内容。

两个例子:

  • 在 setter 中,在根据某些输入更新此对象中的状态之前,我们验证输入。输入验证是附加功能。

  • getter 的返回类型是一个接口。因此,我们将实现与公开的接口分离。

看,我在这里真正提倡的是对 getter 和 setter 的不同立场和哲学。与其说永远不要使用访问器和修改器,我想为您提供我在使用之前尝试用尽的选项列表:

  • 我的“默认”是从私有最终成员开始,仅由构造函数设置。没有 getter 或 setter!

  • 如果另一个班级绝对需要看到这个成员,我想想为什么。我尝试查看是否存在可以公开的行为,并为该行为创建一个方法。

  • 如果出于某种原因绝对有必要,那么我会放松到包私有(在 Java 中)并将成员仅公开给同一个包中的其他类,但不会再公开。

  • 好的,数据用例呢?从字面上看,我可能需要一个对象来跨某种接口边界传递数据(比如文件系统、数据库、Web 服务或其他东西)。我仍然不喜欢 getter 和 setter。我用所有包私有成员创建了这个类,我认为它只是一个属性包。我尝试在应用程序的边界将它们限制在它们自己的包和层中。

  • 我会考虑在公共 API 中为数据用例创建一个 getter 和一个 setter,比如假设我正在编写一个旨在用作许多其他应用程序依赖项的库。但我只会在用尽所有这些列表项后才考虑它。

大师的智慧

一个简短的后记。显然,世界上存在关于 getter 和 setter 的争论。重要的是要知道,显然有一个像 Robert C.(“鲍勃叔叔”)马丁这样的“大师”阵营,他们支持避免 getter 和 setter。在Clean Code一书中,Martin 在第 6 章中写道:

Bean 具有由 getter 和 setter 操作的私有变量。bean 的准封装似乎让一些 OO 纯粹主义者感觉更好,但通常没有其他好处。

Josh Bloch 在Effective Java 的 第 14 条中有着微妙的立场,稍微支持公共类的 getter 和 setter,而稍微反对其他类。他最终基本上说他真正关心的是可变性,我在上面提到了这一点:

总之,公共类不应该暴露可变字段。公共类公开不可变字段的危害较小,但仍然值得怀疑。然而,有时需要包私有或私有嵌套类公开字段,无论是可变的还是不可变的。

进一步阅读

以下是比我更聪明的人的一些有用的想法。

告诉,不要问

为什么 getter 和 setter 方法是邪恶的

访问者是邪恶的

讨论(99)
订阅

图片

 

xowap 个人资料图片

雷米🤖
• 17 年 12 月 9 日

好吧,我曾经认为 getter 和 setter 通常是无用的。直到有一天,一旦设置了一个值,我不得不做一些计算,在那里我不得不重新考虑我的整个代码,因为该成员不再可以直接访问,但有一个 getter 和一个 setter。

所以我想说,把 getter 和 setter 放在未来的证明上,而不是很贵。

然而我也想说这个问题几乎只存在于 Java 中,因为其他语言有办法解决这个问题(Python

关注
打赏
1664521772
查看更多评论
立即登录/注册

微信扫码登录

0.0391s