我们总是说,如果我们仅仅将变量定义为private
并定义 getters 和 setters 来访问这些变量,那么数据会被封装起来。我的问题是,如果我们可以通过 getters 和 setters 访问变量(数据),那么数据如何被隐藏或保护?
我在网上搜了很多解释,但什么都没找到。每个人在他们的博客和文章中都只说这是一种数据隐藏技术,但并没有解释或详细说明。
我们总是说,如果我们仅仅将变量定义为private
并定义 getters 和 setters 来访问这些变量,那么数据会被封装起来。我的问题是,如果我们可以通过 getters 和 setters 访问变量(数据),那么数据如何被隐藏或保护?
我在网上搜了很多解释,但什么都没找到。每个人在他们的博客和文章中都只说这是一种数据隐藏技术,但并没有解释或详细说明。
封装不仅仅是为类定义存取器和修改器方法。它是面向对象编程的更广泛概念,旨在最小化类之间的相互依赖,通常通过信息隐藏实现。
封装的美妙之处在于可以在不影响用户的情况下进行更改。
在像Java这样的面向对象编程语言中,您可以使用可访问性修饰符(public,protected,private以及没有修饰符的意味着包级私有)隐藏细节以实现封装。通过这些可访问性级别,您可以控制封装级别,级别越低,发生更改时成本越高,类与其他依赖类(即用户类和子类)耦合度越高。
因此,目标不是隐藏数据本身,而是隐藏如何操作这些数据的实现细节。
关键是提供公共接口来访问这些数据。稍后,您可以更改数据的内部表示,而不会影响类的公共接口。相反,通过暴露数据本身,您会破坏封装性,从而无法更改操作数据的方式,而不影响其用户。您会创造出对数据本身的依赖,而不是对类的公共接口的依赖。当“更改”最终找到您时,您将创造出一个完美的麻烦混合物。
有几个原因可能会促使您封装对字段的访问。约书亚·布洛赫在他的书中Effective Java第14项:最小化类和成员的可访问性中提到了几个令人信服的原因,我在这里引用:
但是,封装不仅仅是隐藏字段。在Java中,您可以隐藏整个类,从而隐藏整个API的实现细节。例如,请考虑方法Arrays.asList()
。它返回一个List
实现,但只要它满足List
接口,您就不需要关心它的实现方式,对吧?实现可以在将来更改而不影响方法的用户。
javafx.scene.Scene
类中的私有实例变量 javafx.scene.Camera
?通过将实际实例变量内容放在一个 javafx.beans.property.ObjectProperty
中,会增加一个额外的抽象/封装层。这种方式使得 Setter/Getter 方法看起来像是野蛮行为。除了能够添加监听器之外,是否还有其他原因可以使用它呢?在什么情况下使用它会更有利,而不是使用“简单”的Setter/Getter方法呢? - Lealoprivate int x;
public int getInt(String password){
if(password.equals("RealPassword")){
return x;
}
}
对于setter方法同样适用。
ObjectProperty<intstancevariabel>
,我在javafx.scene.Scene类中以及其他地方看到过(查看Scene
类中的私有实例变量camera
)。我并不完全理解它的好处或者如何使用它,但我肯定能感觉到这样做有一些好处。 - Lealo数据是安全的,因为你可以在getter / setter中执行其他逻辑,并且不可能更改变量的值。想象一下你的代码不能使用空变量,所以在设置器中,你可以检查空值并分配一个默认值,该值!= null。因此,无论是否有人尝试将您的变量设置为null,您的代码仍将正常工作。
public void setAge(int age) {
if (age < 0) {
this.age = 0;
}
else {
this.age = age;
}
}
继续Jigar的回答:封装涉及到几个方面。
合同管理:如果你将其设为public
,那么任何人都可以随意更改它。你无法通过添加约束来保护它。你的setter可以确保数据以适当的方式被修改。
可变性:你不总是需要一个setter。如果有一个属性在对象的生命周期内需要保持不变,你只需将其设置为私有,并且不为其提供setter。它可能会通过构造函数进行设置。然后你的getter将只返回属性(如果它是不可变的)或属性的副本(如果属性是可变的)。
一般来说,通过getter和setter封装字段可以为更改留下更大的灵活性。
如果直接访问字段,则会陷入“愚蠢的字段”的困境。只能写入和读取字段,不能在访问字段时进行其他操作。
使用方法可以在设置/读取值时做任何您想做的事情。正如Markus和Jigar提到的,验证是可能的。此外,您可能会决定某天该值是由另一个值派生的或者在值发生变化时执行某些操作。
为什么数据被隐藏或安全了?
使用getter和setter既不隐藏也不保护数据,它只是为您提供了使其安全的可能性。隐藏的是实现而不是数据本身。
我喜欢在考虑线程时的解释。如果您将字段公开,那么您的实例如何知道某个线程何时更改了其字段?
唯一的方法是使用封装,或者更简单地说,为该字段提供getter和setter,这样您就可以始终知道并可以检查/响应字段更新,例如。
封装使代码更易于被其他人重用。使用封装的另一个关键原因是接口不能声明字段,但它们可以声明方法,这些方法可以引用字段!
方法应以动词开头进行适当命名。例如:getName(),setName(),isDying()。这有助于阅读代码!