
加拿大温哥华
自从 1978 年毕业于不列颠哥伦比亚大学以来,我几乎一直与计算机为伴。自 2005 年以来,我一直是全职 Linux 用户,1986 年至 2005 年是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。
在技术方面,我的大部分职业生涯都在担任顾问,从事数据分析和可视化工作,特别是空间数据分析。我拥有大量的相关编程经验,使用 C、awk、Java、Python、PostgreSQL、PostGIS,最近还使用 Groovy。我对 Julia 非常感兴趣。我还构建了一些桌面和 Web 应用程序,主要使用 Java,最近使用 Grails,前端使用大量 JavaScript,数据库选择 PostgreSQL。
除此之外,我花费大量时间撰写提案、技术报告,当然还有在 https://www.opensource.com 上发表文章。
已著评论
感谢您的评论,Bernd。很高兴能与读者互动,非常感谢。
让我们首先回顾一下我们想要完成什么 - 我们去一家大宗商品商店购买一大堆散装包装。我们把它们带回办公室。我们拆开散装包装,并将单位重新分配到价值大致相等的篮子中,然后我们将这些篮子送给邻居。
让我们看看 Golub 在这里的原则“让对象工作”,以及我的类如何或不符合。
Pack 类有一个名为 unpack() 的方法。这正是遵循 Golub 的原则 - 这就是我告诉 Pack 给我一个其组成 Unit 实例列表的方式。
同样,Bought 也有一个 unpack() 方法,它给了我购买的 Pack 实例数量中所有 Unit 实例的列表。所以再次遵循 Golub 的规则。
在这一点上,我们可以注意到我们实际上购买了一些自解包散装材料,将它们放在办公室的地板上,并告诉它们解包自己;我们最终得到一个组成部分的列表,称为 Units。
让我们看看您链接中提到的其他规则。
“违反封装原则” - 实际上,getter 和 setter 的目的是帮助封装。对象外部的东西无法到达对象内部;他们必须通过已知的途径进行沟通。最重要的是,我的类实际上是不可变的,正如我提到的 - 没有 setter - 因此不可能注入任何东西;字段值在创建 Unit、Pack 和 Bought 实例时建立,并且不能随后更改。此外,字段值无法从外部查看,因为字段是私有的。因此,我可以随意更改内部结构,只要我可以继续遵守 getter 方法建立的合同即可。Unit 本身是一个元组,仅用于封装单元概念的关键特征 - 单元是什么,谁制造的以及它的价值是什么。我们需要知道这些事情,因为我们将把这些单元放入篮子中,而收到篮子的人需要能够看到里面有什么并计划如何使用它们。
“暴露实现细节” - 同样,getter 和 setter 的目的是避免暴露实现细节。经典示例是“几何”概念,用户需要了解封装几何的一些细节 - 例如,它的长度或面积 - 而无需知道它是如何存储的。除此之外,还增加了 Golub 风格的需求,例如“计算此几何与另一个几何的交集多边形”。
您提到的文章中提出的另一点是“对象是/不是简单的数据持有者”。我同意对象不仅仅是简单的数据持有者。让我们以 Java String 类为例。是的,它持有一个不可变的字符串值。但它也有很多类似 getter 的方法,例如 length()、substring() 等等。
我的类 Pack 和 Bought 也不是简单的数据类。它们包括 unpack() 方法,这是我对它们唯一要求的行为。Unit 是一个简单的数据类 - 它是一个元组。在我的问题空间中,我不需要告诉 Unit 做任何事情;我只需要移动它。
我可能应该将 Unit 分发功能的 List 放在一个单独的类中,该类的构造函数接受 Unit 实例的 List,并返回 Hamper 实例的 List 的方法。相反,我选择动态创建这些篮子,只是在创建时将它们打印出来。
顺便说一句,我认为一个糟糕的设计是将 Bought 实例的“packs”数组馈送到假设的分发功能中,因为这将违反低耦合原则。也就是说,Bought、Pack 或 Unit 中的更改可能会导致分发功能发生更改。而它的唯一“输入”是 Unit 实例的 List,Bought 或 Pack 的更改是无关紧要的。
回到您的评论 - getter 和 setter 的重点在于它们不会暴露您类的内部结构。Getter 尤其如此,setter 在需要可变对象时,封装(隐藏)实现细节。您只能声称您知道一些正在存储的内容;而不是如何存储,也不是涉及哪种类型的计算。
似乎让很多人困扰的是为类的每个私有字段自动生成 getter 和 setter 的想法。当然,这不一定是一个普遍的好主意 - 尤其是 setter,因为人们应该始终考虑类的实例是否应该是不可变的。当然,一个类也可能具有仅在内部才有意义的字段,并且为这种字段提供 getter,更不用说 setter 了,将是毫无意义的。
但是,假设类适合用途,并且它们仅向用户提供必要的通信途径,那么有什么依据可以认为这种对 getter 和 setter 的有限使用是不好的呢?
在我的例子中,由于我的类没有 setter,因此您不能声称可以将任何旧数据注入到实例中。
此外,由于您无法从我的 Pack 和 Bought 类的公共方法中确定我是保留合并的 Unit 实例列表,还是只是在调用 unpack() 时(惰性地)实现该列表,因此您不能真正声称您了解所有关于这些类的内部实现细节,仅仅因为我为您提供了等同于散装包装标签上的 getter。
在我的 Pack 或 Bought 实例中,您可以争辩说我的 getter 是不必要的,因为我似乎不需要知道它们内部有什么。但也许我希望 Pack 具有等同于散装包装上的“标签”的东西,告诉我里面有什么而无需解包,或者 Bought 告诉我我总共有多少个 Units 的东西而无需解包所有散装包装并计算组件。在任何情况下,我都没有在这个程序中使用这些方法,因此说我不需要 getter 当然是有效的。
这是一个不错的讨论
https://stackoverflow.com/questions/34805276/do-setter-and-getter-methods-breaks-encapsulation
迟来的感谢,感谢您的评论,Bernd。我认为您的阐述中缺少一些内容,例如在您的 Units 类中,您的 for 循环在做任何事情之前就有点结束了。无论如何,我想我们可以争论一下,重构像这里的 while 循环这样紧凑的代码是否真的能带来任何好处。
我希望的是,一些函数式编程向导会想出一个函数式替代我的 while 循环的方法(它不会修改其输入并且是高效的)。
另外,恕我直言,我完全不同意您关于“会违反信息隐藏的 getter 方法”的评论 - 重点是,对于任何人,无论是定义类的人还是以后使用该类的人,都没有任何可维护性好处来暴露内部实现细节。相反,最好通过 getter 和 setter 以及可能的接口来定义合同,这让类维护者可以自由地在必要时更改内部结构。
这称为“统一访问原则”。您可以在很多地方阅读到它,例如这里
https://en.wikipedia.org/wiki/Uniform_access_principle
在减少样板式 getter/setter 痛苦的众多不同方法中,Project Lombok 就是其中之一,它使用注解来自动生成 getter 和 setter。Baeldung 对此进行了很好的实践概述
https://www.baeldung.com/intro-to-project-lombok