# 继承关系——继承财富,更要继承责任

# 什么是继承?

在UML中,通常使用泛化(generalization)来表达继承关系,从逻辑上来说,比继承或遗传更准确。
更确切的说,泛化强调的是概念关系,继承强调的语言机制。
继承是子类继承父类,泛化是父类泛化子类。

从子类到超类的泛化是一种概念抽象(abstraction)的过程;
相反,从超类到子类的特化,是一种概念细化(refinement)的过程。
这两种过程在设计中往往是交替采用的。
## 继承的类型
* 接口继承
```md
不是为了代码重用,而是为了代码被重用。
  • 实现继承
	消费可重用的旧代码,接口继承生产可重用的新代码。
一个是重视权利的索取型的,一个是重视义务的奉献型。

OOP更提倡后者,因为它是自下而上的方式搭建系统的。
如果代码都倾向重用而不是被重用,做顶不做底,就会头重脚轻。

# 子类型

  • 继承又被称为子类化(subcalssing)
接口继承进而被称为子类型化(subtyping)
子类不一定是子类型,比如C++中私有继承产生的子类不是子类型。
子类型也不一定是子类,比如int是long的子类型,但它不是类。
  • 子类型的关键是可代换性(substitutability)
具体地说,类型A的子类型B应该满足下面的条件:
将程序中类型A的对象置换为类型B的对象,不行影响程序的合理性和正确性。
这就是著名的里氏替换原则(Liskov Substitution Principle,LSP)
子类型是一种严格的数学关系,对比数据类型的两个要素:
允许取值的集合和允许参与的运算,

如果B类型是A类型的子类型,那么B的取值范围一定不会超过A,
同时A能参与的运算B也能参与,并且具有相同的语义。
例如 long 和int。

# 里氏替换原则(Liskov Substitution Principle,LSP)

实际上,里氏替换原则本质是为了保证规范抽象(指使用者和实现者之间的一种契约)。

里氏替换原则原则让规范抽象不在局限于单个类型,
而延伸到了真个类型族上,并且规范是向上兼容(forward compatible)的。
说通俗些,这就好比一个人作为某项承诺,他的子子孙孙都得遵守,只能加强,不能消弱。
只有这样,客户用到某个抽象类型(interface 或 abstract class)时,才能完全不关心其实现的具体类型。

与数据抽象类似,这也是一种接口与实现的分离,
只是接口所在的类型和实现所在的类型不同,相应的代码也不在粘合在一起。
不妨认为这是一种推广形式的数据抽象——多态数据类型(polymorphic data abstraction),简称多态抽象。

# 多态

指一种类型能耐具备多种类型的形式,多态抽象是建立在类型层级的基础上的。
或者说,接口继承在遵循里氏替换原则的前提下,通过接口重用达成规范重用,
保证了多态抽象,进而维护了开闭原则,让客户一劳永逸地享受接口服务而无后顾之忧

实现继承也应该遵循,这既是一种义务,也是一种权利
说是义务,因为实现继承在继承实现的同时也继承了接口,
按理也应继承接口的规范,说是权力,因为接口的继承能让代码被重用。
如果只是为了重用基类的代码,并被系统重用他的接口,那就应该采用组合而不是继承的方式。

这涉及一个通用的编程原则,即尽可能地弥合语法和语义之间的缝隙,以压缩代码臭虫的生成空间。
因为特定的语法总会对应特定的语义,如果某些对应并非原作者的初衷,便有误导之嫌。

实际上程序中的逻辑错误大部分来之两个方面的‘配合’:
	因失策而误用了语法,以及因失察而误解了语义。
	为了避免这列歧误,如果采用类型继承的语法规则,就要遵守多态抽象的语义规范。
  否则必须改弦更张,放弃继承而采用组合。以此而论,里氏替换原则不过是语法与语义相符原则的一个推论。

# 继承的关键

与其说继承是一种实现的技巧,不如说是一种规范的技巧;

与其说继承是一种is-a或is-kind-of的关系,
不如说是一种behaves-like-a 或 is-substitutable-for的关系。
	is-a的说法过于笼统,可以表示泛化——企鹅是一种鸟,分类——阿花是一只狗,角色——医生是一个人,
  特征或能力——动物是可以动的,等等。这些关系并不是所有的都适合用继承来表示的。
	因此‘is-a’只能作为判断继承关系的一个必要条件,但不能作为充分条件。

继承的关键在于,可代换性,即准守接口的规范。
而behaves-like-a 表明一个子类型的行为表现正如超类型一样,更贴切。
概念抽象只是手段,规范抽象才是依据。
  • 如何保证类型族的规范抽象?
任何类型都应该保持或强化其超类型的规范,绝对不能弱化规范。
通俗讲,要求只能更少,承诺只能更多。
用契约式设计的语言来说,先验条件只能弱化,后验条件和类不变量只能强化。
由于Java C++ C#在语法上不支持契约式设计,以上的规法主要是语义上的。
在语法上,对子类型有一定的限制。

以Java为类,
	如果一个类型的多态方法——即非static、非final方法——被子类覆盖,
  后者的返回类型必须与前者相同或是其子类型(如果是基本类型,必须相同),即所谓的协变返回类型(covariant return type);
	后者声明的受检异常(checked exception)不能超出前者的范围;
	后者访问修饰符不能比前者更严格(C++无此限制,但建议不要突破此限制),等等。
	所有这些都贯彻了‘要求更少,承诺更多’的精神,充分保证了子类型的可代换性。

# 类和类型

类偏重语法,强调实现方式;
类型偏重语义,强调行为方式。
类是实现,类型是接口。
里氏替换原则原则是基于类型的。

一个作为抽象数据类型(ADT)的类,通过数据抽象和封装机制而被划分为接口和实现两部分。
	此时的接口就是人们常说的API,是类向外界提供服务的窗口。

一个类又可以通过多态抽象和继承机制而拥有多种抽象类型——主要指关键字interface所代表的类型。
	相对于这些抽象类型而言,类又是具体的。在针对接口编程的原则下,
  类往往不是以其本来面目出现的,而是以某种抽象类型的身份来提供服务的。
	从这个意义上来说,抽象类型是接口,类是实现。

# 接口与实现分离:(图9-1,P257)

API接口分离方式,在语义上通过数据抽象得到了API接口,在语法上通过封装机制隐藏了private实现;
interface 分离方式,在语义通过多态抽象得到了interface接口,在语法上通过继承机制隐藏了class实现。
后一对 接口/实现 站在更高的抽象层次上。

# 总结

OOP将现实中的抽象概念映射为程序中的类型,继承机制进一步将概念的分类体系(taxonomy,[tæk'sɒnəmɪ])
映射为类型的层级结构,使得对象模型能更逼真地模拟现实世界。