封装的目的是信息隐藏,包括封装数据、封装实现、封装类型和封装变化。
封装数据
封装数据一般是都由语法解析来实现的,比如提供 private、public、protected 等关键字来提供不同的访问权限。
但 Javascript 并没有提供对这些关键字的支持,所以只能依赖变量的作用域来实现封装,而且只能模拟出 public 和 private 这两种封装性。
let myObject = (function() {
let _name = 'sven'; // private property
return {
getName: function() { // public method
return _name;
}
}
})();
console.log(myObject.getName()); // sven
console.log(myObject ._name); //undefined
但是用这种方法的话,扩展类无法继承私有属性。
在 ECMAScript 6 中,可以通过Symbol
创建私有属性,但是这种私有属性是假的,Object.getOwnPropertyNames()
虽然无法获取Symbols
属性。但是,ES6 新增的Object.getOwnPropertySymbols()
能取到类里面声明的所有Symbols
属性。
封装实现
封装使得对象内部的变化对其他对象而言是透明的。对象只对它自己的行为负责。其他对象或者用户都不关心它的内部实现。封装使得对象之间的耦合变松散,对象之间只通过暴露 API 接口来通信。当我们修改一个对象时,可以随意地修改其内部实现,只要对外的接口没有变化,就不会影响程序的其他功能。
封装实现的例子很多。比如迭代器,迭代器的作用是在不暴露一个聚合对象内部表示的前提下,提供一种方式来顺序访问这个聚合对象。我们编写一个 each 函数,它的作用就是遍历一个聚合对象,使用这个函数的人不用关心它的内部是怎样实现的,只要提供的功能正确就行。即使 each 函数修改了内部源代码,只要对外的接口或者调用方式没有变化,用户就不用关心它内部实现的改变。
封装类型
封装类型是静态类型语言中的一种重要的封装方式。一般而言,封装类型是通过抽象类和接口来(向上转型)进行的。把对象的真正类型隐藏在抽象类或者接口之后,相比对象的类型,客户更关心对象的行为。在许多静态语言的设计模式中,想方设法地去隐藏对象的类型,也是促使这些模式诞生的原因之一。比如工厂方法模式、组合模式等。
封装变化
通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。这可以最大程度地保证程序的稳定性和可扩展性。
拿创建型模式来说,要创建一个对象,是一种抽象行为,而具体创建什么对象则是可以变化的,创建型模式的目的就是封装创建对象的变化。而结构型模式封装的是对象之间的组合关系。行为型模式封装的是对象的行为变化。