在实践中私人与公共成员(封装有多重要?)

时间:2008-09-19 04:44:32

标签: language-agnostic encapsulation

面向对象编程的最大优势之一是封装,我们(或者至少,我已经)教过的“真理”之一是成员应该始终保持私密并通过访问器和增变器方法,从而确保验证和验证更改的能力。

但是,我很好奇,这在实践中是多么重要。特别是,如果你有一个更复杂的成员(例如集合),那么将它公开而不是制作一堆方法来获取集合的密钥,添加/删除集合中的项目是非常诱人的,等

您是否遵循一般规则?你的答案是否会改变,取决于它是为自己编写的代码还是其他人使用的代码?我是否因为这种混淆而缺少更微妙的理由?

22 个答案:

答案 0 :(得分:16)

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

答案 1 :(得分:16)

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

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

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

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

答案 2 :(得分:8)

是的,封装很重要。公开底层实现(至少)有两件事是错误的:

  1. 混合责任。呼叫者不应该或不想了解底层实现。他们应该只希望班级做好自己的工作。通过公开底层实现,你就是上课没有做好自己的工作。相反,它只是将责任推到了呼叫者身上。
  2. 将您与基础实施联系起来。一旦暴露了底层实现,就会与它相关联。如果您告诉呼叫者,例如,下面有一个集合,您就无法轻易地将集合交换为新的实现。
  3. 无论您是直接访问底层实现还是只复制所有底层方法,这些(和其他)问题都适用。您应该公开必要的实现,仅此而已。保持实施私密性使整个系统更易于维护。

答案 3 :(得分:3)

我希望尽可能长时间保持成员私密,并且只能通过getter访问em,即使是在同一个类中也是如此。我也尽量避免使用setter作为初稿来推广价值风格对象,只要它是可能的。使用依赖注入很多,你经常有setter但没有getter,因为客户端应该能够配置对象,但(其他人)不知道什么是实际配置,因为这是一个实现细节。

此致 奥利

答案 4 :(得分:2)

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

答案 5 :(得分:2)

基本上,信息隐藏是关于代码清晰度的。它旨在使其他人更容易扩展您的代码,并防止他们在使用您的类的内部数据时意外地创建错误。它基于没有人阅读评论的原则,特别是那些带有说明的人。

示例:我正在编写更新变量的代码,我需要确保Gui更改以反映更改,最简单的方法是添加一个访问器方法(也就是a更新了“Setter”,而不是更新数据。

如果我将这些数据公开,并且某些内容更改变量而不通过Setter方法(并且这发生在每个脏话时间),那么有人需要花一个小时调试来找出更新不是为什么被显示。这同样适用于“获取”数据。我可以在头文件中添加注释,但很可能没有人会读到它,直到出现严重错误。使用private实现它意味着无法的错误,因为它将显示为一个容易定位的编译时错误,而不是运行时错误。

根据经验,你想要公开一个成员变量的唯一时间,并省略Getter和Setter方法,如果你想明确表示改变它将没有副作用;特别是如果数据结构很简单,就像一个简单地将两个变量保存为一对的类。

这应该是一个相当罕见的事件,因为通常你会想要副作用,如果你正在创建的数据结构如此简单而你没有(例如配对),标准库中已经有一个更有效的书面文件。

话虽如此,对于大多数一次性使用无延期的小程序,就像你在大学里学到的那样,它比任何东西都更“好习惯”,因为你会在写作过程中记住,然后你会把它们交给他们,再也不会触摸代码了。此外,如果您正在编写数据结构以了解它们如何存储数据而不是发布代码,那么有一个很好的论据,即Getters和Setters无法提供帮助,并会妨碍学习体验

只有当你到达工作场所或大型项目时,你的代码才会被不同的人编写的对象和结构调用,这对于使这些“提醒”变得强大至关重要。它是否是一个单人项目是令人惊讶的无关紧要,原因很简单,“你从现在起六周后”与同事不同。而“我六个星期前”常常变得很懒惰。

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

答案 6 :(得分:1)

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

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

通过对接口进行编码,您可以编写不关心目标对象存在位置或调用方法的代码,或者使用相同语言编写代码。


在实现集合的所有方法的对象示例中,实际上该对象实际上集合。所以这可能是继承比封装更好的情况。

答案 7 :(得分:1)

C#Properties'模拟'公共字段。看起来非常酷,语法真的加快了创建那些get / set方法

答案 8 :(得分:0)

当我使对象有意义时,他们更容易 使用并且更容易维护

例如:Person.Hand.Grab(howquick,howmuch);

诀窍不是将成员视为简单的值而是将对象本身视为对象。

答案 9 :(得分:0)

我认为这个问题确实将封装的概念与“信息隐藏”混为一谈 (这不是批评者,因为它似乎与'封装'概念的共同解释相匹配)

然而对我来说,'封装'是:

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

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

  • 代表孩子的孩子名单
  • 考虑到来自不同父母的孩子的地图
  • 一个对象儿童(非儿童)将提供所需信息(如儿童总数)

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

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

  • 使封装公开或受保护或私有:仍然要选择“信息隐藏”
  • 做一个完整的抽象(你需要优化对象子的属性,你可能决定创建一个对象Child,它只能从纳税人系统的角度保留相关的信息)
    抽象是选择对象的哪些属性与您的系统相关的过程,必须完全忽略。

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

不过我只有2美分。我非常尊重人们可能会认为封装是一个包括“信息隐藏”决定的过程。

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

答案 10 :(得分:0)

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

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

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

我最近使用的是一个JSP页面,我在其中显示了一个数据表,在声明的顶部定义。所以,最初它是在多个数组中,每个数据字段一个数组......这在代码中很难通过在定义中彼此不相邻的字段一起显示...所以我创建了一个简单的像上面这样将它拉到一起的类...结果是真正可读的代码,比以前更多。

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

那就是说...公共吸气者和制定者几乎相当于公共领域......至少基本上(有更多的灵活性,但它仍然是一个糟糕的模式,适用于你所拥有的每个领域)。 / p>

即使是java标准库也有一些public fields的情况。

答案 11 :(得分:0)

所有关于控制人们可以用你给他们的东西做什么。你控制的越多,你就可以做出越多的假设。

另外,理论上你可以改变底层的实现或者其他东西,但是因为大多数情况下它是:

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

这有点难以证明。

答案 12 :(得分:0)

我的倾向是尽可能让一切变得私密。这样可以尽可能明确地定义对象边界,并使对象尽可能地分离。我喜欢这个,因为当我必须重写一个我拙劣的第一个(第二个,第五个?)时间的对象时,它会将损坏包含在较少数量的对象中。

如果您将对象紧密地耦合在一起,将它们组合成一个对象可能会更直接。如果你放松了耦合约束,你就会回到结构化编程。

如果您发现一堆对象只是存取函数,则可能需要重新考虑对象划分。如果您没有对该数据执行任何操作,则它可能属于另一个对象的一部分。

当然,如果您正在编写类似于库的内容,那么您希望界面尽可能清晰明了,以便其他人可以对其进行编程。

答案 13 :(得分:0)

当至少其中一个成立时,封装很重要:

  1. 除了你之外的任何人都会使用你的课程(或者他们会破坏你的不变量,因为他们没有阅读文档)。
  2. 任何不阅读文档的人都会使用你的课程(或者他们会破坏你认真记录的不变量)。请注意,此类别包括您 - 从现在起两年。
  3. 在未来的某个时刻,有人会从你的班级继承(因为当一个领域的价值发生变化时,可能需要采取额外的行动,因此必须有一个制定者)。
  4. 如果它只适合我,并且在很少的地方使用,并且我不会继承它,并且更改字段不会使该类假定的任何不变量无效,只有 I偶尔会公开场地。

答案 14 :(得分:0)

@VonC

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

我试图在这里为信息隐藏提供一个案例:这个定义的关键部分: http://www.edmundkirwan.com/encap/s2.html

此致

答案 15 :(得分:0)

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

让getter和setter都等于公共字段(当getter和setter是微不足道/自动生成时)。有时最好只将public字段声明为public,这样代码就会更简单,除非你需要多态或框架需要get / set方法(并且你不能改变框架)。

但也有一些情况下,有吸气剂和制定者是一个很好的模式。一个例子:

当我创建应用程序的GUI时,我尝试将GUI的行为保存在一个类(FooModel)中,以便可以轻松地对其进行单元测试,并在另一个类(FooView)中实现GUI的可视化。只能手动测试。视图和模型用简单的胶水代码连接起来;当用户更改字段x的值时,视图会调用模型上的setX(String),这反过来可能会引发模型的其他部分已更改的事件,并且视图将更新具有getter的模型中的值。

在一个项目中,有一个GUI模型,它有15个getter和setter,其中只有3个g​​et方法是微不足道的(这样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();
}

答案 16 :(得分:-1)

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

话虽如此,我有点像Python处理它的方式。成员变量默认是公共的。如果要隐藏它们或添加验证,则提供了一些技术,但这些都被视为特殊情况。

答案 17 :(得分:-1)

这里有一个实际问题,大多数现有答案都没有解决这个问题。封装以及将干净,安全的接口暴露给外部代码总是很好,但是当您编写的代码旨在被空间和/或时间上较大的“用户”基础消耗时更为重要。我的意思是,如果你计划某人(甚至你)将代码很好地保存到未来,或者如果你正在编写一个模块,该模块将与来自少数其他开发人员的代码进行交互,那么你需要考虑更多如果您编写的代码是一次性的或完全由您编写的,请仔细阅读。

老实说,我知道这是多么糟糕的软件工程实践,但我通常会先将所有内容公之于众,这使得记忆和输入的速度更快,然后在合理的情况下添加封装。如今,大多数流行IDE中的重构工具都会使您使用哪种方法(添加封装与取消封装)的相关性远低于以往。

答案 18 :(得分:-1)

我几乎一直都遵循这个规则。对我来说有四种情况 - 基本上,规则本身和几个例外(都受Java影响):

  1. 可通过当前课程之外的任何内容使用,通过getters / setters
  2. 访问
  3. 内部到类的使用通常以“this”开头,以表明它不是方法参数
  4. 意味着保持极小的东西,就像运输物体一样 - 基本上是属性的直接射击;全部公开
  5. 需要非私人扩展某种

答案 19 :(得分:-1)

当然,无论您是编写内部代码还是代码供其他人使用(或者甚至是您自己,但作为一个包含的单元),都会产生影响。任何将在外部使用的代码都应该有明确的定义/记录你想要尽可能少地改变的界面。

对于内部代码,根据难度,您可能会发现现在以简单的方式执行操作的工作量较少,稍后会付出一点代价。当然,墨菲定律将确保短期收益将被多次删除,因为您需要在以后需要更改未能封装的类内部时进行广泛的更改。

答案 20 :(得分:-1)

我的决定基于模块中代码的深度。 如果我正在编写模块内部的代码,并且不与外部世界接口,那么我不会将这些代码与private一起封装,因为它会影响我的程序员性能(我可以多快地编写和重写代码)。

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

答案 21 :(得分:-1)

在实践中,我总是只遵循一条规则,即“不适合所有人”规则。

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