text: (window.webpackJsonp=window.webpackJsonp||[]).push([[35],{279:function(a,t,s){"use strict";s.r(t);var n=s(28),e=Object(n.a)({},(function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":a.$parent.slotKey}},[s("h1",{attrs:{id:"继承关系-继承财富，更要继承责任"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#继承关系-继承财富，更要继承责任"}},[a._v("#")]),a._v(" 继承关系——继承财富，更要继承责任")]),a._v(" "),s("h2",{attrs:{id:"什么是继承？"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#什么是继承？"}},[a._v("#")]),a._v(" 什么是继承？")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("在UML中，通常使用泛化（generalization）来表达继承关系，从逻辑上来说，比继承或遗传更准确。\n更确切的说，泛化强调的是概念关系，继承强调的语言机制。\n继承是子类继承父类，泛化是父类泛化子类。\n\n从子类到超类的泛化是一种概念抽象（abstraction）的过程；\n相反，从超类到子类的特化，是一种概念细化（refinement）的过程。\n这两种过程在设计中往往是交替采用的。\n"),s("span",{pre:!0,attrs:{class:"token title important"}},[s("span",{pre:!0,attrs:{class:"token punctuation"}},[a._v("##")]),a._v(" 继承的类型")]),a._v("\n"),s("span",{pre:!0,attrs:{class:"token list punctuation"}},[a._v("*")]),a._v(" 接口继承\n```md\n不是为了代码重用，而是为了代码被重用。\n")])])]),s("ul",[s("li",[a._v("实现继承")])]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("\t消费可重用的旧代码，接口继承生产可重用的新代码。\n")])])]),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("一个是重视权利的索取型的，一个是重视义务的奉献型。\n\nOOP更提倡后者，因为它是自下而上的方式搭建系统的。\n如果代码都倾向重用而不是被重用，做顶不做底，就会头重脚轻。\n")])])]),s("h2",{attrs:{id:"子类型"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#子类型"}},[a._v("#")]),a._v(" 子类型")]),a._v(" "),s("ul",[s("li",[a._v("继承又被称为子类化（subcalssing）")])]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("接口继承进而被称为子类型化（subtyping）\n子类不一定是子类型，比如C++中私有继承产生的子类不是子类型。\n子类型也不一定是子类，比如int是long的子类型，但它不是类。\n")])])]),s("ul",[s("li",[a._v("子类型的关键是可代换性（substitutability）")])]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("具体地说，类型A的子类型B应该满足下面的条件：\n将程序中类型A的对象置换为类型B的对象，不行影响程序的合理性和正确性。\n这就是著名的里氏替换原则（Liskov Substitution Principle，LSP）\n")])])]),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("子类型是一种严格的数学关系，对比数据类型的两个要素：\n允许取值的集合和允许参与的运算，\n\n如果B类型是A类型的子类型，那么B的取值范围一定不会超过A，\n同时A能参与的运算B也能参与，并且具有相同的语义。\n例如 long 和int。\n")])])]),s("h2",{attrs:{id:"里氏替换原则（liskov-substitution-principle，lsp）"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#里氏替换原则（liskov-substitution-principle，lsp）"}},[a._v("#")]),a._v(" 里氏替换原则（Liskov Substitution Principle，LSP）")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("实际上，里氏替换原则本质是为了保证规范抽象（指使用者和实现者之间的一种契约）。\n\n里氏替换原则原则让规范抽象不在局限于单个类型，\n而延伸到了真个类型族上，并且规范是向上兼容（forward compatible）的。\n说通俗些，这就好比一个人作为某项承诺，他的子子孙孙都得遵守，只能加强，不能消弱。\n只有这样，客户用到某个抽象类型（interface 或 abstract class）时，才能完全不关心其实现的具体类型。\n\n与数据抽象类似，这也是一种接口与实现的分离，\n只是接口所在的类型和实现所在的类型不同，相应的代码也不在粘合在一起。\n不妨认为这是一种推广形式的数据抽象——多态数据类型（polymorphic data abstraction），简称多态抽象。\n")])])]),s("h2",{attrs:{id:"多态"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#多态"}},[a._v("#")]),a._v(" 多态")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("指一种类型能耐具备多种类型的形式，多态抽象是建立在类型层级的基础上的。\n或者说，接口继承在遵循里氏替换原则的前提下，通过接口重用达成规范重用，\n保证了多态抽象，进而维护了开闭原则，让客户一劳永逸地享受接口服务而无后顾之忧\n\n实现继承也应该遵循，这既是一种义务，也是一种权利\n说是义务，因为实现继承在继承实现的同时也继承了接口，\n按理也应继承接口的规范，说是权力，因为接口的继承能让代码被重用。\n如果只是为了重用基类的代码，并被系统重用他的接口，那就应该采用组合而不是继承的方式。\n\n这涉及一个通用的编程原则，即尽可能地弥合语法和语义之间的缝隙，以压缩代码臭虫的生成空间。\n因为特定的语法总会对应特定的语义，如果某些对应并非原作者的初衷，便有误导之嫌。\n\n实际上程序中的逻辑错误大部分来之两个方面的‘配合’：\n\t因失策而误用了语法，以及因失察而误解了语义。\n\t为了避免这列歧误，如果采用类型继承的语法规则，就要遵守多态抽象的语义规范。\n  否则必须改弦更张，放弃继承而采用组合。以此而论，里氏替换原则不过是语法与语义相符原则的一个推论。\n")])])]),s("h2",{attrs:{id:"继承的关键"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#继承的关键"}},[a._v("#")]),a._v(" 继承的关键")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("与其说继承是一种实现的技巧，不如说是一种规范的技巧；\n\n与其说继承是一种is-a或is-kind-of的关系，\n不如说是一种behaves-like-a 或 is-substitutable-for的关系。\n\tis-a的说法过于笼统，可以表示泛化——企鹅是一种鸟，分类——阿花是一只狗，角色——医生是一个人，\n  特征或能力——动物是可以动的，等等。这些关系并不是所有的都适合用继承来表示的。\n\t因此‘is-a’只能作为判断继承关系的一个必要条件，但不能作为充分条件。\n\n继承的关键在于，可代换性，即准守接口的规范。\n而behaves-like-a 表明一个子类型的行为表现正如超类型一样，更贴切。\n概念抽象只是手段，规范抽象才是依据。\n")])])]),s("ul",[s("li",[a._v("如何保证类型族的规范抽象？")])]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("任何类型都应该保持或强化其超类型的规范，绝对不能弱化规范。\n通俗讲，要求只能更少，承诺只能更多。\n用契约式设计的语言来说，先验条件只能弱化，后验条件和类不变量只能强化。\n由于Java C++ C#在语法上不支持契约式设计，以上的规法主要是语义上的。\n")])])]),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("在语法上，对子类型有一定的限制。\n\n以Java为类，\n\t如果一个类型的多态方法——即非static、非final方法——被子类覆盖，\n  后者的返回类型必须与前者相同或是其子类型（如果是基本类型，必须相同），即所谓的协变返回类型（covariant return type）；\n\t后者声明的受检异常（checked exception）不能超出前者的范围；\n\t后者访问修饰符不能比前者更严格（C++无此限制，但建议不要突破此限制），等等。\n\t所有这些都贯彻了‘要求更少，承诺更多’的精神，充分保证了子类型的可代换性。\n")])])]),s("h2",{attrs:{id:"类和类型"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#类和类型"}},[a._v("#")]),a._v(" 类和类型")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("类偏重语法，强调实现方式；\n类型偏重语义，强调行为方式。\n类是实现，类型是接口。\n里氏替换原则原则是基于类型的。\n\n一个作为抽象数据类型（ADT）的类，通过数据抽象和封装机制而被划分为接口和实现两部分。\n\t此时的接口就是人们常说的API，是类向外界提供服务的窗口。\n\n一个类又可以通过多态抽象和继承机制而拥有多种抽象类型——主要指关键字interface所代表的类型。\n\t相对于这些抽象类型而言，类又是具体的。在针对接口编程的原则下，\n  类往往不是以其本来面目出现的，而是以某种抽象类型的身份来提供服务的。\n\t从这个意义上来说，抽象类型是接口，类是实现。\n")])])]),s("h2",{attrs:{id:"接口与实现分离：（图9-1，p257）"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#接口与实现分离：（图9-1，p257）"}},[a._v("#")]),a._v(" 接口与实现分离：（图9-1，P257）")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("API接口分离方式，在语义上通过数据抽象得到了API接口，在语法上通过封装机制隐藏了private实现；\ninterface 分离方式，在语义通过多态抽象得到了interface接口，在语法上通过继承机制隐藏了class实现。\n后一对 接口/实现 站在更高的抽象层次上。\n")])])]),s("h2",{attrs:{id:"总结"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#总结"}},[a._v("#")]),a._v(" 总结")]),a._v(" "),s("div",{staticClass:"language-md extra-class"},[s("pre",{pre:!0,attrs:{class:"language-md"}},[s("code",[a._v("OOP将现实中的抽象概念映射为程序中的类型，继承机制进一步将概念的分类体系（taxonomy，[tæk'sɒnəmɪ]）\n映射为类型的层级结构，使得对象模型能更逼真地模拟现实世界。\n")])])])])}),[],!1,null,null,null);t.default=e.exports}}]);