Java Encapsulation
我们总是说,如果我们简单地定义变量
我在谷歌上搜索了很多解释,但一无所获。每个人在他们的博客和帖子中都说这是一种数据隐藏技术,但还没有解释/阐述它。
期待在StackOverflow的论坛上得到适当、令人满意的解释。
封装不仅仅是为类定义访问器和赋值器方法。它是面向对象编程的一个更广泛的概念,包括最小化类之间的相互依赖性,通常通过信息隐藏来实现。好的。
封装的美妙之处在于能够在不影响用户的情况下改变事物。好的。
在像Java这样的面向对象编程语言中,通过使用可访问性修改器(公共的、受保护的、私有的、加上没有修改意味着包私有)的细节来实现封装。通过这些可访问性级别,您可以控制封装级别,限制级别越少,发生更改的代价越高,并且类与其他依赖类(即用户类、子类)的耦合程度越高。好的。
因此,目标不是隐藏数据本身,而是隐藏如何操作此数据的实现细节。好的。
其思想是提供一个公共接口,通过它您可以访问这些数据。稍后您可以更改数据的内部表示,而不影响类的公共接口。相反,通过公开数据本身,会损害封装,从而影响在不影响数据用户的情况下更改数据操作方式的能力。您创建了对数据本身的依赖关系,而不是与类的公共接口的依赖关系。当"改变"最终找到你时,你会为麻烦创造一个完美的鸡尾酒。好的。
您可能希望封装对字段的访问有几个原因。Joshua Bloch在他的《有效Java》一书中,在第14项中:最小化类和成员的可访问性,提到了几个令人信服的原因,我在这里引用:好的。
- 您可以限制可以存储在字段中的值(即,性别必须为F或M)。
- 您可以在修改字段时采取操作(触发事件、验证等)。
- 您可以通过同步方法来提供线程安全性。
- 可以切换到新的数据表示形式(即计算字段、不同的数据类型)
然而,封装不仅仅是隐藏字段。在爪哇中,可以隐藏整个类,由此隐藏整个API的实现细节。例如,用
现在,在我看来,要真正理解封装,必须首先理解抽象。好的。
例如,想想汽车概念的抽象层次。汽车的内部实现很复杂。它们有几个子系统,如传动系统、制动系统、燃油系统等。好的。
然而,我们简化了它的抽象,并且通过抽象的公共接口与世界上所有的汽车进行交互。我们知道所有的汽车都有一个方向盘,通过这个方向盘我们可以控制方向,他们有一个踏板,当你踩下它时,你可以加速汽车并控制速度,还有一个踏板,当你踩下它时,你可以使它停下来,你有一个变速杆,可以让你控制前进或后退。这些特性构成了汽车抽象的公共接口。早上,你可以开一辆轿车,然后在下午下车,开一辆SUV,就好像它是一样的。好的。
然而,很少有人知道这些特性是如何在引擎盖下实现的。想想那个时候,汽车没有液压定向系统。有一天,汽车制造商发明了它,他们决定把它装进汽车里。不过,这并没有改变用户与他们交互的方式。最多,用户在使用定向系统方面经历了改进。这样的改变是可能的,因为汽车的内部实现是封装的。可以在不影响公共接口的情况下安全地进行更改。好的。
现在,想想汽车制造商决定把燃料盖放在汽车下面,而不是放在它的一边。你去买一辆新车,当你没油的时候,你去加油站,却找不到加油盖。突然你意识到它在车下面,但是你不能用气泵软管够到它。现在,我们已经打破了公共接口契约,因此,整个世界都崩溃了,因为事情没有按预期的方式运行。像这样的改变要花费数百万美元。我们需要改变世界上所有的燃气泵。当我们破坏封装时,我们必须付出代价。好的。
因此,正如您所看到的,封装的目标是最小化相互依赖性并促进更改。您可以通过最小化实现细节的暴露来最大化封装。类的状态只能通过其公共接口访问。好的。
我真的建议你读一篇由艾伦·斯奈德写的论文,叫做面向对象编程语言中的封装和继承。这个链接指向ACM上的原始文件,但我很确定你可以通过谷歌找到一份PDF副本。好的。好啊。
我理解您的问题的方式是,虽然我们将变量声明为
好吧,当使用getter和setter时,可以限制对
即
1 2 3 4 5 6 7 | private int x; public int getInt(String password){ if(password.equals("RealPassword")){ return x; } } |
对二传手也是如此。希望这有帮助!
数据是安全的,因为您可以在getter/setter中执行额外的逻辑,并且不可能更改变量的值。假设您的代码不使用空变量,那么在setter中,您可以检查空值并指定一个默认值,即!= NULL。因此,不管是否有人试图将变量设置为空,您的代码仍在工作。
My question is if we can access the variables( data) though via getters and setters, how come data is hidden or safe?
例如,可以在getter/setter下封装逻辑
1 2 3 4 5 6 7 | public void setAge(int age){ if(age < 0){ this.age = 0; }else{ this.age = age; } } |
一般来说,getter和setter对字段进行封装会给更改带来更多的灵活性。
如果直接访问字段,您将被困在"愚蠢的字段"中。只能写入和读取字段。访问字段时无法执行任何其他操作。
当使用方法时,您可以在设置/读取值时执行任何您想要的操作。正如Markus和Jigar提到的,验证是可能的。此外,您可以决定某一天该值是从另一个值派生的,或者如果该值发生更改,则必须执行某些操作。
how come data is hidden or safe
使用getter和setter既不隐藏数据,也不安全。它只是给了你安全的可能性。隐藏的是实现,而不是数据。
继续Jigar的回答。有一些东西是封装的。
合同管理:如果你制作IT
易变性:你不必总是有一个setter。如果有一个属性要在对象的生命周期内保持不变。你只是让它成为私人的,没有设置者。它可能通过构造函数设置。然后getter只返回属性(如果它是不可变的)或属性的副本(如果属性是可变的)。
数据验证是关于封装如何在有访问器和/或变异器的情况下提供安全性的问题的主要答案。其他人已经提到了这一点,他们使用了一个具有故障保护功能的例子来在mutator中设置默认值。您回答说,您更喜欢抛出异常,这是很好的,但是在您使用时识别出您有坏数据不会改变您有坏数据的事实。因此,是否最好在修改数据之前捕获异常,也就是说,在mutator中捕获异常?这样,实际数据就永远不会被修改,除非调换器已经验证它是有效的,因此在出现坏数据时,原始数据会被保留。
我自己还是一个学生,但是我和你一样,当我第一次遇到封装的时候,我也有同样的想法,所以我花了一些时间来搞清楚。
我喜欢考虑线程时的解释。如果将字段公开,实例如何知道某个线程何时更改了其中一个字段?唯一的方法是使用封装,或者更简单地将getter和setter赋给该字段,因此您总是知道并可以检查/响应字段更新。
封装使代码更容易被其他人重用。使用封装的另一个关键原因是接口不能声明字段,但它们可以声明方法,这可以引用字段!
方法的名称正确,开头有一个动词。例如:getname()、setname()、isding()。这有助于读取代码!