面向对象编程及面向对象编程语言的关键就是理解其四大特性:封装、抽象、继承、多态。
封装(Encapsulation)
封装主要讲的是如何隐藏信息、保护数据。
特性:封装也叫作信息隐藏或者数据访问保护。类通过访问权限控制暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。
封装的意义是什么?
- 避免了过度灵活导致的不可控。
- 类通过有限的方法暴露必要的操作,使调用者就不需要了解太多背后的业务细节,提高了类的易用性。
它能解决什么编程问题?
- 避免了过度灵活导致的不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。
- 如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。而这对于调用者来说也是一种负担。相反,如果我们将属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。
抽象(Abstraction)
抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
特性:在面向对象编程中,我们常借助编程语言提供的接口类(比如 Java 中的 interface 关键字语法)或者抽象类(比如 Java 中的 abstract 关键字语法)这两种语法机制,来实现抽象这一特性。
抽象的意义是什么?
- 如果上升一个思考层面的话,抽象及其前面讲到的封装都是人类处理复杂性的有效手段,只关注功能点不关注实现的设计思路。
- 抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。
它能解决什么编程问题?
我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。举个简单例子,比如 getAliyunPictureUrl() 就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要随之被修改。相反,如果我们定义一个比较抽象的函数,比如叫作 getPictureUrl(),那即便内部存储方式修改了,我们也不需要修改命名。
继承(Inheritance)
特性:继承是用来表示类之间的 is-a 关系,比如猫是一种哺乳动物。
继承存在的意义是什么?
- 继承最大的一个好处就是代码复用。
它能解决什么编程问题?
- 我们代码中有一个猫类,有一个哺乳动物类。猫属于哺乳动物,从人类认知的角度上来说,是一种 is-a
关系。我们通过继承来关联两个类,反应真实世界中的这种关系,非常符合人类的认知,而且,从设计的角度来说,也有一种结构美感。
Tips:
继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式。我们应该尽量少用,甚至不用。关于这个问题,在后面讲到“多用组合少用继承”这种设计思想的时候,我会非常详细地再讲解,这里暂时就不展开讲解了。
多态(Polymorphism)
特性:多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
一般用三个语法机制来实现多态。
- 第一个语法机制是编程语言要支持父类对象可以引用子类对象,也就是可以将 SortedDynamicArray 传递给
DynamicArray。 - 第二个语法机制是编程语言要支持继承,也就是 SortedDynamicArray 继承了 DynamicArray,才能将
SortedDyamicArray 传递给 DynamicArray。 - 第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法,也就是 SortedDyamicArray 重写了
DynamicArray 中的 add() 方法。
多态特性存在的意义是什么?
- 多态特性能提高代码的可扩展性和复用性。
- 多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的
if-else 语句等等。
它能解决什么编程问题?
我们利用多态的特性,仅用一个 print() 函数就可以实现遍历打印不同类型(Array、LinkedList)集合的数据。当再增加一种要遍历打印的类型的时候,比如 HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next() 等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性。
如果我们不使用多态特性,我们就无法将不同的集合类型(Array、LinkedList)传递给相同的函数(print(Iterator iterator) 函数)。我们需要针对每种要遍历打印的集合,分别实现不同的 print() 函数,比如针对 Array,我们要实现 print(Array array) 函数,针对 LinkedList,我们要实现 print(LinkedList linkedList) 函数。而利用多态特性,我们只需要实现一个 print() 函数的打印逻辑,就能应对各种集合数据的打印操作,这显然提高了代码的复用性。