考试题型:
简答
读程序
设计模式

应试资料

历年题目:
年份
链接
2022
2021
2020
2019
2018

考点

  • 面向对象的基本概念:
    • 什么是:为了快速高效率、大批量的定义对象准备的模板。
    • 什么是对象?包括属性(静态)+行为(动态)
    • 封装和信息隐藏
    • 方法调用(消息)
    • 八类继承(提倡规范化继承和特殊继承)
    • 方法绑定和改写(重置)
      • 内存布局
      • 复制和克隆
      • 函数签名
    • 多态
    • 重载
    • 重定义:和改写比较类似
    • 改写(重置)的语义:代替(子类不调父类方法)/改进。
    • 纯多态
    • 反射和内省
    • 框架
  • 设计原则:OCP最重要
  • 设计模式
    • 需要掌握代码的主体结构、框架和UML
    • 创建型模式:简单工厂、工厂方法、抽象工厂、单例(饿汉、懒汉)
    • 结构型模式:适配器、代理、桥接、装饰
    • 责任链、观察者、策略、命令
 

一、面向对象思想

基本概念

对象
  • 对象是独立存在的客观事物,它由一组属性和一组操作构成。
  • 属性操作是对象的两大要素。属性是对象静态特征的描述,操作是对象动态特征的描述。
  • 属性一般只能通过执行对象的操作来改变
  • 操作又称为方法或服务,它描述了对象执行的功能。通过消息传递,还可以为其它对象使用。
 
对象性质
  • 封装性:信息隐藏
  • 自治性:主动数据
  • 通信性:并发
  • 暂存性:作用域/期
  • 永久性:文档串行化——文档对象的串行化是指对象的持续性,即对象可以将其当前状态,由其成员变量的值表示,写入到永久性存储体(通常是指磁盘)中。下次则可以从永久性存储体中读取对象的状态,从而重建对象。这种对象的保存和恢复的过程称为串行化。
 
面向对象基本思想
  • 要点1:任何事物都是对象,对象有属性和方法。复杂对象可以由相对简单的对象以某种方式构成。
  • 要点2:通过类比发现对象间的相似性,即对象间的共同属性,是构成对象类的依据。
  • 要点3:对象间的相互联系是通过传递“消息”来完成的。通过对象之间的消息通信驱动对象执行一系列的操作从而完成某一任务。

特性

封装

继承

多态

多态性是指一般类中定义的属性和服务,在特殊类中不改变其名字,但通过各自不同的实现后,可以具有不同的数据类型或具有不同的行为。
 
 

面向对象概念

  • 非面向对象编程:对于非面向对象编程,程序往往是面向过程或者面向数据的。
  • 面向对象编程:程序被划分为一组通信的对象,每个对象均封装了关于某个概念所有行为和信息。

抽象

抽象是指对于一个过程或者一件制品的某些细节有目的的隐藏,以便把其他方面、细节或者结构表达得更加清楚。抽象,是控制复杂性时最重要的工具。
在典型的OOP程序中,有许多级抽象,更高层次的抽象部分地体现了面向对象程序面向对象的特征:
  1. 团体:在最高级别上,程序被视为一个对象的“团体”,这些对象间相互作用,以完成共同的目标。
    1. 在面向对象程序开发过程中,关于“团体”有两个层次的含义:
      • 程序员的团体,他们在现实世界中相互作用,以便开发出应用程序来。
      • 这些程序员创建的对象的团体,它们在虚拟世界中相互作用,以完成它们的共同目标。
  1. 单元:许多语言允许协同工作的对象组合到一个“单元”(unit)中。
    1. 例如,Java的“包” (packages),C++的“名字空间”(name spaces),Delphi中的“单元”(units)。这些单元允许某些特定的名称暴露在单元以外,而其他特征则隐藏在单元中。
  1. CS:处理两个对象之间的交互。
    1. 涉及两层抽象:一个对象向另一个对象提供服务,二者之间以通信来交互;消息传递。该级别抽象通常用接口来表示。定义行为,但不描述如何来实现。
  1. 服务实现方式:考虑抽象行为的具体实现方式。
  1. 具体实现:关注执行一个方法的具体操作实现。
抽象的思想可划分为不同的形式:
  • 分治法:将一层划分为多个组成部分,比较传统。
  • 特殊化(具体化、专门化):面向对象的语言,常常使用这种形式的抽象
  • 不同视角:对同一件物品提供不同的视角。每一个视角会强调某一些细节而忽略其他细节,因此,对同一对象描述出不同的特性。
  • 分类:当系统中组件数量变大时,常用分类(Catalogs)来进行组织。
  • 组合:由少量简单的形式,根据一些组合规则,构建出新的形式。
  • 特化分层(分类法):使用特殊化的层次来构建抽象。
  • 模式:在我们遇到新问题时,大多数人都会查看已经解决过的老问题中,是否有与新问题相似的情况。以前的问题可以作为一个解决问题的模型,略做修改可能就能解决新问题了。这就是软件模式(pattern)的思想。
鸭嘴兽提醒我们,总会有例外(非标准行为) 。面向对象的语言,也需要有一种机制来覆盖从上一级继承来的信息。
抽象机制的发展过程:
  1. 汇编语言:最早的抽象。
  1. 过程/函数
  1. 模块:解决全局名称空间拥挤。
  1. 抽象数据类型:把接口的概念和实现的概念分离开来。
  1. 以服务为中心
  1. 消息、继承和多态

类和方法

封装

封装(利用数据抽象进行编程)的作用:
  • 避免重复代码
  • 保护类受到不必要的修改

类定义与实例

使用实例来表示类的一个具体代表或范例。实例包括实例变量(数据成员/数据字段)。对象 = 状态(实例变量) + 行为(方法)
对象外部看,客户只能看到对象的行为;对象内部看,方法通过修改对象的状态,以及和其他对象的相互作用,提供了适当的行为。
变量的静态类是用来声明变量的类;变量的动态类是与变量值相关的类。
为了增强可读性,类在声明时字段次序建议:
  1. 先列出主要特征,次要的列在后面。
  1. 私有数据字段列在后面。
  1. 构造函数列在前面。
Java、C#中类的实现是直接放在类定义中的,C++则将定义和实现分离。对于Java来讲,可以通过接口实现分离。接口的特点:
  • 不提供实现
  • 接口定义新类型、可以声明变量
  • 类的实例可以赋值给接口类型变量
方法体放在类定义之外有两个原因。
  1. 首先,多于一条语句的方法体会使类定义的其他特征变得模糊,因此移开代码比较长的方法体可以改善程序的可读性(然而,可读性是对于观察者的眼光而言,并不是所有的程序员都认为这种分离可以提高程序的可读性,因为程序员现在必须到两个不同的地方去查找方法的主体)。
  1. 第二个原因涉及到语义。当方法体在一个类定义内部被声明时,C++语言编译器就可以(尽管不是必须的)直接将其作为内联方法进行扩展, 而无需建立函数调用。这样,内联方法比函数调用和方法体的结合形式执行起来更加快速。

类主题的变化

类主题的变化包括:
  • 接口
  • 属性概念
  • 向前定义
  • 内部类(嵌套类)
  • 类的数据字段(类属性)

消息、实例和初始化

这一节介绍消息传递的机制,然后将探讨对象的创建和初始化。

消息

消息:对象间相互请求或相互协作的途径。
消息的特点:
  • 对象接受多个消息,响应不同
  • 同一消息给多个对象,响应不同
  • 广播,可响应可不响应
notion image
语言的类型区别:
  • 静态语言类型:类型和变量联系在一起
    • 编译时作出内存分配决定。不必运行时刻重新分配。控制类型错误。
  • 动态语言类型:变量看作名称标识,类型和数值联系在一起。
在消息传递这方面,静态类型语言和动态类型语言之问存在显著的差异:
  • 一方面,静态类型语言在编译时使用接收器的类型来检查选择器,以理解它所接收的消息。
  • 另一方面,动态类型语言在编译时则没有办法去核实这一消息。因此在动态类型语言中,如果接收器不理解消息选择器,消息就可能产生运行时错误。而对于静态类型语言就从来不会发生这种运行时错误。
消息总是传递给接收器。然而,在大多数面向对象语言中,接收器并不出现在方法的参数列表中,而是隐藏于方法的定义之中。只有当必须从方法体内部去存取接收器的数值时,才会使用伪变量(pseudo-variable)。伪变量和通常的变量很相似,只是它不需要声明,也不能被更改(也许用伪常量这一术语更加合适,但是这一术语好像没有出现在任何语言的定义中),也就是Java中的this
显然,Python 语言并不是这样的,他需要显式声明self。

指针和内存分配

创建就是为一个新对象分配存储空间并且将这段空间与对象名称进行绑定。初始化不但包括为对象的数据区域设置初始值,这类似于对记录中数据字段进行的初始化,而且还包括建立操作对象所需的初始条件这个更一般的过程。
在大多数面向对象语言中,后者对于使用对象的容户的隐藏程度是封装的一个重要的方面,我们认为这是面向对象技术优于其他编程技术的一个主要方面。
对象数组的创建涉及两个层次的问题。一是数组自身的分配和创建,然后是数组所包含的对象的分配和创建。在C++语言中,这些特征是结合在一起的。数组由对象组成,而每个对象则使用缺省 (即无参数)构造函数来进行初始化;另一方面,在Java 中,表面上看来相似的语句却有着完全不同的效果。用来创建数组的 new操作符只能用来创建数组。数组包含的每个数值必须独立创建,典型的方法是通过循环来实现。
所有面向对象语言在它们的底层表示中都使用指针,但不是所有的语言都把这种指针暴露给程序员。Java的对象引用实际是存在于内部表示中的指针。这一点有三个原因:
  • 指针通常引用堆分配的(heap allocated)内存, 因此不符合传统的命令式语言中的与变量相关的通用规则。对于命令式语言,在一个过程中创建的变量值会随着过程的活动而存在,当从一个过程返回时,变量值也随之消失。另一方面, 对于堆分配的变量值,只要存在对它的引用,就会一直存在,因此,变量值的生存期一般长于创建该变量过程的生存期。
  • 通过堆进行分配的内存必须通过某种方式进行回收。
  • 对于某些语言(特别是C++ 语言),指针值和传统的变量值是有区别的。在C++语言中,对于以通常方式声明的变量,即所谓的自动(automatic)变量,其生存期总是绑定在创建该变量的函数上。当退出过程时,变量的内存就会被回收。赋值给指针(或者是引用,指针的另外一种形式)的数值没有绑定到过程入口。这样的变量与自动变量有很多重要的区别。
内存的回收有两种机制:
  • 手动回收
  • GC
内存的分配策略有三种:
  • 静态:在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间。
  • 堆式:在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。
    • 堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。
  • 栈式:可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。
    • 栈式存储分配按照先进后出的原则进行分配。
构造函数(constructor) 是用来初始化一个新创建对象的方法。把创建和初始化联系起来有很多优点。最重要的是,它确保对象在正确地初始化之前不会被使用。当创建和初始化分离时(当使用没有构造函数的编程语言时),程序员在创建新对象之后, 很容易忘记调用初始化过程,这样通常会导致不良后果。

元类

有一个特殊的类,一般称为Class,这就是类的类。在一种所有皆对象的世界观背景下,在类模型基础上还诞生出了一种拥有元类(metaclass)的新对象模型。即类本身也是一种其他类的对象。
目前,有三种不同观点的对象模型:
  • 基于类的对象模型(C,Java)
  • 基于原型的对象模型
  • 基于元类的对象模型

继承和替换

替换原则:如果类B是类A的子类,那么在任何情况下都可以用类B来替换类A,而外界毫无察觉。
子类型:指符合替换原则的子类关系。区别于一般的可能不符合替换原则的子类关系
子类有时为了避免继承父类的行为,需要对其进行改写。
  • 语法上:子类定义一个与父类有着相同名称且类型签名相同的方法。
  • 运行时:变量声明为一个类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。
改写与替换结合时,想要执行的一般都是子类的方法。
与类一样,接口可以继承于其他接口,甚至可以继承于多个父接口。虽然继承类和实现接口并不完全相同,但他们非常相似,因此使用继承这一术语来描述这两种行为。
抽象方法是介于类和接口之间的概念,定义方法但不实现;创建实例前,子类必须实现父类的抽象方法。

继承的形式

继承共有八种:
  • 特殊化(Specialization)继承
  • 规范化(Specification)继承
  • 构造(Construction)继承
  • 泛化继承
  • 扩展继承
  • 限制继承
  • 变体继承
  • 合并继承(多重继承)

特殊化继承

很多情况下,都是为了特殊化才使用继承。在这种形式下,新类是基类的一种特定类型,它能满足基类的所有规范。用这种方式创建的总是子类型,并明显符合可替换原则。
规范化继承一起,这两种方式构成了继承最理想的方式,也是一个好的设计所应追求的目标。

规范化继承

规范化继承用于保证派生类和基类具有某个共同的接口,即所有的派生类实现了具有相同方法界面的方法。在这种情况下,基类有时也被称为抽象规范类
基类中既有已实现的方法,也有只定义了方法接口、留待派生类去实现的方法。派生类只是实现了那些定义在基类却又没有实现的方法。
派生类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。 也就是说,基类定义了某些操作,但并没有去实现它。只有派生类才能实现这些操作。
在Java中,关键字abstract确保了必须要构建派生类。声明为abstract的类必须被派生类化,不可能用new运算符创建这种类的实例。除此之外,方法也能被声明为abstract,同样在创建实例之前,必须覆盖类中所有的抽象方法。

构造继承

一个类可以从其基类中继承几乎所有需要的功能,只是改变一些用作类接口的方法名,或是修改方法中的参数列表。即使新类和基类之间并不存在抽象概念上的相关性,这种实现也是可行的。
当继承的目的只是用于代码复用时,新创建的子类通常都不是子类型。这称为构造子类化。一般为了继承而继承,如利用一些工具类已有的方法。构造子类化经常违反替换原则(形成的子类并不是子类型)

泛化继承

派生类扩展基类的行为,形成一种更泛化的抽象。泛化子类化通常用于基于数据值的整体设计,其次才是基于行为的设计。

扩展继承

如果派生类只是往基类中添加新行为,并不修改从基类继承来的任何属性,即是扩展继承。(泛化子类化对基类已存在的功能进行修改或扩展,扩展子类化则是增加新功能)。由于基类的功能仍然可以使用,而且并没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的派生类还是派生类型。

限制继承

如果派生类的行为比基类的少或是更严格时,就是限制继承。常常出现于基类不应该、也不能被修改时。由于限制继承违反了可替换性原则,用它创建的派生类已不是派生类型,因此应该尽可能不用。

变体继承

两个或多个类需要实现类似的功能,但他们的抽象概念之间似乎并不存在层次关系
但是,通常使用的更好的方法是将两个类的公共代码提炼成一个抽象类,并且让这两个类都继承于这个抽象类。与泛化子类化一样,但基于已经存在的类创建新类时,就不能使用这种方法了。

合并继承

可以通过合并两个或者更多的抽象特性来形成新的抽象。一个类可以继承自多个基类的能力被称为多重继承

子类和子类型

对于静态类型的面向对象语言来说,存在着一个关于继承和替换这一面向对象核心思想的悖论。这一悖论来自于子类(sublass)和子类型(subtype)这一对概念。
如果一个类是通过继承创建的,那么就称这个类为子类。
如果说新类是已存在类的子类型,那么这个新类不仅要提供已存在类的所有操作,而且还要满足于这个已存在类相关的所有属性;子类型关系是通过行为这个术语描述的,与新类的定义或构造无关。
当继承的目的只是用于代码复用时,新创建的子类通常都不是子类型,这称为构造子类化

信息隐藏

使用可复用组件的程序员只需了解组件的性质和接口,而不必了解用于实现组件的技术的详细信息。这样可以滅少软件系统的相互关联。前面我们曾提到,这种相互关联的特性是导致传统软件变得复杂的一个主要原因。

静态行为和动态行为

编程语言中,术语静态总是用来表示在编译时绑定于对象并且不允许以后对其进行修改的属性或特征;术语动态用来表示直到运行时绑定于对象的属性或特征。
  • 变量的静态类是指用于声明变量的类。静态类在编译时就确定下来,并且再也不会改变。
  • 变量的动态类指与变量所表示的当前数值相关的类。动态类在程序的执行过程中,当对变量赋新值时可以改变。

静态类型化和动态类型化

编程语言中最显著的分歧来自于静态类型语言与动态类型语言之间的差异。动态类型语言与静态类型语言之问的差异在于变量或数值是否具备类型这种特性
  • 对于静态类型语言,类型在编译时绑定于变量。
  • 对于动态类型语言,类型决定于数值,而与变量无关。变量仅仅代表一个名称。在程序执行期间,不仅变量所代表的数值可以改变,而且变量所代表的类型也可以改变。

静态类和动态类

替换原则:声明为父类类型的变量可以用来保存子类类型的数值。
为了区别这两种类型,我们引入一对术语,静态类(static clase)和动态类(dynamnie class)。关于变量的静态类是指用于声明变量的类。静态类(就像名称所暗示的那样)在编译时就确定下来,并且再也不会政变。关于变量的动态类是指与变量所表示的当前数值相关的类。同样,如名称所暗示的那样,动态类在程序的执行过程中,当对变量赋新值时可以改变。例如:
静态类型与动态类型之间的最重要区别为:对于静态类型面向对象编程语言,在编译时消息传递表达式的合法性不是基于接收器的当前动态数值,而是基于接收器的静态类来决定的。
运行时类型决定:替换原则可以通过提升数值在继承层次上的位置来体现。有时则相反,还需要判断一种变量目前所包含的数值是否为类层次中的低层次类。
notion image
向下造型(反多态):做出数值是否属于指定类的决定之后,通常下一步就是将这一数值的类型由父类转换为子类。这一过程称为向下造型,或者反多态,因为这一操作所产生的效果恰好与多态赋值的效果相反。

静态方法绑定和动态方法绑定

对于几乎所有的面向对象编程语言来说,在响应消息时对哪个方法进行绑定是由接收器当前所包含的动态数值来决定的。
如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么我们就称这个变量是**多态(polymarphic)**的(对于Smalltalk、Java 和大多数其他面向对象语言来说,从这种意义上来讲,所有变量都是多态的)。而对于C++语言的声明为简单类型的变量,在这种意义上则不是多态的;而使用指针引用的对象数值是多态的。例如:
方法绑定:分为静态方法绑定和动态方法绑定。响应消息时对哪个方法进行绑定是由接收器当前所包含的动态数值来决定的。
多态变量:如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么就称这个变量是多态的。

替换的本质

内存布局

内存分配方案有三种:
  • 最小静态空间分配:只分配基类所需的存储空间。
    • 代价是带来切割。把子类赋值给父类后,子类特有空问会被截掉(调用父类拷贝构造函数)。
  • 最大静态空间分配:无论基类还是派生类,都分配可用于所有合法的数值的最大的存储空间
  • 动态内存分配:堆栈中不保存对象值,只分配用于保存一个指针所需的存储空间。

赋值语义

  • 复制语义 (copy semantics):赋值会将操作符右侧的变量值复制给操作符左侧的变量。此后,这两个变量值是互相独立的,其中一个变量值的改变不会影响到另外一个变量值。复制语义有时用在C++ 语言中,有时则不是。
  • 指针语义(pointer semantics ):赋值会将操作符左侧变量的参考值改变成右侧变量的参考值(这种方法有时也称为指针赋值 (pointer assignent))。这样,两个变量不仅具有相同的数值,而且还指向存储数值的同一内存地址。一个变量值的改变会同时改变两个变量的数值,这可以通过不同的变量名称得以反映。Java、CLOS、Objeet Pascal 语言以及许多其他的面向对象语言都采用指针语义。

复制

当对指向其他对象的变量值进行复制时,有两种可能的方案。一种是与原来变量共享实例变量的浅复制(shallow copy),即原有变量和复制产生的变量引用相同的变量值;另一种方案就是深复制(deep copy),这种方式将建立实例变量的新的副本。
notion image
  • 浅复制
  • 深复制:C++通过拷贝构造函数实现,Java通过改写clone方法。

相同

多重继承

多重继承:一个对象可以有两个或更多不同的父类,并可以继承每个父类的数据和行为。

名称歧义

父类有重名方法:
  1. 使用全限定名(复杂名称)
  1. 对继承方法重定义或重命名,包裹父类方法,但违反了替换原则
  1. 引入辅助类,使用不同方法重新定义同名操作,保留同名操作
  1. 使用内部类存储剩余的父类,然后封装方法,仍然违反替换原则
父类有重名值:
对于C++如果父类中值为virtual,则子类对象维护同一个重名值,否则分别维护。

接口多继承

Java和C#支持接口的多重继承,接口不会提供代码,不会因为重名引起冲突。

多态及软件复用

多态有四种形式:
  • 重载(专用多态):类型签名区分
  • 改写(包含多态):层次关系中,相同类型签名
  • 多态变量(赋值多态):声明与包含不同
  • 范型(模板):创建通用工具
最常用的软件复用机制:继承和组合。
组合和继承的比较:
  • 组合是较为简单的一种技术。优点是在特定的数据结构中需要执行哪些操作。无需考虑列表类所定义的所有操作。
  • 继承无法知道一个方法是否可以合法地应用于集合。
  • 使用继承构建数据抽象的代码的简洁性是继承的一个优点
  • 继承无法防止用户使用父类的方法来操纵新的数据结构

重载

重载是在编译时执行的,而改写是在运行时选择的。重载是多态的一种很强大的形式。非面向对象语言也支持。

类型签名

函数类型签名是关于函数参数类型、参数顺序和返回值类型的描述。类型签名通常不包括接收器类型——因此,父类中方法的类型签名可以与子类中方法的类型签名相同。

范畴

范畴定义了能够使名称有效使用的一段程序,或者能够使名称有效使用的方式。(局部变量/public成员)。通过继承创建的新类将同时创建新的名称范畴,该范畴是对父类的名称范畴的扩展
对于一个程序代码中的任何位置,都存在着多个活动的范畴。(类成员方法同时具有类范畴和本地范畴)。

两种重载

通过类型签名和范畴可以对重载进行两种分类:
  • 基于具有不同范畴的方法:同一个名称在不引起歧义和精度损失的情况下出现多个不同范畴
  • 基于具有不同类型签名的方法:多个过程共享同一个名称,通过函数签名对他们进行区分在编译时基于参数值的静态类型完成解析

强制与转换

强制、转换和造型:
  • 强制是一种隐式的类型转换,它发生在无需显式引用的程序中:
    • 转换表示程序员所进行的显式类型转换。在许多语言里这种转换操作称为“造型”:
      • 造型和转换既可以实现基本含义的改变;也可以实现类型的转换,而保持含义不变(子类指针转换为父类指针) 。
      造型也就是有两种:
      • 上溯造型:Parent a = new Child()
      • 下溯造型:Child b = (Child) new Parent()

      重定义

      子类定义了一个与父类具有相同名称但类型签名不同的方法
      • Java采用融合模型
      • C++采用分级模型

      改写

      子类的方法具有与父类的方法相同的名称和类型签名,可看成重载的特殊情况。

      标识改写

      各种语言在如何通过代码实现标识改写这方面存在着差异。
      • 某些语言,例如,Smalltalk、 Java 和 Objective-C 等完全不需要标识。在这些语言中,仅通过父类和子类方法类型签名的相似性来指示改写的存在。
      • 对于其他的语言, 例如C++ 语言,则必须对父类进行标识, 来预示着有可能发生改写(尽管这种标识不能保证一定会发生改写)。
      • 还有一些语言,例如 Object Pascal 语言,标识必须置于子类。在Delphi Pascal 语言和C#语言中,父类和子类都需要用关键字进行标识。

      代替与改进

      存在两种不同的关于改写的解释方式:
      • 代替(美国语义):在程序执行时,实现代替(replacenent)的方法完全覆盖父类的方法。即当操作于类实例时,父类的代码完全不会执行。
      • 改进(斯堪的纳维亚语义):实现改进(refinement)的方法将继承自父类的方法的执行作为其行为的一部分。这样,父类的行为得以保留且扩充。

      延迟方法

      如果方法在父类中定义,但并没有对其进行实现,那么我们称这个方法为延迟(delerred) 方法。延迟方法有时也称为抽象(abstract)方法,并且,在C++语言中通常称之为纯虛方法
      只有当编译器可以确认与给定消息选择器相匹配的响应⽅法时,才允许程序员发送消息给这个对象。

      改写与遮蔽

      遮蔽是在编译时基于静态类型解析的,并且不需要运行时机制。 C++需要对改写显式声明,如果不使用关键字,将产生遮蔽。

      多态变量

      多态变量是指可以引用多种对象类型的变量,这种变量在程序执行过程可以包含不同类型的数值。
      • 对于动态类型语言,所有的变量都可能是多态的。
      • 对于静态类型语言,多态变量则是替换原则的具体表现。
      多态变量有四种形式:
      • 简单变量
      • 接收器变量:多态变量最常用的场合,用来表示正在执行的方法内部的接收器 (this, self)
      • 反多态(向下造型):处理多态变量的过程,判断多态变量能否赋值给子类变量。取消多态赋值的过程,也称为反多态。
      • 纯多态(多态方法):支持可变参数的函数。
        • notion image
       

      范型

      通过类型的使用提供了一种将类或者函数参数化的方法。
      将名称定义为类型参数,在将来的某一时刻,会通过具体的类型来匹配这一类型参数,这样就形成了类的完整声明
      主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
      Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。

      框架

      对于一类相似问题的骨架解决方案。通过类的集合形成,类之间紧密结合,共同实现对问题的可复用解决方案。

      继承的两种方式

      • 代码复用:对于问题的现存解决方案。
      • 概念复用:对于特定应用的解决方案。
      继承允许进行高级别算法细节的封装,基类不需要改变,由特化子类满足不同的需求。

      倒置库

      • 传统的应用程序:应用程序特定的代码定义了程序执行的总体流程。
      • 框架:控制流是由框架決定的,并且随应用程序的不同而不同;框架占主导地位,而应用程序特定的代码处于次要位置。
      Loading...