面向对象编程的最大优点之一是封装,我们(或者至少是我)学到的“真理”之一是成员应该始终设为私有并通过访问器和修改器提供方法,从而确保验证和验证更改的能力。

不过,我很好奇这在实践中到底有多重要。特别是,如果您有一个更复杂的成员(例如集合),则很容易将其公开,而不是创建一堆方法来获取集合的键,从集合中添加/删除项目, ETC。

您一般遵守规则吗?您的答案是否会根据它是为您自己编写的代码还是为别人编写的代码而变化?被别人利用?我是否因为这种混淆而忽略了更微妙的原因?

有帮助吗?

解决方案

这取决于。这是必须务实决定的问题之一。

假设我有一个代表一个点的类。我可以为 X 和 Y 坐标设置 getter 和 setter,或者我可以将它们公开并允许对数据进行自由读/写访问。在我看来,这是可以的,因为该类的行为就像一个美化的结构——一个可能附加了一些有用函数的数据集合。

然而,在很多情况下,您不想提供对内部数据的完全访问,而是依赖类提供的方法与对象进行交互。一个例子是 HTTP 请求和响应。在这种情况下,允许任何人通过网络发送任何内容都是一个坏主意 - 它必须由类方法进行处理和格式化。在这种情况下,类被视为一个实际的对象,而不是一个简单的数据存储。

这实际上取决于动词(方法)是否驱动结构或者数据是否驱动结构。

其他提示

作为一个必须维护过去由许多人编写的几年前的代码的人,我很清楚,如果一个成员属性被公开,它最终会被滥用。我什至听到人们不同意访问器和修改器的想法,因为这仍然没有真正达到封装的目的,即“隐藏类的内部工作原理”。这显然是一个有争议的话题,但我的意见是“将每个成员变量设为私有,主要考虑该类必须做什么” (方法)而不是 如何 你将让人们改变内部变量”。

是的,封装很重要。暴露底层实现(至少)会造成两件事错误:

  1. 混淆了责任。调用者不需要或不想了解底层实现。他们应该只是希望班级完成其工作。通过暴露底层实现,你的类并没有完成它的工作。相反,它只是将责任推给调用者。
  2. 将您与底层实现联系起来。一旦公开了底层实现,您就与它绑定了。例如,如果您告诉调用者,下面有一个集合,则您无法轻松地将集合交换为新的实现。

无论您是直接访问底层实现还是只是复制所有底层方法,这些(和其他)问题都适用。您应该公开必要的实现,仅此而已。保持实现私有使得整个系统更易于维护。

我更喜欢尽可能长时间地保持成员私有,并且只能通过 getter 访问它们,即使是在同一个类中也是如此。我还尽量避免将 setter 作为初稿,以尽可能推广价值风格对象。经常使用依赖注入,您通常有 setter,但没有 getter,因为客户端应该能够配置对象,但(其他人)不知道实际配置的内容,因为这是一个实现细节。

问候,奥利

我倾向于非常严格地遵守规则,即使这只是我自己的代码。出于这个原因,我真的很喜欢 C# 中的属性。它使得控制所给出的值变得非常容易,但您仍然可以将它们用作变量。或者将集合设置为私有,然后将其设置为公开,等等。

基本上,信息隐藏与代码清晰度有关。它的设计目的是让其他人更容易地扩展您的代码,并防止他们在使用您的类的内部数据时意外地创建错误。它基于这样的原则 从来没有人看评论, ,尤其是其中有说明的内容。

例子: 我正在编写更新变量的代码,并且我需要绝对确保 Gui 发生更改以反映更改,最简单的方法是添加一个访问器方法(又名“Setter”),调用该方法而不是更新数据已更新。

如果我将这些数据公开,并且在不通过 Setter 方法的情况下更改了变量(并且每次脏话都会发生这种情况),那么有人将需要花费一个小时的调试来找出更新未显示的原因。在较小程度上,这同样适用于“获取”数据。我可以在头文件中添加注释,但很可能没有人会读它,直到出现非常非常错误的情况。用 private 强制执行意味着错误 不能 这样做,因为它会显示为一个容易定位的编译时错误,而不是运行时错误。

根据经验,唯一需要将成员变量设为 public 并省略 Getter 和 Setter 方法的情况是,如果您想绝对清楚地表明更改它不会产生任何副作用;特别是当数据结构很简单时,比如一个简单地保存两个变量作为一对的类。

这应该是相当罕见的情况,通常情况下您会 副作用,如果您创建的数据结构非常简单(例如配对),那么标准库中已经有一个更有效的编写结构。

话虽如此,对于大多数一次性无扩展的小程序(例如您在大学获得的程序)来说,这比任何事情都更“好实践”,因为您会在编写它们的过程中记住它们,然后您就可以记住它们。我会把它们交给你,并且永远不会再碰代码。另外,如果您编写数据结构是为了了解它们如何存储数据而不是作为发布代码,那么有一个很好的论点是 Getters 和 Setters 不会有任何帮助,并且会妨碍学习体验。

只有当您进入工作场所或大型项目时,您的代码很可能会被不同人编写的对象和结构调用,此时使这些“提醒”变得强大就变得至关重要。无论这是否是一个单身男子的项目,都令人惊讶地无关紧要,原因很简单,“六周后的你”就像同事一样是不同的人。而“六周前的我”往往被证明是懒惰的。

最后一点是,有些人非常热衷于信息隐藏,如果您的数据不必要地公开,他们会感到恼火。最好是幽默一下他们。

C# 属性“模拟”公共字段。看起来很酷,语法确实加快了创建这些 get/set 方法的速度

请记住在对象上调用方法的语义。方法调用是一种非常高级的抽象,可以通过编译器或运行时系统以各种不同的方式实现。

如果您调用的方法的对象存在于同一进程/内存映射中,则编译器或虚拟机可以很好地优化该方法以直接访问数据成员。另一方面,如果该对象位于分布式系统中的另一个节点上,那么您无法直接访问它的内部数据成员,但您仍然可以通过向其发送消息来调用其方法。

通过对接口进行编码,您可以编写不关心目标对象存在于何处、如何调用其方法的代码,甚至不关心它是否是用相同语言编写的。


在实现集合所有方法的对象示例中,那么肯定该对象实际上 一个集合。所以也许在这种情况下继承会比封装更好。

这一切都是为了控制人们可以用你给他们的东西做什么。你的控制力越强,你就能做出越多的假设。

另外,理论上您可以更改底层实现或其他内容,但因为大多数情况下它是:

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

这有点难以自圆其说。

当至少满足以下条件之一时,封装就很重要:

  1. 除了您之外的任何人都将使用您的类(或者他们会破坏您的不变量,因为他们不阅读文档)。
  2. 任何不阅读文档的人都会使用您的类(否则他们会破坏您仔细记录的不变量)。请注意,此类别包括两年后的您。
  3. 将来某个时候,有人会继承你的类(因为当字段的值发生变化时,可能需要采取额外的操作,所以必须有一个setter)。

如果它只适合我,并且在少数地方使用,并且我不会继承它,并且更改字段不会使该类假设的任何不变量无效, 只有那时 我偶尔会公开一个领域。

我的倾向是尽可能将所有事情保密。这使得对象边界尽可能清晰地定义,并使对象尽可能地解耦。我喜欢这个,因为当我必须重写第一次(第二次、第五次?)次搞砸的对象时,它会将损坏保留在较少数量的对象中。

如果将对象耦合得足够紧密,则将它们组合成一个对象可能会更简单。如果你足够放松耦合约束,你就会回到结构化编程。

如果您发现一堆对象只是访问器函数,那么您可能应该重新考虑您的对象划分。如果您没有对该数据执行任何操作,它可能属于另一个对象的一部分。

当然,如果您正在编写类似库之类的东西,您希望界面尽可能清晰明了,以便其他人可以针对它进行编程。

让工具适合工作...最近我在当前的代码库中看到了一些这样的代码:

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

然后这个类在内部使用来轻松传递多个数据值。它并不总是有意义,但如果您只有数据,没有方法,并且您没有将其暴露给客户端,我发现它是一个非常有用的模式。

我最近使用的是一个 JSP 页面,其中显示了一个数据表,在顶部以声明方式定义。所以,最初它是在多个数组中,每个数据字段一个数组......这导致代码相当难以遍历,因为定义中的字段不是彼此相邻的,而这些字段会一起显示......所以我创建了一个像上面这样的简单类,它将它们组合在一起......结果是真正可读的代码,比以前好多了。

道德...有时你应该考虑“接受的坏”替代方案,如果它们可以使代码更简单且更易于阅读,只要你仔细考虑并考虑后果......不要盲目接受你听到的一切。

那是说...公共 getter 和 setter 几乎等同于公共字段......至少本质上(有一点更多的灵活性,但它仍然是一个糟糕的模式,适用于你拥有的每个领域)。

甚至 java 标准库也有一些情况 公共领域.

当我让物体变得有意义时,它们就是 更轻松使用 并且更容易 维持.

例如:Person.Hand.Grab(多快,多少);

诀窍在于不要将成员视为简单的值,而是将其视为对象本身。

我认为这个问题确实将封装的概念与“信息隐藏”混淆了
(这不是批评家,因为它似乎确实符合“封装”概念的常见解释)

然而对我来说,“封装”是:

  • 将多个项目重新组合到一个容器中的过程
  • 容器本身重新组合项目

假设您正在设计一个纳税人系统。对于每个纳税人,您可以 封装 的概念 孩子 进入

  • 代表孩子的孩子名单
  • to 的地图考虑了来自不同父母的孩子
  • 一个 Children 对象(不是 Child),它将提供所需的信息(例如孩子的总数)

这里有三种不同类型的封装,其中 2 种由低级容器(列表或映射)表示,一种由对象表示。

通过做出这些决定,您不会

  • 将该封装设为公开、受保护或私有:“信息隐藏”的选择仍有待做出
  • 进行完整的抽象(您需要细化对象 Children 的属性,并且您可能决定创建一个对象 Child,从纳税人系统的角度来看,该对象仅保留相关信息)
    抽象是选择对象的哪些属性与您的系统相关以及哪些必须完全忽略的过程。

所以我的观点是:
这个问题的标题可能是:
私人 vs 私人实践中的公众成员(有多重要 信息隐藏?)

不过,只是我的 2 美分。我完全尊重人们可能将封装视为一个包括“信息隐藏”决策的过程。

然而,我总是尝试区分“抽象”-“封装”-“信息隐藏或可见性”。

@冯C

您可能会发现国际标准化组织的“开放式分布式处理参考模型”很有趣。它定义:“封装:对象中包含的信息只能通过对象支持的接口上的交互来访问的属性。”

我试图证明信息隐藏是这个定义的关键部分:http://www.edmundkirwan.com/encap/s2.html

问候,

埃德。

我发现很多 getter 和 setter 都是 代码气味 程序的结构设计得不好。您应该查看使用这些 getter 和 setter 的代码,并查找真正应该属于该类的功能。在大多数情况下,类的字段应该是私有实现细节,并且只有该类的方法可以操作它们。

同时拥有 getter 和 setter 等于该字段是公共的(当 getter 和 setter 很简单/自动生成时)。有时最好将字段声明为公共,这样代码会更简单,除非您需要多态性或框架需要 get/set 方法(并且您无法更改框架)。

但在某些情况下,拥有 getter 和 setter 也是一个很好的模式。一个例子:

当我创建应用程序的 GUI 时,我尝试将 GUI 的行为保留在一个类(FooModel)中,以便可以轻松进行单元测试,并在另一个可以测试的类(FooView)中显示 GUI 的可视化只能手动。视图和模型通过简单的粘合代码连接起来;当用户更改字段值时 x, ,视图调用 setX(String) 在模型上,这反过来可能会引发模型的其他部分发生更改的事件,并且视图将使用 getter 从模型中获取更新的值。

在一个项目中,有一个 GUI 模型,有 15 个 getter 和 setter,其中只有 3 个 get 方法是微不足道的(以便 IDE 可以生成它们)。所有其他都包含一些功能或重要的表达式,例如以下内容:

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

在实践中,我总是只遵循一条规则,即“没有一刀切”的规则。

封装及其重要性是您的项目的产物。什么对象将访问您的界面,他们将如何使用它,如果他们对成员拥有不需要的访问权限,这会重要吗?在实施每个项目时,您需要问自己这些问题以及类似的问题。

我的决定基于模块内代码的深度。如果我正在编写模块内部的代码,并且不与外界交互,我不会用 private 封装太多东西,因为它会影响我的程序员性能(我编写和重写代码的速度)。

但对于作为模块与用户代码的接口的对象,我遵守严格的隐私模式。

当然,无论您编写内部代码还是供其他人(甚至您自己,但作为一个包含的单元)使用的代码,这都会有所不同。任何要在外部使用的代码都应该有一个定义良好/记录良好的接口,您可以使用该接口。想要尽可能少地改变。

对于内部代码,根据难度,您可能会发现现在用简单的方法做事会更少工作,并且稍后会付出一些代价。当然,墨菲定律将确保短期收益将被多次消除,因为稍后您需要更改未能封装的类的内部结构,因此必须进行广泛的更改。

特别是对于使用要返回的集合的示例,此类集合的实现似乎可能会发生变化(与更简单的成员变量不同),从而使封装的效用更高。

话虽这么说,我有点喜欢 Python 处理它的方式。成员变量默认是公共的。如果您想隐藏它们或添加验证,可以提供一些技术,但这些被认为是特殊情况。

我几乎一直遵守这方面的规则。对我来说有四种情况 - 基本上是规则本身和几个例外(全部受 Java 影响):

  1. 可供当前类之外的任何内容使用,通过 getter/setter 访问
  2. 内部到类的使用通常以“this”开头,以明确它不是方法参数
  3. 某种东西应该保持非常小,比如运输物体——基本上是属性的直接镜头;所有公众
  4. 需要非私有才能进行某种扩展

大多数现有答案都没有解决这里存在的一个实际问题。封装以及向外部代码公开干净、安全的接口总是很棒的,但当您编写的代码打算由空间和/或时间上较大的“用户”基础使用时,这一点就更为重要。我的意思是,如果您计划让某人(甚至您)在未来很好地维护代码,或者如果您正在编写一个将与来自多个其他开发人员的代码进行交互的模块,那么您需要考虑更多与编写一次性或完全由您编写的代码相比,要小心。

老实说,我知道这是多么糟糕的软件工程实践,但我通常会首先将所有内容公开,这使得记住和输入内容的速度稍快一些,然后在有意义时添加封装。如今,最流行的 IDE 中的重构工具决定了您使用哪种方法(添加封装还是添加封装)。把它拿走)比以前的相关性要低得多。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top