C++必知必会(二) 多态

多态(Polymorphism)在一些编程教程中被弄得很神秘,而在另外一些教程中则被忽略,其实它不过是C++语言所支持的一个简单而有用的概念。按照C++标准所言,"多态类型(Polymorphic type)”就是带有虚函数的类类型。从设计的角度来看,"多态对象(Polymorphic object)"就是一个具有不止一种类型的对象,而"多态基类(Polymorphic base class)"则是一个为满足多态对象的使用需求而设计的基类。

让我们来看一个金融期权的类型AmOption,如下面的代码所示:

class Deal
{
};
class Priceable
{
};
class Option: public Deal, public Priceable
{
};
class AmOption: public Option
{
};
class EurOption: public Option
{
};

AmOption对象同时具有4个类型:AmOption, Option, Deal以及Priceable。由于一个类型是一组操作,因此,AmOption对象可以通过其4个接口中的任何一个进行操纵。这意味着从一个AmOption对象可以被针对Deal, Priceable, Option接口编写的代码所操纵,从而允许AmOption的实现利用或复用所有那些代码。对于AmOption这样的多态类型,从基类继承的最重要的东西就是它们的接口,而不是它们的实现。事实上,一个基类仅仅由接口组成不但常见,而且通常正是我们所希望的。

当然,这里有一个需要注意的地方。如果让这种优势能够发挥出来,一个良好设计的多态类对于它的每个基类而言必须是可替换的。换句话说,如果针对Option接口编写的通用代码接受的是一个AmOption对象,那么该对象的行为最好就像一个Option对象!

这并不是说AmOption对象应该和Option对象的行为完全一致(首先可能是因为Option基类的许多操作是不带任何实现的纯虚函数)。实际上,将一个多态基类(如Option)想象成一份契约更好理解一些。这个基类对其接口的用户做了某些承诺,这些承诺包括郑重的语法承诺,即特定的成员函数可以通过一些特定类型的实参进行调用,以及不太容易验证的语义上的承诺,即当一个特定的成员函数被调用时将会发生什么实际情况。像AmOption和EurOption这样的具体派生类被称为"转包类",它们实现Option与其客户签订的契约。

举个例子,如果Option具有一个纯虚成员函数price,其作用是给出Option的当前值,那么AmOption和EurOption都必须实现这个函数。我们显然不会为这两种类型的Option实现完全一致的行为,但它们都应该计算并返回一个价格(price),而不应该去拨打一个电话或打印一个文件。

另一方面,如果我要去访问同一个对象的两种不同接口的price函数,那么我应该得到相同的结果。就本质而言,每一个调用都应该绑定到同一个函数:

AmOption *d = new AmOption;
Option *b = d;
d->price();// 如果这一个调用的是AmOption::price()

b->price();// 那么这一个也应该如此

这是有意义的。假如我问你“那个美国期权的当前值是什么?”,我期望得到以下简短提问方式相同的答案:“那个期权的当前值是什么?”

当然,同样的推理也适用于对象的非虚函数:

b->update(); //如果这一个调用的是Option::update

d->update(); //那么这一个也是如此!

正是基类提供的契约允许针对基类接口编写的“多态”代码对特定的期权起作用,同时有助于对派生类的存在保持“健康的不知情”。换句话说,多态代码可能正在操纵AmOption和EurOption对象,但除非特别关心它们到底是什么对象,否则均被视作Option对象。各种各样“具体的”Option类型可以被添加或删除而不会影响到只关心基类Option的通用代码。比如说,如果在某一个地方出现一个AsianOption对象,那么只知道Option的多态代码也能够操作它。

出于同样的原因,像AmOption和EurOption这样具体的期权类型只需要知道基类就可以了(它们实现了基类的契约),改变通用代码对它们毫无影响。原则上,基类可以不知道除自身以外的任何事物。从实践的角度看,对其接口的设计要考虑预期用户的需求,并且应该以这样的方式进行设计:派生类可以很容易地推知并实现其契约。然而,基类应该对其派生类的具体细节全然不知,因为知道这些会不可避免地致使在类层次结构上添加或删除派生类变得困难。

时间: 2023-01-01

C++必知必会(二) 多态的相关文章

Visual Studio 使用及调试必知必会

原文:Visual Studio 使用及调试必知必会   一:C# CODING 技巧 1:TODO 然后 CTRL + W + T,打开任务列表,选中 Comments,就会显示所有待做的任务 2:打开所在的文件夹 右键单击任何一个文件选项卡, 选择"打开所在的文件夹",或在 Solution Explorer 的文件上面点右键: 3:比对同一个文件 鼠标向下拖动红框内的图标. 4:按意愿编程 我把它定义为:按意愿编程,即,在写代码过程中,如果觉得需要使用到一个新类,可以先不用创建这

Android必知必会-使用Intent打开第三方应用及验证可用性

本文讲的是Android必知必会-使用Intent打开第三方应用及验证可用性,一个普通的应用默认会有一个入口 Activity,它在 AndroidManifest.xml 中一般这样写: <application>      <activity android:name=".MainActivity" >          <intent-filter>              <action android:name="andr

Python 程序员必知必会的开发者工具

Python已经演化出了一个广泛的生态系统,该生态系统能够让Python程序员的生活变得更加简单,减少他们重复造轮的工作.同样的理念也适用于工具开发者的工作,即便他们开发出的工具并没有出现在最终的程序中.本文将介绍Python程序员必知必会的开发者工具. 对于开发者来说,最实用的帮助莫过于帮助他们编写代码文档了.pydoc模块可以根据源代码中的docstrings为任何可导入模块生成格式良好的文档.Python包含了两个测试框架来自动测试代码以及验证代码的正确性:1)doctest模块,该模块可

《Oracle PL/SQL必知必会》——第2章 初识Oracle和PL/SQL 2.1 什么是Oracle

第2章 初识Oracle和PL/SQL Oracle PL/SQL必知必会 在本章中,你将认识Oracle和PL/SQL是什么,以及你可以使用什么工具来操作它们. 2.1 什么是Oracle 在前一章中,你学习了数据库和SQL.如所解释的那样,做所有工作(存储.检索.管理和操作数据)的实际上是数据库软件(DBMS或数据库管理系统[Database Management System]).Oracle DBMS(或者简称为Oracle)就是一个DBMS:也就是说,它是数据库软件. Oracle已经

《Oracle PL/SQL必知必会》导读

前言 Oracle PL/SQL必知必会 Oracle Database(或Oracle RDBMS)是如此流行并且获得了广泛的认可,以至于大多数用户将其简称为"Oracle"(忽略了Oracle公司制作其他软件甚至硬件的事实).Oracle Database(我将像大多数人那样将其简称为"Oracle",以使事情变得简单)从20世纪70年代起就出现了,成为最早的数据库管理系统之一.Oracle是世界上最常用的数据库管理系统(Database Management

【web必知必会】——图解HTTP(上)

原文:[web必知必会]--图解HTTP(上) 本篇总结关于http的相关知识,主要内容参考如下导图: 主要讲解的内容有: 1 URL与URI的区别. 2 请求报文与相应报文的内容. 3 GET与POST的区别. 4 http的cookie.持久化.管道化.多部分对象集合.范围请求等 后续会更新http其他的相关知识. 关键词概念 平时会经常接触到URL,他就是我们访问web的一个字符串地址,那么URI是什么呢?他们是什么关系呢? 先看看官方的解释: URL:uniform resource l

MySQL必知必会

本文链接 http://alex-my.xyz/books/database/MySQL必知必会 http://blog.csdn.net/alex_my/article/details/72357498 1 基础知识 1 主键 唯一标识表中每行的这个列(这组列)称为主键. 应该总是定义主键,虽然并不总是需要主键. 任意两行都不具有相同的主键值. 每一行都必须具有一个主键值,不可为NULL. 2 常用命令 SHOW DATABASES; SHOW TABLES; SHOW COLUMNS FRO

【web必知必会】—— 图解HTTP(下)

原文:[web必知必会]-- 图解HTTP(下) 上一篇<图解HTTP 上>总结了HTTP的报文格式,发送方式,以及HTTP的一些使用. 本文再总结以下内容: 1 http状态码 2 http报文首部中的各字段 3 http中的身份验证 通过上篇粗略的描述,大体了解了http首部的概念. 其实请求报文与响应报文长得差不多,区别就在于请求报文与响应报文有一个各自的报文首部,和一个请求行和状态行. 可以看到,差别就在于 请求行中指定的是HTTP版本和请求的方式(GET\POST等). 状态行中指定

JavaScript必知必会(九)function 说起 闭包问题_javascript技巧

function 函数格式 function getPrototyNames(o,/*optional*/ a) { a = a || []; for(var p in o) { a.push(p); } return a; } caller func.caller 返回函数调用者 function callfunc() { if(callfunc.caller) { alert(callfunc.caller.toString()); }else { alert("没有函数调用");

JavaScript必知必会(十) call apply bind的用法说明_javascript技巧

call 每个func 都会继承call apply等方法. function print(mesage) { console.log(mesage); return mesage; } print.call(this, "cnblogs");//cnblogs call(thisAgr,agr1,agr2...) ,call方法第一个传递一个context上下文.后面是参数的个数. apply apply(thisAgr,[agr1,agr2]),apply方法和call的用法一样,