封装
封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。
对于封装这个特性,需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。例如Java语言中的private、public、protected等关键字。
如果对类中属性的访问不做限制,那么任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一个方面来说,过度的灵活也意味着不可控,属性可以随意被各种奇葩的方式修改,而且修改逻辑可能散落在代码的各个角落中,势必影响代码的可读性、可维护性。
除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。如果把类属性都暴露给类的调用者,调用者想要正确的操作这些属性,就势必要对业务细节有足够的了解。而这对调用者来说也是一种负担。相反,把属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多业务背后的业务细节,用错的概率就减少很多。
抽象
抽象讲的是如何隐藏方法的实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
在面向对象编程中,我们常借助编程语言提供的借口类或者抽象类这两种语法机制,来实现抽象这一特性。
抽象这个特性是非常容易实现的,并不需要非得依赖接口类或者抽象类这些特殊的语法机制了来支持。因为类的方法是通过编程语言中的“函数”这一语法机制来实现的,通过函数包裹具体的实现逻辑,这本身就是一种抽象。调用者在使用函数的时候,并不需要去研究函数的内部实现逻辑,只需要通过函数的命名、注释、或者文档,了解其提供了什么功能,就可以直接使用了。比如使用C语言的malloc()函数的时候,并不需要了解它的底层代码是怎么实现的。
抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。
在定义类的方法的时候,也需要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑。
继承
继承是用来表示类之间的is-a关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只能继承一个父类,多继承表示一个子类可以继承多个父类,比如猫即使哺乳动物,又是爬行动物。
为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如Java使用extends关键字来实现继承,C++使用冒号,Python使用parentheses()。不过有些语言只支持单继承,不支持多重继承,比如Java、PHP、C#等,有些编程语言既支持单重继承,也支持多重继承,比如C++、Python、Perl等。
继承最大的一个好处就是代码复用,假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样两个子类就可以重用父类中的代码,避免代码重复写多遍。不过,这一点也不是继承所独有的,也可以通过其他方式来解决这个代码复用的问题,比如利用组合关系而不是继承关系。
继承的概念喝好理解,也很容易使用。不过过度使用继承,继承层次过深过复杂,就会导致代码的可读性、可维护性变差。为了了解一个类的功能不仅需要查看这个类的代码,还需要按照继承关系一层一层的往上查看父类,父类的父类......的代码,还有,子类和父类高度耦合,修改父类的代码,会直接影响到子类。
多态
多态是指,子类可以替换父类,在实际代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现。
第一个语法机制是编程语言要支持父类对象可以引用子类对象,第二个语法机制是编程语言要支持继承,第三个语法机制是编程语言要支持子类重写父类中的方法。通过这些机制实现了多态特性。
对于多态的实现方式,除了利用继承加方法重写的这种实现方式之外,还有其他两种比较常见的实现方式,一个是利用接口类语法,另一个是利用duck-typing语法。不过,并不是每种编程语言都支持接口类或者duck-typing这两种语法机制,比如C++就不支持接口语法类,而duck-typing只有一些动态语言才支持,比如Python、JavaScript等。
duck-typing 实现多态的方式非常灵活。Logger 和 DB 两个类没有任何关系,既不是继承关系,也不是接口和实现的关系,但是只要它们都有定义了 record() 方法,就可以被传递到 test() 方法中,在实际运行的时候,执行对应的 record() 方法。
也就是说,只要两个类具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是所谓的 duck-typing,是一些动态语言所特有的语法机制。而像 Java 这样的静态语言,通过继承实现多态特性,必须要求两个类之间有继承关系,通过接口实现多态特性,类必须实现对应的接口。
多态特性能提高代码的可扩展性和复用性。除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。
小结
封装特性
封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如 Java 中的 private、protected、public 关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。
抽象特性
封装主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类来实现,但也并不需要特殊的语法机制来支持。抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。
继承特性
继承是用来表示类之间的 is-a 关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。
多态特性
多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。