什么是封装?它如何实际隐藏数据?

6
搜索出一个简单的定义:“数据隐藏”。
但是,请考虑以下两个例子:
1) 第一个例子:
Class Employee
{
    public int age;
}

第二个例子:
Class Employee
{
    private int age;

    public int getAge(){return age;}
}

问题: 在上面指定的两个示例中,都没有数据隐藏,因为年龄要么被他人修改,要么被他人查看。数据隐藏在哪里?封装如何帮助上述示例?

2
你有没有读过维基百科的文章:http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)? - David Heffernan
@Reno,封装并不仅仅意味着保护数据不被修改。如果我添加了另一个setter方法...根据你的解释,这意味着封装意味着类的只读属性...这是不正确的...我确定... - Anil Purswani
另请参阅:Jon Skeet的为什么属性很重要 - Cody Gray
7个回答

25

存在数据隐藏。第二个例子隐藏了值的存储方式。此外,第二个例子使得类外代码只能读取值而无法进行修改。通过使用私有字段,并通过方法或属性来公开它们,您可以立即获得一些优点,其中一些包括:

  • 访问控制粒度;您可以选择将值公开为只读、读写或仅写。
  • 在存储值之前验证输入的可能性
  • 添加日志记录或其他审计机制的可能性

请注意,封装不仅仅是关于访问控制。封装的主要用途是从调用代码中隐藏实现细节。当调用代码想要检索一个值时,它不应依赖于该值的来源。在内部,类可以在字段中存储该值或从某些外部资源(如文件或数据库)中检索该值。也许这个值根本没有被存储,而是动态计算的。对于调用代码而言,这些都不应该有所影响。

当您通过方法或属性公开值时,您就屏蔽了调用代码对这些细节的感知。这也为您提供了更改实现的可能性,而不会影响调用代码。


这是否意味着将设置器方法添加到属性会破坏封装性? - Anil Purswani
2
@anil:完全不是的。封装是关于隐藏实现细节的。通过将字段设置为私有,并通过方法暴露,您可以封装内部实现细节。该值可以存储在字段中,在磁盘上的文件中,在数据库中,可以实时计算等等。调用代码不知道(也不应该知道)这些细节。这就是封装。 - Fredrik Mörk
我猜隐藏实现细节是与抽象化有关,而不是封装。现在听起来好像两个不同的名称被用于相同的术语。请看这个链接:http://en.wikipedia.org/wiki/Abstraction_%28computer_science%29 - Anil Purswani
@anil:我认为封装是一种抽象形式。这也得到了你链接的文本的支持:“封装术语指的是隐藏状态细节”(在段落面向对象编程中的抽象中)。 - Fredrik Mörk

3

封装将“某物做什么”和“它如何实现”这两个概念分开。

封装是面向对象编程中非常重要的概念。它隐藏了应用程序中其他模块的数据。在您的示例2中,正如您所看到的,您只能获取年龄,并且只能使用getter方法设置私有成员的值,但是不能在类外部设置该值。这就是封装的优点。


1
多态性是如何加入这个派对的? - GregC
@GregC,哎呀!我想说的是封装。谢谢你指出来 :) - Shankar Raju

2

考虑这个变体:

class Employee
{
    private int _age;

    public string Age
    {
        get { return _age.ToString(); }
    }
}

在您的示例中,内部和外部使用整数,这会令人困惑。(类似于用2 * 2 = 4来解释乘法,却忘记了2 + 2 = 4)

@UGEEN,Erno,内外使用相同的数据类型不会产生任何混淆。将其转换为字符串听起来很奇怪,因为客户端可能需要它作为整数。那么有没有真正的理由将其转换为字符串?此外,这如何帮助封装? - Anil Purswani
类型转换成字符串仅是为了展示在外部,类型可以与内部非常不同。当客户端需要将其作为整数时,Age属性应该是该类型。该示例仅用于展示属性获取封装(或可以封装)内部数据格式。 - Emond

1
封装就是简单的数据隐藏
让我们看看封装是如何发生的。假设您创建了一个Employee类并包括两个实例变量:name和salary。您没有将它们设置为私有的。现在,如果程序中的另一个类创建一个Employee类的对象,它就可以轻松地访问这两个变量。因此,我们希望将这两个或工资信息隐藏起来,以防止外部类访问。
如果我们想要隐藏工资,我们可以将工资声明为private变量。现在,即使另一个类创建一个对象并尝试像以前那样访问工资,它也不起作用。因为它是一个只能被Employee类访问的私有字段。现在我们已经隐藏了我们的数据。
但是假设我们需要一个人输入每个员工的工资细节。更明确一点,这个输入数据的人有3种类型的数据要输入woked_hours, OT and leaves(Emplyee类有三个变量),我们希望这个人输入这些详情,但我们不希望他知道工资计算是如何完成的。它是根据Employee类内部的数学方程完成的。
所以他无法计算和输入工资。但是我们让他看到最终的工资。于是我们在员工类里创建了一个方法来获取工资的值。它是:
public int getSalary()
{
   return salary;
} 

他知道有两种方法可以访问这些隐藏字段,即“设置方法”和“获取方法”。因此,他调用了“get方法”,是的,他可以看到“薪资”信息。因为我们已经创建了“getMethod”来返回薪资。现在他尝试使用“set方法”输入新的薪资金额以更改当前的薪资值。所以他调用了“e.setSalary(1000);”但是它会出现错误。为什么?因为在员工类中没有编写该方法。
   public void setSalary(int newSalary)
   {
   salary=newSalary;
   }

我们故意没有编写那段代码,因为我们不希望他立即设置薪资。所以我们隐藏了一些信息,但仍然让他可以访问一些信息。我们给他只读权限。但是对于薪资,我们没有给他写入权限。

所以,现在封装的作用就体现出来了。

同样的方式,他被允许设置这些字段工作时间、加班和请假。对于这些字段,我们只给了他写入权限。因此,我们故意没有编写getter方法。如果你编写了gettersetter,你就允许他进行读/写访问。

无论是设置还是获取,字段都是私有的,数据已经隐藏。但是通过gettersetter仍然可以访问。


0

假设我像这样使用你的代码:

Employee employee = new Employee();
int age = employee.getAge();
// Do something with age

在这里,除非我查看您的源代码,否则我完全不知道Employee如何计算其年龄。 我所知道的是,getAge()可能看起来像这样:

public int getAge() {
    return new java.util.Random().nextInt( 100 );
}

或许还有更复杂的情况,我不确定。

所以在你的第二个例子中,getAge() 只返回一个私有成员变量的事实对我来说是完全隐藏的。


0
在您的第一个示例中,变量age被设置为公共变量。这意味着当声明Employee类型的对象时,任何人都可以修改成员age。
但是,在您的第二个示例中,变量age实际上是私有的,只能从Employee类内部进行修改。这就是数据隐藏的情况。
在面向对象编程中,封装是指一个对象由另一个对象组成。这与继承相反,后者是指一个对象可以扩展已经存在的类。

正如我已经在其他回答中评论的那样,您的意思是只有一个setter会破坏封装性。您是否意味着只读属性可以实现封装性。我确定不是这样。因为每个人都是基于第二种方法进行回应的,但是如果添加一个简单的setter方法呢? - Anil Purswani

0

封装不仅仅是数据隐藏。封装允许您将与实体相关的所有数据和功能与该实体一起保留。这些数据的可见性级别是一个相关但不完全相同的概念。例如,Employee对象携带所有员工数据和功能(方法)。请注意,这不是强制执行,而是由面向对象语言启用

如果您出生在面向对象时代,这可能听起来很自然,但这是从函数式编程到支持此范例的语言中进行的重大飞跃,无论是在概念(设计)上还是在语言上。

引用维基百科article

在编程语言中,封装用于指称两个相关但不同的概念之一,有时也指它们的组合:

  • 一种限制访问对象某些组件的语言机制。
  • 一种语言结构,便于将数据与操作该数据的方法(或其他函数)捆绑在一起。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接