看了一早上的面对对象,郁闷中~~ 找了一点相关的基本资料,发一下~~
我在看去年的高程题目:其中有涉及到多态的形式,找了很久还是找不到那几个多态的定义
一般最好是用第一种,最不提倡最后一种,第二种也要小心,因为有类型的隐式转换
[quote]原帖由 "mygod"]已经分的最小了吧[/quote 发表:
没有!还有什么参数多态、过载多态等等,我实在不想去找资料了
如果还要再分的话,可能就是分为:参数个数不同、参数类型不同、参数返回值不同
已经分的最小了吧
水能告诉我,纯多态性和特定多态性又分为哪几种?? 郁闷了一天的问题~~~
( 这就是我找了很久的东西~~)第6章 多 态 性多态性(polymorphism),即对象具有多种形态。在面向对象的编程语言中,多态性是“是一个”关系、消息传递机制、继承、可替换性等概念的自然结果。面向对象编程(OOP)方法的一个主要优点就是,能把这些概念组合起来使用,进而产生了许多代码共享和复用技术。
6.1 多态性的种类纯多态性(pure polymorphism)是指单个函数可用于多个不同类型的参数。在纯多态性中,只有一个函数(代码块),却可以有多种解释(不同的意思)。另一种情况是多个不同的函数(代码块)使用同一个名称,这种情形被称为重载(overload),有时也叫特定多态性(ad hoc polymorphism)。在这两种极端形式之间的是覆盖(override)和延期方法(deferred methods)
6.2 多态变量除了方法覆盖,面向对象语言中的多态性只有通过多态变量(polymorphic variables)和可替换性思想来实现。多态变量具有多面性;也就是说,它能容纳不同类型的值。多态变量体现了可替换性原则。换句话说,尽管多态变量也有一个所期望的类型,但它能容纳所期望类型的任何子类型值。对于动态绑定的语言(如Smalltalk),所有的变量可能都是多态的——任何变量都可以容纳任何类型的值。对于静态类型的语言(如Java),情况会稍微复杂一些。在Java中,多态性是通过变量的声明类型(静态的)与其实际容纳的值类型(动态的)之间的差异来实现的。
6.3 重载(overload)如果同一个方法名对应于多个方法体,我们就说此方法名被重载(overload)了。注意,重载是覆盖(override)的一个必要部分,但这两种方式是不相同的,重载能够在没有覆盖的情况下单独出现。对于重载,只有方法名是多态的——它有多种形式。我们也可以把重载和覆盖想像成另一种方式——只有一种抽象函数,它能接收各种不同类型的参数,而实际执行的代码要由所接收的参数来决定。编译器通常能在编译时确定所使用的方法,然后产生一组惟一的代码,这些代码都是经过优化的6.3.1 现实生活中的重载消息在第1章中,有一个只有重载没有覆盖的例子。例:为了给朋友一个惊喜,在他生日时我想给他送一束鲜花一种办法是,向当地的花商发送一条sendFlowersTo消息;另一种办法是,向我的妻子发送一条同样的消息。花商和我的妻子都能理解这条消息,两者都能做出反应并取得相同的结果。在某种程度上,我也可以这样认为:sendFlowersTo方法是一种妻子和花商都能理解的方法,但他们却使用了不同的算法来响应我的请求。特别注意,本例中没有用继承,妻子和花商的共同超类是人(Human)当然,sendFlowersTo行为并不是人所共有的,如我的牙科医生,虽然他也是人,但如果我向他发送这条消息,他一定会很困惑。6.3.2 重载和强制例: 要开发一个类库,该类库要表示常用的数据结构有许多种数据结构可用来容纳一组元素(例如,集合、包、字典、数组、优先队列等)。这些数据结构中都定义了一个add方法,可以在数据结构中插入新元素。这种情形——用两种完全独立的函数为不同的数据类型提供语义上相似的行动——经常出现在所有的编程语言中,而不只是出现在面向对象的语言中。例: 重载加号运算符“+”。编译器所产生的整型加法代码一定跟浮点加法所产生的代码有很大区别,但对程序员来说,这些操作都是一样的,都是加法函数。在这个例子中,还需要重点指出的一点是,重载并不是所要做的一切。一种语义上独立的操作——强制(coercion),也常常与算术运算有关。强制用于把一种数字类型转换成另一种数字类型。如果允许混合类型的算术运算,则两个数的相加可以用许多不同的方式来解释:有可能提供4种不同的函数,分别是:整数+整数、整数+实数、实数+整数和实数+实数。在这种情况下,只有重载但没有强制。提供两种不同的函数:整数+整数、实数+实数。对于整数+实数和实数+整数,可以把整数强制转换成实数。在这种情况下,把重载和强制结合到了一起。只提供一种函数:实数+实数。在运算前,所有的参数都被强制转换成实数。在这种情况下,就只有强制而没有重载。6.3.3 独立类的重载有两种不同的重载方式。一种是,相同的方法名出现在没有继承关系的多个类中;另一种是,在同一个类中,有多个方法使用相同的名字。
重载并不意味着相似被重载的方法间不需要有任何的语义相似性。它们只是共用了相同的名称。这种重载——独立的、互不相干的方法使用同一名称——并没有被认为是不好的风格,一般也不会产生混乱。实际上,选择一些短的、有意义的名字如add,draw等,有助于理解和正确使用面向对象组件。当需要往集合中加入一个新元素时,调用add方法比调用addNewElement方法,或者调用set_Modul_Addition_Method方法更方便、简明。所有的面向对象语言都允许在不相关的类中使用相同的方法名。这时候,消息接收者类需要区分被重载的方法名然而,这并不意味着这些方法可接收任意类型的参数。Java的静态类型特性仍要求方法指定所有的参数类型。
6.3.4 参数重载另一种风格的重载——在相同的上下文环境中,多个过程(函数或方法)使用同一个名称,通过参数的个数和类型来区别这些过程,这种重载的方式叫参数重载(parametric overloading)。这种重载方式不只是出现在Java语言中,许多功能性语言和一些命令式语言(如Ada)也使用了这种重载方式。参数重载常出现在构造器方法中
6.4 覆盖在一个类中(通常是抽象超类)定义了一个通用的方法,这个方法要由子类来继承和使用。至少在一个子类中,定义了一个同名的方法,这个方法隐藏了子类实例对通用方法的调用(如果在子类中修订了通用的方法,则子类的方法代替了对通用方法的调用)。我们就说,第二个方法覆盖(override)了第一个方法。覆盖通常对类用户都是透明的。而且,与重载不同的是,这两个有覆盖关系的方法在语义上被认为是同一体。
6.5 抽象方法声明为抽象(abstract)的方法也可以认为是延期(deferred)方法。我们在父类中声明它,却留待子类去具体实现。接口(interface)也被认为是一种延期方法。这两种情况都被看作是覆盖的一般形式。在这两种情况下,子类都修改了父类的行为。对于抽象方法,它的行为实际上是空的,它只起一个占位符的作用,所有定义行为的代码都由子类来提供。
6.6 纯多态性许多作者把多态性( polymorphism, 或称纯多态性)术语用于一个方法能接收各种不同类型参数的情形,而用重载(overload)来表示多个方法使用同一个名称。这种用法并没有强加给面向对象语言。例如,在Lisp或ML语言中,很容易写出一个能操作任意类型元素的函数。这个函数是多态的,因为在定义函数的时候,还不知道参数的确切类型。创建多态函数是面向对象编程的一个最强有力的技术。它允许在更高的抽象层次上编写代码,然后根据需要裁减代码,以适应具体的情况。通常,程序员会向此方法的接收者发送更进一步的消息,以实现裁减。这些更进一步的消息通常与定义多态方法的类无关,而是发送给定义在低层类中的延期方法。
第4章 继 承
父类属性的扩展在编程语言里,继承意味着子类的数据和行为是父类相关属性的扩展(一个更大的集)。子类不仅拥有父类的所有属性,而且还可能另外定义了一些新属性。
父类形式的简化另一方面,由于子类是父类的一个更特殊(或称更严格)的形式,因此在某种程度上,也可以说子类是父类的一个简化版。
扩展与简化这种把继承作为一种扩展同时也作为一种收缩的思想,正是面向对象技术强大的原因,同时也会在正常的部署中引起混淆
继承是向下传递的继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性如果Dog是Mammal的子类,而Mammal又是Animal的子类,则Dog不仅继承了Mammal的属性,同时也继承了Animal的属性
子类可以覆盖从父类继承来的行为
可替换性可替换性是面向对象编程中一种强大的软件开发技术。可替换性的意思是:变量声明时指定的类型不必与它所容纳的值类型相一致。这在传统的编程语言中是不允许的,但在面向对象的编程语言中却常常出现。可替换性常发生在使用接口的时候。
判断可替换性是否合法的依据当使用继承从已有的类构建新类时,判断可替换性是否合法的依据如下:子类实例必须拥有父类的所有数据字段。子类实例必须实现父类所定义的所有功能,最起码要通过继承来实现(如果没有明确覆盖的话)子类也可以定义新的功能,这与可替换性讨论无关。子类的实例可以模拟父类的行为,在相似的条件下,使用子类实例来替换父类实例时,应该没有区别。
合法性依据并非总是正确考虑继承的所有使用情况后,上面这条合法性依据并不总是正确。因此,并不是所有通过继承构建的子类都可以代替父类。
子类型子类型用于描述能明显满足可替换性原则的类之间的关系。如果满足以下两个条件,则可把类型B称为类型A的子类型。1:类型B的实例可以合法地赋给声明为类型A的变量。2:当A类型的变量使用B类型的实例时,不会引起行为的改变
子类与子类型子类专指使用继承构建新类的机制。在Java语言中,可以通过extends关键字来辨认。子类型的关系更抽象,只能通过源程序来松散地体现这种关系。在大部分例子中,子类也就是子类型。但也可以使用某种方式来构建不是子类型的子类此外,还可以使用接口来构造子类型,这就使相关的类型间根本不存在继承关系。理解这两个概念间的相似性与区别是很重要的。
继承的形式
特殊化(specialization)继承很多情况下,都是为了特殊化才使用继承和子类化的。在这种形式下,新类是父类的一种特定类型,它能满足父类的所有规范。用这种方式创建的总是子类型,并明显符合可替换性原则。与规范化继承一起,这两种方式构成了继承最理想的方式,也是一个好的设计所应追求的目标。
规范化(specification)继承规范化继承用于保证子类和父类具有某个共同的接口,即所有的子类实现了具有相同方法头的方法。父类中既有已实现的方法,也有只定义了方法头、留待子类去实现的方法。子类只是实现了那些定义在父类却又没有实现的方法。子类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。也就是说,父类定义了某些操作,但并没有去实现它。只有子类才能实现这些操作。在这种情况下,父类有时也被称为抽象规范类。在Java中,关键字abstract确保了必须要构建子类。声明为abstract的类必须被子类化,不可能用new运算符创建这种类的实例。除此之外,方法也能被声明为abstract,同样在创建实例之前,必须覆盖类中所有的抽象方法。规范化继承可以通过以下方式辨认:父类中只是提供了方法头,并没有实现具体的行为,具体的行为必须在子类中实现。
构造(Construction)继承一个类可以从其父类中继承几乎所有需要的功能,只是改变一些用作类接口的方法名,或是修改方法中的参数列表。即使新类和父类之间并不存在抽象概念上的相关性,这种实现也是可行的。
扩展继承如果子类只是往父类中添加新行为,并不修改从父类继承来的任何属性,即是扩展继承。由于父类的功能仍然可以使用,而且并没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的子类还是子类型
限制继承如果子类的行为比父类的少或是更严格时,就是限制继承。和扩展继承一样,当程序员以一个现有的类为基类来构建新类时,常常会出现限制继承,只是这个基类不应该、也不能被修改。限制继承可描述成这么一种技术:它先接收那些继承来的方法,然后使它们无效。由于限制继承违反了可替换性原则,用它创建的子类已不是子类型,因此应该尽可能不用。
合并继承 (多重继承)可以通过合并两个或者更多的抽象特性来形成新的抽象。一个类可以继承自多个父类的能力被称为多重继承
继承方式的总结特殊化:子类是父类的一个特例;也就是说,子类是父类的一个子类型。规范化:父类中定义的行为需要在子类中实现,而父类本身没有实现这些行为。构造:子类利用父类提供的行为,但并不是父类的子类型。扩展:子类添加了一些新功能,但并没有改变继承来的行为。限制:子类限制了一些来自父类的方法的使用。合并:子类从多个父类中继承特性。
多人参与的编程活动一旦可以使用可重用组件来构造程序,编程就从一种个人行为变成了团体的共同努力。一个程序员既可以是一种新抽象类型的开发者,也可以是别的程序员所开发的软件系统的用户。我们也常提到由几个对象组成的组织,其中某个客户对象向某种服务的提供者发出服务请求
第1课 面向对象的思维方式1. 有关术语Object-Oriented ProgrammingOOP面向对象程序设计
2. 为什么OOP能够流行这么久?被实践证明是成功的适用于从小型到大型规模的问题思维方式与解决其他领域的问题的方式类似
3. 范例OOP常被描述为一种新的范例。范例(paradigm)指一种示范性的模型或例子,它提供了一种组织信息的形式
4. 传统的编程和计算模式用来描述计算机执行程序的传统模型称为过程状态模型,或者称为鸽子笼模型。它主要考虑的是:状态、变量、赋值、循环等计算机是一个数据管理器,它遵循一套设计指令,在内存中来回移动,从多个插槽(内存地址)中取出数据,以某种方式改变它们的状态,再把结果放入别的插槽中通过检查插槽中的值,可以确定计算机的状态或计算的结果。尽管这个模型或多或少地描述了计算机内部所发生的事情,但它却无法帮助我们理解计算机是如何解决问题的,它也不是大多数人(除了鸽子的主人以及邮递工人)用于解决问题的方法。
5. 递归:程序由小的相似的计算部件构成20世纪70年代,Alan Kay考虑了计算机的传统设计方式,并提出了程序可以由多个小的相似的计算部件构成。如果把每一个部件看成一个代理,由程序可以由小的计算代理所构成。
6. OOP的定义Alan Kay对OOP的定义:OOP是基于递归设计的原则的:--1. 一切都是对象。--2. 计算通过对象间相互通信,请求其他对象执行动作来实现。对象间通过发送和接收消息来通信。--3. 每个对象都有自己的内存,其中可能包括了其他的对象。--4. 每一个对象都是某个类的实例。类就是一组相似的对象。--5. 类是对象相关行为的储存库。也就是说,同一个类的所有对象都能执行同样的动作。--6. 类被组织成有单个根节点的树状结构,被称为继承层次结构。与类实例相关的内存和行为都会被树结构中的后代自动继承。
7 OOP概念实例:给朋友送花例:我要送花给住在另一城市的Sally.我无法直接送这些花,因此,我将使用了本地花商的服务系统。我将Sally的地址、花的价钱和品种告诉花商Flora,Flora联系Sally所在城市的花商,后者准备这些花,并与送花人联系送花。再深入地研究,可以发现还有更多的人参与其中,如花农、种植园主等。7.1 OOP概念:代理与团队一个面向对象的程序是由一个相互作用的代理团体组成,这些代理被称作对象。每一个对象承担一个角色。每一个对象都提供一种服务或者执行一种动作,以便为团体中其他对象服务。7.2 OOP元素: 对象OOP第1条原则: 1. 一切都是对象。 OOP中的动作是由代理来完成的,这些代理我们称为“实例”( instances)或“对象”(objects).在我们设定的场景中,有很多代理一起工作这些代理包括:我,Sally,花商Flora,Sally所在城市的花商,送花人,花卉批发商,花场主,花农。每一个代理要扮演一个角色,结果就是当所有工作都做完后,我们的目的就达到了。7.3 OOP元素: 消息 OOP第2条原则:2. 计算通过对象间相互通信,请求其他对象执行动作来实现。对象间通过发送和接收消息来通信。OOP中的动作是通过对动作请求(称为消息,messenges)的响应来完成的。一个实例可以接收一条消息,然后完成一个动作并返回一个值。要完成送花的过程,我向Flora发出一条消息,Flora接着向Sally所在城市的花商发出一条消息,后者又向送花人发出一条消息,……7.4 信息隐藏原则注意,作为某对象提供的服务的一个用户,我只需要知道对象将接受的消息的名字。我不需要知道要完成我的要求,需要执行哪些动作。在接收到一条消息后,对象会负责将该项任务完成。7.5 OOP元素: 接收者消息是发给接收这条消息的接收者(receiver)的。不同的接收者对消息的解释可以不同。7.6 送达对象不同,对消息的解释也可能不同var Flora: Florist; Elizabeth : Friend; Kenneth : Dentist;begin Flora.sendFlowersTo(Sally); {可以完成} Elizabeth.sendFlowersTo(Sally); {也可以完成} Kenneth.sendFlowersTo(Sally); {也许无法完成}end;同一条消息,取决于送达对象的不同,可能得到不同的结果。7.7 行为、后期绑定不同的对象即使接收的是同样的消息,对象所完成的动作(行为,behavior)也很可能不同。确定完成何种行为的决定可以在运行时刻再做,这称为后期绑定(late binding)。同一个名字表示完成不同的操作,这是多态性(polymorphism)的一种形式。7.8 OOP元素: 递归设计 OOP第3条原则:3. 每个对象都有自己的内存,其中可能包括了其他的对象。7.9 不干预原则允许对象以任何它认为合适的不干涉其他对象的方式来完成任务,而不要干预它。7.10 OOP元素: 类OOP第4、5条原则: 4. 每一个对象都是某个类的实例。类是一组相似的对象 5. 类是对象相关行为的储存库(repository)。即同一个类的所有对象都能执行同样的动作。
对Flora行为的预期是根据对所有花商的一般行为来确定Flora是花商(Florist)类(class)的一个实例(instance)
行为是与类相联系,而不是与单个实例相联系的。属于某一类的实例的所有对象,对相似的消息采取同样的方法来响应。7.11 类别的层次除了知道Flora是花商外,我还知道他是商人、人类、哺乳动物、物质对象。在每一层次上,都可以了解特定的信息,这些信息适用于所有较低层次。7.12 类的层次7.13 OOP元素: 继承 OOP第6条原则:6. 类被组织成有单个根节点的树状结构,称为继承层次结构。与类实例相关的内存和行为都会被树结构中的后代自动继承。在类层次结构中与某层相联系的信息(数据、行为)都会自动地提供地该层次结构的较低层次中。7.14 OOP元素: 覆盖子类可以改变其从父类中继承来的信息:所有哺乳动物是胎生后代的鸭嘴兽是卵生的哺乳动物继承与覆盖相结合,体现了面向对象的巨大能力
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
暂无简介
文章 0 评论 0
接受
发布评论
评论(9)
我在看去年的高程题目:
其中有涉及到多态的形式,找了很久还是找不到那几个多态的定义
一般最好是用第一种,最不提倡最后一种,第二种也要小心,因为有类型的隐式转换
[quote]原帖由 "mygod"]已经分的最小了吧[/quote 发表:
没有!
还有什么参数多态、过载多态等等,我实在不想去找资料了
如果还要再分的话,可能就是分为:参数个数不同、参数类型不同、参数返回值不同
已经分的最小了吧
水能告诉我,纯多态性和特定多态性又分为哪几种?? 郁闷了一天的问题~~~
( 这就是我找了很久的东西~~)
第6章 多 态 性
多态性(polymorphism),即对象具有多种形态。
在面向对象的编程语言中,多态性是“是一个”关系、消息传递机制、继承、可替换性等概念的自然结果。
面向对象编程(OOP)方法的一个主要优点就是,能把这些概念组合起来使用,进而产生了许多代码共享和复用技术。
6.1 多态性的种类
纯多态性(pure polymorphism)是指单个函数可用于多个不同类型的参数。
在纯多态性中,只有一个函数(代码块),却可以有多种解释(不同的意思)。
另一种情况是多个不同的函数(代码块)使用同一个名称,这种情形被称为重载(overload),有时也叫特定多态性(ad hoc polymorphism)。
在这两种极端形式之间的是覆盖(override)和延期方法(deferred methods)
6.2 多态变量
除了方法覆盖,面向对象语言中的多态性只有通过多态变量(polymorphic variables)和可替换性思想来实现。
多态变量具有多面性;也就是说,它能容纳不同类型的值。
多态变量体现了可替换性原则。换句话说,尽管多态变量也有一个所期望的类型,但它能容纳所期望类型的任何子类型值。
对于动态绑定的语言(如Smalltalk),所有的变量可能都是多态的——任何变量都可以容纳任何类型的值。
对于静态类型的语言(如Java),情况会稍微复杂一些。在Java中,多态性是通过变量的声明类型(静态的)与其实际容纳的值类型(动态的)之间的差异来实现的。
6.3 重载(overload)
如果同一个方法名对应于多个方法体,我们就说此方法名被重载(overload)了。注意,重载是覆盖(override)的一个必要部分,但这两种方式是不相同的,重载能够在没有覆盖的情况下单独出现。
对于重载,只有方法名是多态的——它有多种形式。
我们也可以把重载和覆盖想像成另一种方式——只有一种抽象函数,它能接收各种不同类型的参数,而实际执行的代码要由所接收的参数来决定。编译器通常能在编译时确定所使用的方法,然后产生一组惟一的代码,这些代码都是经过优化的
6.3.1 现实生活中的重载消息
在第1章中,有一个只有重载没有覆盖的例子。
例:为了给朋友一个惊喜,在他生日时我想给他送一束鲜花
一种办法是,向当地的花商发送一条sendFlowersTo消息;
另一种办法是,向我的妻子发送一条同样的消息。
花商和我的妻子都能理解这条消息,两者都能做出反应并取得相同的结果。在某种程度上,我也可以这样认为:sendFlowersTo方法是一种妻子和花商都能理解的方法,但他们却使用了不同的算法来响应我的请求。
特别注意,本例中没有用继承,妻子和花商的共同超类是人(Human)
当然,sendFlowersTo行为并不是人所共有的,如我的牙科医生,虽然他也是人,但如果我向他发送这条消息,他一定会很困惑。
6.3.2 重载和强制
例: 要开发一个类库,该类库要表示常用的数据结构
有许多种数据结构可用来容纳一组元素(例如,集合、包、字典、数组、优先队列等)。这些数据结构中都定义了一个add方法,可以在数据结构中插入新元素。
这种情形——用两种完全独立的函数为不同的数据类型提供语义上相似的行动——经常出现在所有的编程语言中,而不只是出现在面向对象的语言中。
例: 重载加号运算符“+”。编译器所产生的整型加法代码一定跟浮点加法所产生的代码有很大区别,但对程序员来说,这些操作都是一样的,都是加法函数。
在这个例子中,还需要重点指出的一点是,重载并不是所要做的一切。一种语义上独立的操作——强制(coercion),也常常与算术运算有关。
强制用于把一种数字类型转换成另一种数字类型。如果允许混合类型的算术运算,则两个数的相加可以用许多不同的方式来解释:
有可能提供4种不同的函数,分别是:整数+整数、整数+实数、实数+整数和实数+实数。在这种情况下,只有重载但没有强制。
提供两种不同的函数:整数+整数、实数+实数。对于整数+实数和实数+整数,可以把整数强制转换成实数。在这种情况下,把重载和强制结合到了一起。
只提供一种函数:实数+实数。在运算前,所有的参数都被强制转换成实数。在这种情况下,就只有强制而没有重载。
6.3.3 独立类的重载
有两种不同的重载方式。
一种是,相同的方法名出现在没有继承关系的多个类中;
另一种是,在同一个类中,有多个方法使用相同的名字。
重载并不意味着相似
被重载的方法间不需要有任何的语义相似性。它们只是共用了相同的名称。
这种重载——独立的、互不相干的方法使用同一名称——并没有被认为是不好的风格,一般也不会产生混乱。
实际上,选择一些短的、有意义的名字如add,draw等,有助于理解和正确使用面向对象组件。当需要往集合中加入一个新元素时,调用add方法比调用addNewElement方法,或者调用set_Modul_Addition_Method方法更方便、简明。
所有的面向对象语言都允许在不相关的类中使用相同的方法名。这时候,消息接收者类需要区分被重载的方法名
然而,这并不意味着这些方法可接收任意类型的参数。Java的静态类型特性仍要求方法指定所有的参数类型。
6.3.4 参数重载
另一种风格的重载——在相同的上下文环境中,多个过程(函数或方法)使用同一个名称,通过参数的个数和类型来区别这些过程,这种重载的方式叫参数重载(parametric overloading)。
这种重载方式不只是出现在Java语言中,许多功能性语言和一些命令式语言(如Ada)也使用了这种重载方式。
参数重载常出现在构造器方法中
6.4 覆盖
在一个类中(通常是抽象超类)定义了一个通用的方法,这个方法要由子类来继承和使用。至少在一个子类中,定义了一个同名的方法,这个方法隐藏了子类实例对通用方法的调用(如果在子类中修订了通用的方法,则子类的方法代替了对通用方法的调用)。我们就说,第二个方法覆盖(override)了第一个方法。
覆盖通常对类用户都是透明的。而且,与重载不同的是,这两个有覆盖关系的方法在语义上被认为是同一体。
6.5 抽象方法
声明为抽象(abstract)的方法也可以认为是延期(deferred)方法。我们在父类中声明它,却留待子类去具体实现。
接口(interface)也被认为是一种延期方法。
这两种情况都被看作是覆盖的一般形式。
在这两种情况下,子类都修改了父类的行为。
对于抽象方法,它的行为实际上是空的,它只起一个占位符的作用,所有定义行为的代码都由子类来提供。
6.6 纯多态性
许多作者把多态性( polymorphism, 或称纯多态性)术语用于一个方法能接收各种不同类型参数的情形,而用重载(overload)来表示多个方法使用同一个名称。这种用法并没有强加给面向对象语言。例如,在Lisp或ML语言中,很容易写出一个能操作任意类型元素的函数。这个函数是多态的,因为在定义函数的时候,还不知道参数的确切类型。
创建多态函数是面向对象编程的一个最强有力的技术。它允许在更高的抽象层次上编写代码,然后根据需要裁减代码,以适应具体的情况。通常,程序员会向此方法的接收者发送更进一步的消息,以实现裁减。这些更进一步的消息通常与定义多态方法的类无关,而是发送给定义在低层类中的延期方法。
第4章 继 承
父类属性的扩展
在编程语言里,继承意味着子类的数据和行为是父类相关属性的扩展(一个更大的集)。
子类不仅拥有父类的所有属性,而且还可能另外定义了一些新属性。
父类形式的简化
另一方面,由于子类是父类的一个更特殊(或称更严格)的形式,因此在某种程度上,也可以说子类是父类的一个简化版。
扩展与简化
这种把继承作为一种扩展同时也作为一种收缩的思想,正是面向对象技术强大的原因,同时也会在正常的部署中引起混淆
继承是向下传递的
继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性
如果Dog是Mammal的子类,而Mammal又是Animal的子类,则Dog不仅继承了Mammal的属性,同时也继承了Animal的属性
子类可以覆盖从父类继承来的行为
可替换性
可替换性是面向对象编程中一种强大的软件开发技术。
可替换性的意思是:变量声明时指定的类型不必与它所容纳的值类型相一致。
这在传统的编程语言中是不允许的,但在面向对象的编程语言中却常常出现。
可替换性常发生在使用接口的时候。
判断可替换性是否合法的依据
当使用继承从已有的类构建新类时,判断可替换性是否合法的依据如下:
子类实例必须拥有父类的所有数据字段。
子类实例必须实现父类所定义的所有功能,
最起码要通过继承来实现(如果没有明确覆盖的话)
子类也可以定义新的功能,这与可替换性讨论无关。
子类的实例可以模拟父类的行为,在相似的条件下,使用子类实例来替换父类实例时,应该没有区别。
合法性依据并非总是正确
考虑继承的所有使用情况后,上面这条合法性依据并不总是正确。因此,并不是所有通过继承构建的子类都可以代替父类。
子类型
子类型用于描述能明显满足可替换性原则的类之间的关系。
如果满足以下两个条件,则可把类型B称为类型A的子类型。
1:类型B的实例可以合法地赋给声明为类型A的变量。
2:当A类型的变量使用B类型的实例时,不会引起行为的改变
子类与子类型
子类专指使用继承构建新类的机制。
在Java语言中,可以通过extends关键字来辨认。
子类型的关系更抽象,只能通过源程序来松散地体现这种关系。
在大部分例子中,子类也就是子类型。
但也可以使用某种方式来构建不是子类型的子类
此外,还可以使用接口来构造子类型,这就使相关的类型间根本不存在继承关系。
理解这两个概念间的相似性与区别是很重要的。
继承的形式
特殊化(specialization)继承
很多情况下,都是为了特殊化才使用继承和子类化的。
在这种形式下,新类是父类的一种特定类型,它能满足父类的所有规范。
用这种方式创建的总是子类型,并明显符合可替换性原则。
与规范化继承一起,这两种方式构成了继承最理想的方式,也是一个好的设计所应追求的目标。
规范化(specification)继承
规范化继承用于保证子类和父类具有某个共同的接口,即所有的子类实现了具有相同方法头的方法。
父类中既有已实现的方法,也有只定义了方法头、留待子类去实现的方法。子类只是实现了那些定义在父类却又没有实现的方法。
子类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。
也就是说,父类定义了某些操作,但并没有去实现它。只有子类才能实现这些操作。
在这种情况下,父类有时也被称为抽象规范类。
在Java中,关键字abstract确保了必须要构建子类。声明为abstract的类必须被子类化,不可能用new运算符创建这种类的实例。除此之外,方法也能被声明为abstract,同样在创建实例之前,必须覆盖类中所有的抽象方法。
规范化继承可以通过以下方式辨认:父类中只是提供了方法头,并没有实现具体的行为,具体的行为必须在子类中实现。
构造(Construction)继承
一个类可以从其父类中继承几乎所有需要的功能,只是改变一些用作类接口的方法名,或是修改方法中的参数列表。
即使新类和父类之间并不存在抽象概念上的相关性,这种实现也是可行的。
扩展继承
如果子类只是往父类中添加新行为,并不修改从父类继承来的任何属性,即是扩展继承。
由于父类的功能仍然可以使用,而且并没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的子类还是子类型
限制继承
如果子类的行为比父类的少或是更严格时,就是限制继承。
和扩展继承一样,当程序员以一个现有的类为基类来构建新类时,常常会出现限制继承,只是这个基类不应该、也不能被修改。
限制继承可描述成这么一种技术:它先接收那些继承来的方法,然后使它们无效。
由于限制继承违反了可替换性原则,用它创建的子类已不是子类型,因此应该尽可能不用。
合并继承 (多重继承)
可以通过合并两个或者更多的抽象特性来形成新的抽象。
一个类可以继承自多个父类的能力被称为多重继承
继承方式的总结
特殊化:子类是父类的一个特例;也就是说,子类是父类的一个子类型。
规范化:父类中定义的行为需要在子类中实现,而父类本身没有实现这些行为。
构造:子类利用父类提供的行为,但并不是父类的子类型。
扩展:子类添加了一些新功能,但并没有改变继承来的行为。
限制:子类限制了一些来自父类的方法的使用。
合并:子类从多个父类中继承特性。
多人参与的编程活动
一旦可以使用可重用组件来构造程序,编程就从一种个人行为变成了团体的共同努力。
一个程序员既可以是一种新抽象类型的开发者,也可以是别的程序员所开发的软件系统的用户。
我们也常提到由几个对象组成的组织,其中某个客户对象向某种服务的提供者发出服务请求
第1课 面向对象的思维方式
1. 有关术语
Object-Oriented Programming
OOP
面向对象程序设计
2. 为什么OOP能够流行这么久?
被实践证明是成功的
适用于从小型到大型规模的问题
思维方式与解决其他领域的问题的方式类似
3. 范例
OOP常被描述为一种新的范例。
范例(paradigm)指一种示范性的模型或例子,它提供了一种组织信息的形式
4. 传统的编程和计算模式
用来描述计算机执行程序的传统模型称为过程状态模型,或者称为鸽子笼模型。它主要考虑的是:
状态、变量、赋值、循环等
计算机是一个数据管理器,它遵循一套设计指令,在内存中来回移动,从多个插槽(内存地址)中取出数据,以某种方式改变它们的状态,再把结果放入别的插槽中通过检查插槽中的值,可以确定计算机的状态或计算的结果。
尽管这个模型或多或少地描述了计算机内部所发生的事情,但它却无法帮助我们理解计算机是如何解决问题的,它也不是大多数人(除了鸽子的主人以及邮递工人)用于解决问题的方法。
5. 递归:程序由小的相似的计算部件构成
20世纪70年代,Alan Kay考虑了计算机的传统设计方式,并提出了程序可以由多个小的相似的计算部件构成。如果把每一个部件看成一个代理,由程序可以由小的计算代理所构成。
6. OOP的定义
Alan Kay对OOP的定义:
OOP是基于递归设计的原则的:
--1. 一切都是对象。
--2. 计算通过对象间相互通信,请求其他对象执行动作来实现。对象间通过发送和接收消息来通信。
--3. 每个对象都有自己的内存,其中可能包括了其他的对象。
--4. 每一个对象都是某个类的实例。类就是一组相似的对象。
--5. 类是对象相关行为的储存库。也就是说,同一个类的所有对象都能执行同样的动作。
--6. 类被组织成有单个根节点的树状结构,被称为继承层次结构。与类实例相关的内存和行为都会被树结构中的后代自动继承。
7 OOP概念实例:给朋友送花
例:我要送花给住在另一城市的Sally.
我无法直接送这些花,因此,我将使用了本地花商的服务系统。
我将Sally的地址、花的价钱和品种告诉花商Flora,Flora联系Sally所在城市的花商,后者准备这些花,并与送花人联系送花。
再深入地研究,可以发现还有更多的人参与其中,如花农、种植园主等。
7.1 OOP概念:代理与团队
一个面向对象的程序是由一个相互作用的代理团体组成,这些代理被称作对象。
每一个对象承担一个角色。
每一个对象都提供一种服务或者执行一种动作,以便为团体中其他对象服务。
7.2 OOP元素: 对象
OOP第1条原则:
1. 一切都是对象。
OOP中的动作是由代理来完成的,这些代理我们称为“实例”( instances)或“对象”(objects).
在我们设定的场景中,有很多代理一起工作
这些代理包括:我,Sally,花商Flora,Sally所在城市的花商,送花人,花卉批发商,花场主,花农。
每一个代理要扮演一个角色,结果就是当所有工作都做完后,我们的目的就达到了。
7.3 OOP元素: 消息
OOP第2条原则:
2. 计算通过对象间相互通信,请求其他对象执行动作来实现。对象间通过发送和接收消息来通信。
OOP中的动作是通过对动作请求(称为消息,messenges)的响应来完成的。一个实例可以接收一条消息,然后完成一个动作并返回一个值。
要完成送花的过程,我向Flora发出一条消息,Flora接着向Sally所在城市的花商发出一条消息,后者又向送花人发出一条消息,……
7.4 信息隐藏原则
注意,作为某对象提供的服务的一个用户,我只需要知道对象将接受的消息的名字。
我不需要知道要完成我的要求,需要执行哪些动作。
在接收到一条消息后,对象会负责将该项任务完成。
7.5 OOP元素: 接收者
消息是发给接收这条消息的接收者(receiver)的。
不同的接收者对消息的解释可以不同。
7.6 送达对象不同,对消息的解释也可能不同
var
Flora: Florist;
Elizabeth : Friend;
Kenneth : Dentist;
begin
Flora.sendFlowersTo(Sally); {可以完成}
Elizabeth.sendFlowersTo(Sally); {也可以完成}
Kenneth.sendFlowersTo(Sally); {也许无法完成}
end;
同一条消息,取决于送达对象的不同,可能得到不同的结果。
7.7 行为、后期绑定
不同的对象即使接收的是同样的消息,对象所完成的动作(行为,behavior)也很可能不同。
确定完成何种行为的决定可以在运行时刻再做,这称为后期绑定(late binding)。
同一个名字表示完成不同的操作,这是多态性(polymorphism)的一种形式。
7.8 OOP元素: 递归设计
OOP第3条原则:
3. 每个对象都有自己的内存,其中可能包括了其他的对象。
7.9 不干预原则
允许对象以任何它认为合适的不干涉其他对象的方式来完成任务,而不要干预它。
7.10 OOP元素: 类
OOP第4、5条原则:
4. 每一个对象都是某个类的实例。类是一组相似的对象
5. 类是对象相关行为的储存库(repository)。即同一个类的所有对象都能执行同样的动作。
对Flora行为的预期是根据对所有花商的一般行为来确定
Flora是花商(Florist)类(class)的一个实例(instance)
行为是与类相联系,而不是与单个实例相联系的。
属于某一类的实例的所有对象,对相似的消息采取同样的方法来响应。
7.11 类别的层次
除了知道Flora是花商外,我还知道他是商人、人类、哺乳动物、物质对象。
在每一层次上,都可以了解特定的信息,这些信息适用于所有较低层次。
7.12 类的层次
7.13 OOP元素: 继承
OOP第6条原则:
6. 类被组织成有单个根节点的树状结构,称为继承层次结构。与类实例相关的内存和行为都会被树结构中的后代自动继承。
在类层次结构中与某层相联系的信息(数据、行为)都会自动地提供地该层次结构的较低层次中。
7.14 OOP元素: 覆盖
子类可以改变其从父类中继承来的信息:
所有哺乳动物是胎生后代的
鸭嘴兽是卵生的哺乳动物
继承与覆盖相结合,体现了面向对象的巨大能力