第三课
面向对象的程序设计方法
? 1.面向对象程序设计方法的起源
? 2.面向对象程序设计方法的基本概念
? 3,应用框架
? 4.面向对象程序设计方法的基本原则
? 5,设计模式
本章主要研究内容
面向对象程序设计方法的起源
? ( 1)面向对象的程序设计( Objiect Oriented
Programming,OOP) 方法起源于信息隐藏和抽象数
据类型概念,其研究开始于 20世纪 70年代,到 80年
代开始进入使用(以 C++ 的使用为标志)。
? ( 2) OOP的基本思想是将要构造的软件表示为对象
集,每个对象是将一组数据和使用它的一组基本操
作或过程封装在一起而形成的实体,对象与对象之
间依靠消息的传递实现联系。
? ( 3)在 OOP中较好地体现了人类的两种思维方式:
从一般到特殊的演绎推理和从特殊到一般的归纳方
法。
面向对象程序设计方法的基本概念--对象和类
? ( 1)对象( Objiect) 是将一组数据和作用该组数
据的一组操作或过程封装而形成的实体。是 OOP中的
最基本单元。
? ( 2) 对象由对象名、状态、方法(操作)组成。其
中状态是指对象存储的数据结构的值的集合,状态
随对象的运行(即操作)而变化。
? ( 3)对象具有封装性,从外面只能看见其外部特性
(及具备的处理能,由操作实现),而处理能力是
如何实现的及对象的内部状态对外都是不可见的。
面向对象程序设计方法的基本概念--对象和类
? ( 4)对象的功能是比较基本的,对象要完成复杂的
功能,需要与其他对象协同工作,即一个对象可能
要引用另一个对象。对象之间的相互作用只能通过
消息的转递来实现。
? ( 5) 一个对象收到来自其它对象的消息后,就可以
激活(运行)对象中的某个操作,改变其内部状态,
必要时以消息回传的方式将运行的结果通知引用该
对象的对象。
? ( 6)在一个软件系统中,对象的个数及种类是很多
的,通常,把具有相同内部存储结构和相同的一组
操作的对象看作是同一, 类,,而将属于某一类的
对象称之为该类的实例。
面向对象程序设计方法的基本概念--对象和类
? ( 1)例如:狗是具有某些特性的动物的总称,,小
黄是一条狗,,则可以认为, 狗, 是一个类,而小
黄是一个狗的实例(对象)
? ( 2)教学楼是具有某种特定属性的建筑的总称,是
一个, 类,,则西 12教学楼则是一个具体的对象。
? ( 3)从方法学上看,类是一个抽象的概念,因为它
抽象了一类对象的数据及方法特征,从工程实现的
角度考虑,类是一种共享机制,属于该类的对象都
可以共享类中定义的数据及操作。
面向对象程序设计方法的基本概念--继承性
? 从对象到类是一个抽象的过程,类与类之间也可以
有许多共性,也即可以在抽象的基础上进一步抽象。
? ( 1)例 1:如, 小黄-》狗-》动物,,狗比小黄
抽象,动物比狗抽象。( 2)例 2:数的分类
类 A( 数)
类 D( 复数)
类 C( 双精度数)
类 B( 浮点数) 类 E( 整数)
面向对象程序设计方法的基本概念--继承性
? 一般地,上一层的类成为下一层类的超类
( superclass,或基类 baseclass),下一层的类成为
子类( subclass)。
? 上图中,C是 B的子类,也是 A的子类,B,D,E均是 A
的子类,A是超类。
? 子类自动继承超类(父类)的性质,如 B,D,E自动
继承 A的性质。继承性具有传递性,如 B继承 A的性质,
C继承 B的性质,则 C继承 A的性质。
? 总之,一个类除了具有该类自有的各种性质外,还
可以继承超类的所有性质。
面向对象程序设计方法的基本概念--继承性
? 继承性的主要作用是提供了共享机制,可以
缩短代码的长度,减轻编程人员的负担,节
省存储空间,提高程序的可阅读性及可靠性。
面向对象程序设计方法的基本概念--实例(字
符集合,用 C++表示 )
? #include <IOSTREAM.H>
? #include <STDIO.H>
? #define MAX_SET_SIZE 256
? class CCharSet
? {
? public:
? void Clear();//字符集的初始化
? void WriteSet();//显示字符集的内容
? void InsertMember(char c); //往字符集插入一个字符
? void DeleteMember(char c); //从字符集中删除一个字符
? bool CharInSet(char c); //判断一个字符是否是字符集的成员
? private:
? char m_Set[MAX_SET_SIZE];
? int m_nNum;
? };
面向对象程序设计方法的基本概念--实例
? int main(int argc,char* argv[])
? {
? CCharSet set;
? printf("Testing general set object:\n");
? set.Clear();
? set.InsertMember('A');
? set.InsertMember('h');
? set.InsertMember('4');
? set.InsertMember('+');
? cout << "Set is" << endl;
? set.WriteSet();
? set.DeleteMember('A');
? set.DeleteMember('4');
? set.DeleteMember('Y');
? cout << "After deleting A,4,and Y,set is" << endl;
? set.WriteSet();
? cout << "Is char '4' in set? " << (set.CharInSet('4')? "true", "false") << endl;
? cout << "Is char 'h' in set? " << (set.CharInSet('h')? "true", "false") << endl;
? return 0;
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::Clear()
? {
? m_nNum = 0;
? }
? void CCharSet::WriteSet()
? {
? cout << "[";
? for(int i=0; i<m_nNum; i++)
? {
? printf(" %c ",m_Set[i]);
? }
? cout << "]" << endl;
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::InsertMember(char c)
? {
? if (m_nNum == MAX_SET_SIZE)
? {
? //字符存储数量已经达到上限
? return;
? }
? if (!CharInSet(c))
? {
? m_Set[m_nNum] = c;
? m_nNum ++;
? }
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::DeleteMember(char c)
? {
? for(int i=0; i<m_nNum; i++)
? {
? if (m_Set[i] == c)
? {
? m_nNum --;
? for(int j=i; j<m_nNum; j++)
? {
? m_Set[j] = m_Set[j + 1];
? }
? break;
? }
? }
? }
面向对象程序设计方法的基本概念--实例
? bool CCharSet::CharInSet(char c)
? {
? for(int i=0; i<m_nNum; i++)
? {
? if (m_Set[i] == c)
? {
? return true;
? }
? }
? return false;
? }
面向对象程序设计方法的基本概念--实例
? 运行结果:
面向对象程序设计方法的基本概念--实例
? 试验 2:
? 1.用面向对象思想描述下列组合电路;
? 2.对给出的输入,计算输出。要求编程实现
( C++ 语言),打印运行结果。
? 作完后发到我的信箱
+
+
X
A
B
C
D
G
E
F
面向对象程序设计方法的基本概念--实例
输入 输出
A B C D E F G
0 0 0 0
0 0 0 1
0 0 1 0
0 0 1 1
0 1 0 0
0 1 0 1
0 1 1 0
0 1 1 1
1 0 0 0
1 0 0 1
1 0 1 0
1 0 1 1
1 1 0 0
1 1 0 1
1 1 1 0
1 1 1 1
面向对象程序设计方法--应用框架定义
? 应用框架( Application FrameWork) 是在特定应用领域中,
程序的共同结构,应用框架就是整个或部分挼件系统的可
重用行设计,表现为一组抽象组件及组件实例间交互的方
法。或者:框架是被应用开发者定制的应用骨架。
? 框架是组件技术、软件体系结构研究和应用软件开发三者
发展结合的产物。一个框架是一个可复用的设计组件,其
规定了应用的体系结构,规定了整个设计、协作组件之间
的依赖关系、责任分配和控制流程(利用抽象类与其实例
之间的协作实现),为组件复用提供了上下文关系。
? 框架以组件库的方式出现,但不同于组件库,组件库是框
架的一个重要部分,但框架更重要的是还提供了框架内对
象的交互模式和控制流模式 。
面向对象程序设计方法--应用框架中的重要概念
? ( 1)抽象:在同领域的软件程序中,将存在于相
关类中的共性提取出来,形成更为抽象的类,再
建立类与类之间的交付模式和控制流模式,得到
应用框架。
? ( 2)派生:以框架中抽象类为基础,添加特殊功
能,成为具体类,再创建对象。
? 例:矩形抽象图的形成
抽象
图
面向对象程序设计方法--应用框架的基本组成
? 应用框架的基本组成:
? ( 1)类库:含有抽象类、具体类、函数以及对象
等。抽象类有成员函数,函数内有指令,但主要
函数体内的指令缺省,预留给应用程序补充(重
载)。
? ( 2)抽象类之间的关系及控制流程(正向调用和
反向调用)
面向对象程序设计方法--应用框架与类库的联系
? 应用框架是在类库基础上的发展,已经包含了类
库的基本功能,但应用框架编程与调用类库编程
的方式有很大的不同。
面向对象程序设计方法--应用框架与类库的联
系
应用框架 类库
作用:供应用程序派生出具体类,
派生式时修正类, 必须将虚函数具
体化 ( 给出函数体 ) 才能创建对象 。
作用:供程序员用其中的类来产生对
象, 应用程序不能修改类库
应用框架中的类的成员函数与应用
程序中的函数之间可以双向调用
应用程序的函数可以调用类库中的函
数, 但类库中的函数不能调用应用程
序中的函数 ( 晚辈调用前辈, 及正向
调用, 不能倒过来 )
含有类间的关系 ( 预设了对象间的
互助合作关系 )
类是独立的, 没有预设对象间的通信
方式, 只能由用户 ( 应用程序 ) 确定
对象中常定义有预设行为, 应用程
序可以继承, 修改预设行为
类库中对象的行为是固定的, 无法修
正 。
面向对象程序设计方法--应用框架
应用框架的主要代表有 Smalltalk-80语言中
的 MVC框架,Macintosh推出的 MacApp框
架,Visual C++中的 MFC框架。
MFC类库介绍
MFC是什么?
微软基础类库( MFC,Microsoft
Foundation Class) 是微软为 Windows程序
员提供的一个面向对象的 Windows编程接
口 API,大部分以 C++形式封装。
MFC库中的类代表了窗口、对话框、设
备上下文、通用 GDI对象,例如画刷和笔、
控件以及其它标准 Windows项。这些类对
于它们封装的 Windows中的结构提供了一
个习惯的 C++成员函数接口。
为什么使用 MFC?
? 首先,MFC提供了一个标准化的结构,这样开发人员不
必从头设计创建和管理一个标准 Windows应用程序所需
的程序,而是, 站在巨人肩膀上,,从一个比较高的起
点编程,故节省了大量的时间;
? 其次,它提供了大量的代码,指导用户编程时实现某些
技术和功能。 MFC库充分利用了 Microsoft开发人员多年
开发 Windows程序的经验,并可以将这些经验融入到你
自己开发的应用程序中去。
? 对用户来说,用 MFC开发的最终应用程序具有标准的、
熟悉的 Windows界面,这样的应用程序易学易用;
? 另外,新的应用程序还能立即支持所有标准 Windows特
性,而且是用普通的、明确定义的形式。
MFC的特性
? 封装
? 继承
? 虚函数与多态
封装
? 对 Win32应用程序编程接口的封装
用一个 C++ Object来包装一个 Windows Object。
? 对应用程序概念的封装
MFC把许多基本处理封装起来,替程序员完成这些工作
MFC提出了以文档 -视图为中心的编程模式,MFC类库封
装了对它的支持。
? 对 COM/OLE特性的封装
MFC的 OLE类封装了 OLE API大量的复杂工作,这些类
提供了实现 OLE的更高级接口。尤其是 Interface接口
? 对 ODBC功能的封装
以少量的能提供与 ODBC之间更高级接口的 C++类,封
装了 ODBC API的大量的复杂的工作,提供了一种数据
库编程模式。
继承
? MFC抽象出众多类的共同特性,设计出一些基类作为实现
其他类的基础。
? 最重要的基类是 CObject和 CCmdTarget:
CObject是 MFC的根类,实现了动态类信息、动态创建、
对象序列化、对程序调试的支持等重要的特性
CCmdTarget通过封装一些属性和方法,提供了消息处理
的架构。 MFC中任何消息处理类都从 CCmdTarget派生。
? 针对每种不同的对象,MFC都设计了一组类对这些对象进
行封装,每一组类都有一个基类,从基类派生出众多更
具体的类。这些对象包括以下种类:窗口对象,基类是
CWnd; 应用程序对象,基类是 CWinThread; 文档对象,
基类是 CDocument,等等。
? 程序员可以结合自己的实际,从适当的 MFC类中派生出自
己的类,实现特定的功能,达到自己的编程目的。
虚函数与多态
? MFC以, C++”为基础,支持虚函数和多态
? 作为一个编程框架,MFC建立了消息映射机制,
以一种富有效率、便于使用的手段解决消息处
理函数的动态约束问题(多态性)
? 通过虚函数和消息映射,MFC类提供了丰富的
编程接口。程序员继承基类的同时,把自己实
现的虚拟函数和消息处理函数嵌入 MFC的编程
框架。 MFC编程框架将在适当的时候、适当的
地方来调用程序的代码。
MFC的构成
MFC可分为两个主要部分:
? ( 1)基础类(见下简图)
? ( 2)宏和全程函数。
MFC基础类
? 根类,CObject
? MFC应用程序框架类
? 窗口、对话和控件类
? 绘画和打印类
? 简单的数据类型类
? 数组、列表和映射类
? 文件和数据库类
? Internet和网络类
? OLE类
? 调试和异常类
MFC宏和全局函数
Microsoft基本宏和全程函数提供以下功能:
? 数据类型
? MFC类对象的强制类型转换
? 运行时刻对象类型服务
? 诊断服务
? 异常处理
? CString格式化及信息框显示
? 消息映射
? 应用消息和管理
? 对象连接和嵌入( OLE) 服务
? 标准命令和 Windows IDs
? 数据库
? ClassWizard向导
? 其他
基类,CObject类
? CObject 是大多数 MFC类的根类或基类。
? MFC从 CObject派生出许多类,具备其中的一个或者多个
特性。
? 程序员也可以从 CObject类派生出自己的类,利用
CObject类的特性。
? CObject类的特性
( 1)对运行时类信息的支持
提供 IsKindOf函数来实现这个功能
( 2)对动态创建的支持
窗口对象、视对象、文档对象
( 3)对序列化的支持
将对象内容存入一个文件或从一个文件中读取对象内容
MFC应用程序框架类
? 应用程序框架不仅提供了构建应用程序所需要
的类,还定义了程序的基本执行结构。所有的
应用程序都在这个基本结构基础上完成不同的
功能。
? MFC的应用程序结构体系,是以文档 -视结构
( Document/View) 为核心的编程模式。
? MFC除了定义程序执行结构之外,还定义了三
种基本的主窗口模型:单文档窗口,多文档窗
口和对话框作为主窗口。
MFC应用程序框架类
? MFC应用程序框架类用于一个应用程序的构造。它们对
大多数应用程序提供了通用功能。可以在框架中填写对
于应用特定的功能。典型地,可以从构造类派生新的类,
然后添加新成员和 /或覆盖已有的成员函数。
? AppWizard产生几种类型的应用,所有这些都以不同的方
式使用应用框架。 SDI( 单文档界面)和 MDI( 多文档界
面)应用充分利用了框架的一部分,即文档 /视结构。
? 文档 /视应用包含了一个或多个文档、视图和框架窗口集。
一个文档 -模板对象将类与每一个文档 /视图 /框架集联系
起来。
? 所有的 MFC应用都至少有两个对象:一个由 CWinApp派
生出的应用对象和一些派生自 CWnd的主窗口对象。
文件和数据库类
? 这些类允许将信息存储在一个数据库或一个磁盘文件中。包括:
? 文件 I/O类
对传统磁盘文件、内存文件,Ative流和 Windows套接字提供了一
个接口。所有从 CFile派生出来的类可以与 CArchive对象一起使用
来执行串行操作。
? DAO类
与其它应用框架一起工作,执行对 DAO( 数据访问对象)数据库
的访问。它使用的数据库引擎与 Microsoft VB和 Microsoft Access
相同。 DAO还能访问许多支持开放数据库链接( ODBC) 驱动程
序的数据库。
? ODBC类
和其它应用框架一起工作,执行对开放数据库链接( ODBC) 数
据库的访问。使用 ODBC数据库的程序至少有一个 CDatabase对象
和一个 CRecordset对象。
应用消息管理
? Windows消息的管理包括消息发送和消息处理
? 为了支持消息发送机制,MFC提供了三个函数:
SendMessage,PostMessage和
SendDlgItemMessage。
? MFC采用一种消息映射机制来决定如何处理特
定的消息。这种消息映射机制包括一组宏,用
于标识消息处理函数、映射类成员函数和对应
的消息等。
消息映射
? 消息映射就是让程序员指定要某个 MFC类(有
消息处理能力的类)处理某个消息。
? MFC处理三类消息:
Windows消息、控制通知消息、命令消息
? 使用 MSG结构来描述消息的 6个属性
? 应用程序通过窗口过程来传递和处理消息
? 应用程序通过消息循环来获得对消息的处理
? MFC 使用 ClassWizard帮助实现消息映射
应用框架的程序设计方法--多态性
? 追求软件的弹性(适用性)和可重用行是面向对象程序设
计方法的主要目的之一,多态性是实现程序适用性和可重用行
的关键技术。
多态性简单地说是为同一函数 (和运算符 )提供几个版本,
不同的对象收到相同的消息产生不同的动作,这种功能称为多
态性。
C++ 支持两种多态性:编译时的多态性通过函数 (和运算
符 )重载来实现,运行时的多态性通过虚函数来实现。
两个或两个以上的函数如果函数名相同,只要参数的个数
不同或参数的类型不同,编译系统便能够在函数调用时知道应
当实际调用哪个函数,这就叫做函数重载。
在派生一个类时不仅能添加新的数据成员和成员函数,还
可以重新定义基类的函数。
应用框架的程序设计方法--多态性
在教材的 P64举了一个将多态性用于自动售
货机及硬币接口的例子,请大家看看。
应用框架的程序设计方法--反向调用
传统意义上,应用程序可以调用库函数,
库函数不能反向调用应用程序中的函数,原
因是库函数开发在前,应用程序开发在后,
即只能晚辈调用前辈,也称正向通信。
反向调用是前辈(应用框架)调用晚辈
(用户应用程序),是应用框架的进行程序
设计的一个重要方法,也是与使用类库的重
要区别。
应用框架的程序设计方法--反向调用
反向调用的功能:
( 1)框架能事先定义许多预设函数;
( 2)程序员的主要工作设计函数,这些函数通过
反向调用,可以修改或取代框架中的同名函数
( 3)如果应用程序中的函数已经修改或取代了预
设函数,框架就优先调用程序中的函数,否则调
用预设函数。
应用框架的程序设计方法--反向调用
正
反
向
调
用
实
例
使用者
Windows
预设 f4()预设 f3()预设 f1()
f4()f2()f1()
事件 1 事件 3事件 2
消息 消息 (调用 )
消息 正向 消息
应用程序
应用框架
f2( )=0
消息
反向 消息
应用框架的程序设计方法--抽象类
抽象是指提取共同特征的过程,在软件工程中,通过
抽象后得到的具有相同属性的类就是抽象类。但在软件实
现时,定义抽象类为:有成员函数是不完整的类中。反之,
如果类中的所有成员函数都是完整的,则是具体类。
一般地,不完整是指一个函数的函数体缺省。
Class Person
{
public:
virtual void Display()=0;
}
Display()是一个纯粹虚拟函数,其函数内容为空,留待子类
补充。
在此基础上,C++有更为严格的定义:凡含有纯虚函数的类
就是抽象类,不含有纯虚函数的类就是具体类。
应用框架的程序设计方法--抽象类
Class Person
{
public:
virtual void Display()=0;
}
Class Employee:public Person
{……
public:
……
}
Employee是一个子类(派生类), 但他依旧没有将其函数
Display()内容补充完整,Employee也是一个抽象类。
应用框架的程序设计方法--抽象类
( 1) 纯虚函数的产生:将名称相同但内容不同的
函数提取出来,放进抽象类时,即为纯虚函数。
( 2)纯虚函数与虚拟函数的区别在于虚拟函数在抽
象类里面是由函数体的,而纯虚函数的函数体是
缺省的。在 C++语言里,用“= 0”表示纯虚函数,
如 virtual void Display()=0;
( 3)纯虚函数的作用一方面在于函数名共享,更重
要的是留给了编程人员很大的自主空间,编程人
员可以补充、完善本身的特殊性,得到合乎自己
要求的功能。
( 4)抽象类不能创建对象,只有具体类才能创建对
象。(为什么?)
应用框架的程序设计方法--抽象类
class Person
{
protected:
char name[20];
public:
void SetName(char *na) {strcpy(name,na);}
virtual void Display( )=0;
};
class Customer:public Person
{
public:
virtual void Display( )
{ cout<<“Customer:”<<name<<endl;}
};
抽象类
具体类
(继承)
应用框架的程序设计方法--抽象类
? 抽象类使用上的限制:抽象类由于自身含有不
完整的成员函数,不能创建对象,具体地说:
? ( 1)抽象类不能成为自变量的类型,如:
? Void disp( Person p) {p.SetName(“Tom”);}
? Void main()
? { Person x; disp(x);}
? 由于 Person为抽象类,无法创建对象 p,x;
应用框架的程序设计方法--抽象类
? ( 2)抽象类不能成为函数的类型,如:
? Person Sub(Person p)
? { return p;}
? Sub( )函数将返回对象 p,必须派生 Person对象
来接受返回值,由于 Person为抽象类,无法创
建对象,若采用具体类就可以:
? Customer Sub( Customer c)
? { return c;}
应用框架的程序设计方法--抽象类
? ( 3)抽象类不能用来转换对象的类型,如:
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? ((Person)cust).SetName(“Jack”);
? }
? 实际运行时,必须创建一个 Person类的临时对象,用于
存放 cust对象转换后的内容,但 Person不能派生出临时
对象。
应用框架的程序设计方法--抽象类
? 抽象类不能派生对象,但可以派生指针。
(比较好的用法)
? ( 1)使用抽象类指针指向子类的对象:
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? Person *p=&cust;
? p->Display( );
? }//输出结果为,Customer,Linda
“Linda”
指针 p 对象 cust
应用框架的程序设计方法--抽象类
? ( 2)使用抽象类指针作为函数的自变量,
? Void Set(Person *p ) {p->SetName(“Amy”);}
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? Set(&cust);
? cust.Display( );
? }//输出结果为,Customer,Amy
应用框架的程序设计方法--抽象类
? ( 3)函数可以返回抽象类的指针
? Person *sub( )
? {
? Customer *pc=new Customer( );
? pc->SetName(“Tom”); //调用 Person类中的 SetName( )函数
? return(Person*)pc;
? }
? void main( )
? {
? sub( )->Display( );//调用 Customer类中的 Dispaly( )函数
? delete sub( );/ /delete pc
? }//输出结果为,Customer,Tom
应用框架的程序设计方法--双向通信
? 传统的链接库与类库都只提供单(正)向通信(调用),
而应用框架则提供双向通信。
? ( 1)传统的链接库提供的正向通信
? void main( )
? {
? char place[ ]=“Beautiful Beijing”;
? int len=strlen(place);//strlen( )是库函数,直接调用
? int x=abs(-8); //abs( )是库函数,直接调用
? cout<<len<<“,”<<x<<endl;
? }
应用框架的程序设计方法--双向通信
? void main( )
? {
? VIP v(“Tom”,“321-7777”);
? v.Display( );
? }
? 输出结果,Customer,Tom
? TEL,321-
7777
? ( 2)应用框架提供的正向通信
? Class VIP:public Customer
? {
? char tel[15];
? public:
? VIP(char *na,char*t) {
? SetName(na);//调用抽象类 Person
? 类中的成员函数
? strcpy(tel,t);
? }
? virtual void Display( ) {
? Customer::Display( ); //调用
? Customer类中的成员函数
? cout<<“TEL:”<<tel<<endl;
? }
? }
应用框架的程序设计方法--双向通信
class Product {
protected:
int pno; //产品编号
Customer *pc;
public,
Product(int no) { pno=no;} //Product类的构造函数
void SoldTo(Customer &c) {pc=&c;}; //在产品与顾
客之间建立对应关系
void Inquire( )
{ Print( );
cout<<“sold to…”<<endl;
pc->Display( );//调用 Customer类的 Display( )
}
virtual void Print( )=0;//纯粹虚拟函数
}
( 3)应用框架提供的反向通信
应用框架的程序设计方法--双向通信
( 3)应用框架提供的反向通信
Product,
Person,
Customer
三个类
之间的
关系
VIP
Product
Customer
Person
TV
……….,……………… ……………
应用框架的程序设计方法--双向通信
( 3)应用框架提供的反向通信
class TV:public Product
{
double price;
public:
TV(int no,double pr):Product( no )
{ price=pr;}
virtual void Print( )// 重载 Print( )
{
cout<<“TV No:”<<pno<<endl;
cout<<“Price:”<<price<<endl;
}
};
Void main( )
{
TV t(1100,1800.5)
VIP vp(“Peter”,”666-8899”);
t.SoldTo(vp);
t.Inquire( );
};
运行结果,TV No:1100
Price:1800.5
Sold to…
Customer:Peter
TEL:666-8899
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
反向通信有两种形式:
( 1)框架中的类调用同体系中的子类
( 2)框架中的类调用不同体系中的子类--跨体
系反向调用
问题:反向调用是前辈调用晚辈,先辈(如父类)
设计在前,晚辈(派生类或子类)设计在后,如何
保证先辈要调用的函数再晚辈中就一定存在?
反向通信的实现机理:
双向调用都是依赖虚拟函数来完成的,如上例中:
TV的 Print( ),VIP的 Display( )。( 请参看 41页)
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
如果 Print( )不是虚拟函数而是一般函数,能不能实现反向调用?
Class Product{
Int pno;
Public:
Product(int no) {pno=no;}
Void Inquire( ){Print( );}
Void Print( ){ cout<<“This is Product::Print( )\n”;}
};
Class TV:public Product{
Double price;
Public:
TV(int no,double pr):Product(no)
{price=pr;}
Void Print( ){ cout<<“This is TV::Print( )\n”;}
};
Void main( )
{
TV t(1100,1800.5);
t.Inquire( );
}//运行结果是什么?
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
Print( )是不是一定必须是纯粹虚拟函数才可能实现反向调用?
Class Product{
Int pno;
Public:
Product(int no) {pno=no;}
Void Inquire( ){Print( );}
Virtual Void Print( ){ cout<<“This is Product::Print( )\n”;}
};
Class TV:public Product{
Double price;
Public:
TV(int no,double pr):Product(no)
{price=pr;}
Virtual Void Print( ){ cout<<“This is
TV::Print( )\n”;}
};
Void main( )
{
TV t(1100,1800.5);
t.Inquire( );
}//运行结果是什么?
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
Product中的 Inquire( )要调用 VIP::Display( ),
Product与 VIP不在同一个类系,即实现的是跨体系的反向
调用,Inquire( )与 Display( )之间只能通过指针调用,
不能经由函数的形式调用。
( 1) VIP必须是具体类才能派生对象
( 2) pc指针指向派生的对象
( 3)由 pc所指向的对象来调用 Display( )函数
( 4) Inquire() 经过 pc而调用 Display( )函数
完成上述过程有两个关键点:
( 1) Display( )必须是虚拟函数(为什么?)
( 2) pc指针只能是 Customer* 类型(为什么不能是 VIP*
型?)
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
例子:先在框架中增加一个公用函数
void DoInquire(Product *p){p->Inquire( );}
class Computer:public Product
{
char model[20];
public:
Computer(int no,char *ml):Product(no)
{ strcpy(model,ml); }
virtual void Print( ) { Show( );}
virtual void Show( )
{ cout<<“This is Computer::Show( )\n”;}
};
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
class Notebook:public Computer
{
public:
Notebook(int no,char *ml),Computer(no,ml)
{ }
virtual void Show( )
{ cout<<“This is Notebook::Show( )\n”;}
};
Void main( )
{
Customer cust; cust.SetName(“****”);
Computer com(1288,”PowerPC”);
Com.SoldTo(cust);
DoInquire(&com);
}
输出结果:
This is Computer::Show()
Soldto:…
Customer:****
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
类的结构图:
DoInquire()
函数
Notebook
类
Computer
类
Product
类
Customer
类
Person
类
……………………………………………………
…
框架中
程序中
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
反向调用过程,a,利用 Customer类派生出对象 com,
执行 DoInquire(& com) 使指针 p指向对象 com
指针 p
Customer类
Print( )
Show( )
………………………………………………………
…
Com对象
Inquire( )
b,执行 DoInquire( )中的 p->Inquire( ) 就等于执行了 Product
类中的 Inquire() 函数。 Inquire( ) 反向调用 Computer类中的
Print(),Print() 再调用同一个类中的 Show()
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
另一种情况,使用 Computer的子类 Notebook:
void main( )
{
Customer cust;
cust.SetName(“****”);
Notebook nb(1288,“486DX66”);
nb.SoldTo(cust);//不能将 cust换成 nb
DoInquire(&nb);
}//输出结果,This is Notebook::Show( )
sold to…
Customer:****
执行 P->Inquire( )相当于执行 nb.Inquire( ),而 nb是由 Notebook创建出来
的,故能反向调用 Notebook中的函数,考虑:若在 Notebook类中只定义了
Print( )函数,而没有定义 Show( ) 函数,如何反向调用?
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
另一种情况,使用 Computer的子类 Notebook:
void main( )
{
Customer cust;
cust.SetName(“****”);
Notebook nb(1288,“486DX66”);
nb.SoldTo(cust);//不能将 cust换成 nb
DoInquire(&nb);
}//输出结果,This is Notebook::Show( )
sold to…
Customer:****
执行 P->Inquire( )相当于执行 nb.Inquire( ),而 nb是由 Notebook创建出来
的,故能反向调用 Notebook中的函数,考虑:若在 Notebook类中只定义了
Print( )函数,而没有定义 Show( ) 函数,如何反向调用?按照先晚辈后
前辈的原则,此时只能调用父类 Computer中的 Show( ) 函数。
应用框架的程序设计方法--预设函数
( 1) 预设函数的定义
( 1) 反向调用是前辈调用晚辈,其技术核心是虚拟函数,即在
先辈(如应用框架)中事先定义了许多可能要用的函数或函数
名,这些函数是依惯例(或按常规、常识)事先定义的,也称
预设函数。
( 2) 惯例是指通常情况下处理问题的方法,如出租车的行驶路
线,一般政府机关办事的流程等。
( 3)惯例所对应的方法,一般都是经过实践检验的理想方法,
直接使用可使用户负担减轻,提高效率。
( 4)惯例只适合一般情况,若遇上特殊情况,应对惯例进行修
改,甚至取消。
应用框架的程序设计方法--预设函数
( 2) 预设函数的应用
在面向对象程序设计中,按照晚辈优先的原则,预设函数(通常存在应用框
架中)通常其备用函数的作用。当应用框架反向调用时,若发现用户的应用
程序中没有定义某函数,才逐步退回到上一级类中去找,如刚介绍过的
Computer:,Show( )。 再如 Display( )函数:
Person
类
……………………………………………………
Virtual Display()
{… };
Customer
类
Virtual Display()=0;
VIP
类
Virtual Display()
{… }; 框架
应用程序
应用框架的程序设计方法--预设函数
( 2) 预设函数的应用
例 1:
Class VIP:public Customer
{
char telphone[15];
Public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}//没有定义 Display( ),只能采用备用函数,
}; //Customer::Display( )
Void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果,Customer:Mr.White
应用框架的程序设计方法--预设函数
例 1:另一种形式
Class VIP:public Customer
{
char telphone[15];
Public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}
virtual void Display( ){ cout<<“TEL:”<<telphone<<endl;} //优先调用
};
void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果,TEL:888-2222
应用框架的程序设计方法--预设函数
在有些场合,晚辈类定义的函数并不一定要完全取代预设函数,也可以只是修正,如:
Class VIP:public Customer
{
char telphone[15];
public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}
virtual void Display( ){ Customer::Display( );
cout<<“TEL:”<<telphone<<endl;}
}; //不是覆盖预设函数,而是修正
Void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果:
Customer:Mr.White
TEL:888-2222
构造函数与反向调用
? 框架中的函数可以反向调用应用程序中的函数,但构
造函数不能反响调用。原因在于当执行父类的构造函
数时,由于子类部分尚未完成,有可能出现无法调用
子类函数的问题,例如:
? class Product {
? protected:
? int pno;
? public:
? Product(int no)//构造函数
? { pno=no;
? Print( );
? }
? virtual void Print( )=0;//纯虚函数
? };
构造函数与反向调用
? class TV:public Product
? {
? char model[20];
? public:
? TV(int no,char *m),Product(no)
? { strcpy(model,m);}
? virtual void Print( )
? {cout<<pno<<“,”<<model<<endl;}
? };
? void main( )
? {
? TV tv(100,“KS1100”);
? )
构造函数与反向调用
? 上例是 Product类的构造函数反向调用,是一种直接的
行为,间接反向也不行,如:
? class Product
? {
? Product( )//构造函数
? { ……
? func( );//过渡函数,用于反向调用
? }
? void func( )
? {……
? Print( );
? }
? virtual void Print( )=0;//纯虚函数
? };//思考题:构造函数的跨体系反向调用行吗?请举例
说明
构造函数与反向调用
? 利用反向调用可以实现对框架里中类的成员变量赋值,如对
Product类中的 pno赋值:
? class Product
? {
? protected:
? int pno;//只能是 protected 或 public型,不能是 private型
? public:
? virtual void Paint( )=0;
? };
? class TV:public Product
? {
? public:
? TV(int no) { pno=no;}
? Virtual void Paint( ) { cout<<pno<<endl;}
? };
? Void main( )
? {
? TV tv(1100); Tv.Print( );//不灵活(弹性不好),要改 pno的值,就必须改程序
? } //输出为,1100
构造函数与反向调用
? 另一种方法,pno可以是 private型:
? class Product
? {
? protected:
? int pno;
? public:
? Product(int no) {pno=no;}
? virtual void Paint( )=0;
? };
? class TV:public Product
? {
? public:
? TV(int no),Product(no) { }
? Virtual void Paint( ) { cout<<pno<<endl;}
? };
? Void main( )
? {
? TV tv(1100); Tv.Print( );//不灵活(弹性不好),要改 pno的值,就必须改程序
? } //输出为,1100
构造函数与反向调用
? 上述两种方法是一般意义的赋值方法,有两个明
显的缺点,
? ( 1)赋值是通过构造函数实现的,但构造函数是
不能重复定义的,即不能重载,应用程序无法经
过重载的方法修改构造函数。
? ( 2)程序的弹性不好,要修改初值必须要改程序
构造函数与反向调用
? 通过 InitPno( )和 SetPno()实现了
? 让子类 TV弹性地给 pno赋值,但
没有实现对构造函数 Product( )的
修改,原因在于 Product( )不能反
向调用 InitPno( ),程序有明显的错
误
? 为了让子类可以弹性地给 pno赋值,同
时实现有子类反向修改构造函数,可将
程序修改为,
? class Product
? { protected:
? int pno;
? public:
? Product() { InitPno( ); }
? Void SetPno(int no) { pno=no;}
? virtual void InitPno( )=0;
? };
? class TV:public Product
? { public:
? Virtual void InitPno( )
? { int no;
? cin>>no;
? SetPno(no);
? }
? }
? Void main( )
? { TV tv; tv.InitPno( );
? }
构造函数与反向调用
? 利用 Initialize()取代了
Product(),两者的作用是一致的,
通过 Initialize() 可以反向调用
应用程序中的 InitPno( ),也即
应用程序可以修正框架中的
Initialize()和 InitPno( ),同
时可以弹性地对 pno赋值。
? 为了让子类可以弹性地给 pno赋值的另一种方法,
? class Product
? { protected:
? int pno;
? public:
? Initialize() { InitPno( ); }
? Void SetPno(int no) { pno=no;}
? virtual void InitPno( ) { SetPno(0);}
? };
? class TV:public Product
? { public:
? Virtual void InitPno( )
? { int no;
? cout<<“Input No:”;
? cin>>no;
? SetPno(no);
? }
? };
? Void main( )
? { TV tv; tv,Initialize();
? }
小结:利用应用框架的程序设计方法
? 多态性
? 接口和抽象类
? 双向通信
? 预设函数和重载
? 模板和反射
预设函数与反向调用在 MFC中的具体实现
? 本节将以 MFC为例,分析预设函数与反向调用的具体实现过
程 MFC框架中共有 Cobject,CCmdTarget,CWnd,CWinApp、
CFrameWnd六个核心类。
? ( 1) CWinApp为具体类,可直接创建对象,如:
? CWinApp App;
? 框架中安排一个指针 appCurrentWinApp指向 App
? 框架中设置了一个公用函数 AfxGetApp() 来取得
appCurrentWinApp指针,如执行函数 AfxGetApp() ->Run(),
实际就是执行了 App中的 Run()函数
? 框架中预设了 WinMain()函数作为应用程序的 main()函数。
? WinMain()
? {……
? AfxGetApp->InitInstance( );
? ……
? AfxGetApp->Run( );
? }
预设函数与反向调用在 MFC中的具体实现
? Run( )利用 for循环反复接受
? Windows传来的消息并送给 CFrameWnd类
对象。问题:本例中没有为 CFrameWnd类
派生对象。
? InitInstance( )也是一个预设函数,内容为:
? Virtual InitInstance( ){ return TRUE;},主要作要是作为一个虚拟
函数,留待用户在应用程序中定义新程序来重载他。
? AfxGetApp->InitInstance( )实际上调用了 App的 InitInstance( ),
若在 App中没有定义,实际上就调用了 CWinApp中的 InitInstance( )。
? Run( )也是框架中预设的一个虚拟函数,要求用户重新定义函数体。
Run( )预设的函数体为:
? Run()
? {……
? For(;;)
? {
? ……
? if(!PumpMessage()) break;
? }
? }
预设函数与反向调用在 MFC中的具体实现
? 解决办法:定义一个新的 InitInstance( )来取代预设函数。
? Class Ywindow:public CFrameWnd
? {
? Public:
? Ywindow( );
? };
? Class YApp:public CwinApp
? {
? virtual BOOL InitInstance( )
? { m_pMainWnd=new Ywindow;
? m_pMainWnd->ShowWindow(m_nCmdShow);
? return 1;
? }
? Yapp App;
预设函数与反向调用在 MFC中的具体实现
WinMain( )
………………………………………………………
…
InitInstance( )
……
Run( )
CWinApp类
……
InitInstance( )
YApp类
框架中
程序中
反向调用
M_pMainWnd
指针 Ywindow类的对象
YApp类的对象 App
应用框架的发展
1,Smalltalk-80语言中的 MVC框架
2,Macintosh推出的 MacApp框架
3,Borland OWL框架
4,Microsoft Visual C++中的 MFC框架
5,JAVA J2EE规范
6,Microsoft,net构架
…………
面向对象程序设计方法--基本原则
1,开闭原则 (OCP)
2,里氏代换原则( LSP)
3,依赖倒转原则( DIP)
4,迪米特法则( LoD)
5,单责任原则( SRP)
开闭原则 (OCP)
开闭原则是面向对象程序设计的第一个主要的原
则原则,这个原则最早由 Bertrand Meyer提出:
,Software entities should be open for
extension,but closed for modification”,即一个
软件实体应该对扩展开放,对修改关闭。也就是说,
当一个软件需要增加或者修改某些功能时候,应该尽
可能的只是在原来的实体中增加代码,而不是修改代
码。开闭原则保证了系统具有一定的稳定性,同时保
证了系统的灵活性。开闭原则的另外一个叫法称为
,对可变性的封装原则, 。
开闭原则 (OCP)
实现开闭原则的最好办法时为软件提供一个抽象
层,用户通过抽象层的接口访问软件实体,该抽象层
根据用户请求的具体内容决定使用软件实体中的那些
具体功,而从用户角度只能看到抽象层,并且以为是
抽象层在提供服务。当软件实体需要增加新的功能是,
只需要在软件中增加新的实体,而增加后客服端代码
和抽象层不需改变或只需很小的改变。
里氏代换原则( LSP)
里氏代换原则最早由 Barbara Liskov提出:, 基类出
现的地方,子类一定可以出现, 。这是显而易见的,但
里氏原则反映了分类层次的一些本质属性 。问题是:
是不是有基类的地方,子类就一定可以出现?
例如:
正方形是否是长方形的子类?
里氏代换原则( LSP)
长方形类
public class Rectangle
{
long width;
long height;
public void setWidth(long
width){
this.width = width;
}
public long getWidth(){
return width;
}
public void setHeight(long
height){
this.height = height;
}
public long getHeight(){
return height;
}
}
正方形类
public class Square extends
Rectangle( JAVA)
{
public void setWidth(long
width){
this.width = width;
this.height = width;
}
public long getWidth(){
return width;
}
public void setHeight(long
height){
this.width = height;
this.height = height;
}
public long getHeight(){
return height;
}
}
上例中, 将正方形作为长方形的扩展 ( 子类 ) 来实现,
但出现下列代码时:
public void resize(Rectangle r){
while(r.getHeight()>=r.getWidth())
r.setWidth(r.getWidth()+1);
}
输入是一个合乎条件的长方形 ( 高度 <=宽度 ) 时, 是
没有问题的, 会使长方形的形状变得如何?
问题是:如果输入的是一个正方形, 会出现什么情况?
里氏代换原则( LSP)
问题是:如果输入的是一个正方形, 会出现什么情况?
会使正方形不停地变大直到溢出 。
原因:设计是违反了里氏代换原则, 解决方法:为正方
形和长方形设计一个共同的基类, 如 Shape类, 使正方
形不是长方形的子类 。
里氏代换原则( LSP)
依赖倒转原则( DIP)
依赖倒转原则要求用户尽量依赖于抽象而不是 ( 具体 ) 实现 。
传统的过程式的程序设计方法倾向于使高层次的模块依赖于低
层次的模块,依赖倒转原则把这个依赖关系倒转过来。一般而言,
抽象层次包含的是应用系统的常规逻辑和宏观的控制过程,而低层
次模块包含一些具体的算法。低层次代码经常会变动,高层次则相
对稳定一些。为了更有效的保持系统的稳定性,应该使得低层次的
模块依赖于高层次模块。从软件复用的角度来看,只有实现依赖倒
转原则,才会避免当低层次模块发生改变的时候不会导致高层次模
块的修改。
迪米特法则( LOD)
迪米特法则又称为最少知识法则,其含义是指一个对象应当尽可能少地了
解其它对象。这种思想是和模块化程序设计中的模块低耦合高内聚同样的道理。
public class Someone{
public void operation1(Friend frieng){
Stranger stranger = friend.provide();
Stranger.operation3();
}
public class Friend{
private Stranger stranger = new Stranger();
public void operation2(){}
public Stranger provide(){
return stranger;
}
}
//Someone中的函数 operation1引
//用了 frieng和 stranger两个对象,
//不符合迪米特法则。思考题:请
//修改上述代码,使之符合迪米特
单责任原则 (SRP)
一个类应该有且仅有一个职责。关于职责的含意,
面向对象大师 Robert.C Martin有一个著名的定义:所谓
一个类的职责是指引起该类变化的原因,如果一个类具
有一个以上的职责,那么就会有多个不同的原因引起该
类变化,其实就是耦合了多个互不相关的职责,就会降
低这个类的内聚性。
需要说明的是:上述五个原则是面向对象程序设计
的一般指导性原则,违背这些原则有可能会使程序的结
构、弹性、可维性、规范化变差,甚至出错,但没有遵
循这些原则与程序出错或语法错误没有必然的联系。
设计模式( Design Patterns)
? 模式的来源
? 模式的定义
? 模式的要素
? 模式的种类
? 模式的前景
设计模式的起源
? 设计模式产生于建筑学和人类学的疑问:, 质量可
以客观评价吗?,
? Alexander 发现,通过这样的方式 — — 观察解决
相似问题的不同解决方案 — — 缩小他的关注焦点,
他可以洞悉优质设计之间的相似之处。他把这些相
似之处称为 模式 。
? 模式是, 在某一个情景下的问题解决方案,
模式的发展
? 1987年。 Ward Cunningham和 Kent Beck在一起用 Smalltalk作设计用户界面的工
作。他们决定使用 Alexander的理论发展出一个有五个模式的语言来指导 Smalltalk
的新手,因此他们写成了一篇 "Using Pattern Languages for Object-Oriented
Programs( 使用模式语言作面向对象的程序)的论文(发表于 OOPSLA'87 in
Orlando )。
? Jim Coplien开始搜集 C++语言的成例 (idioms)。 成例是模式的一种;这些 C++成例
发表在 1991年出版的 Advanced C++ Programming Styles and Idioms( 高级 C++
编程风格和成例)一书中
? 从 1990到 1992年,由四个人组成的小组 开始他们搜集模式的工作。关于模式的讨
论和工作会议则一再举行。
? 在 1993年 8月份,Kent Beck和 Grady Booch主持了一个在科罗拉多的山区度假村
召开的第一次关于模式的会议。模式研究的主要人物都参加了这次会议,包括:
Jim Coplien,Doug Lea,Desmond D'Souze,Norm Kerth,Wolfgang Pree,等。
?, 1995年, Design Patterns,一书发表。
? 编程模式语言大会( Pattern Languages of Programming,或者 PLoP) 每年一次
定期在美国举行,大会的论文也汇编成书,公开发表 [PLOP95],[PLOP96]、
[PLOP98],[PLOP99]。
组成一个模式的要素
? 模式的名称( Pattern name)
? 问题 (problem)
? 解决方案 (solution)
? 效果 (consequences)
模式的种类
目的
创建型 结构型 行为型
范围 类 Factory Method Adapter Interpreter
Template Method
对象 Abstract Factory
Builder
Prototype
Singleton
Adapter
Bridge
Composite
Decorator
Fa?ade
Flyweight
Proxy
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor
模式的前景
? 模式的应用前景
? 一套通用的设计词汇
? 书写文档和学习的辅助手段
? 简化设计的手段
? 实现重构目标
? 模式的研究前景
? 模式的形式化定义
? 设计模式的支持工具
? 自动代码生成
框架、设计模式、组件
? 组件通常是代码重用,而设计模式是设
计重用,框架则介于两者之间,部分代
码重用,部分设计重用,有时分析也可
重用。
? 框架比模式更具体;模式比框架粒度小。
? 如果说框架是软件,则模式是软件的知
识
? 框架模式 ;设计模式 ;代码模式
面向对象的程序设计方法
? 1.面向对象程序设计方法的起源
? 2.面向对象程序设计方法的基本概念
? 3,应用框架
? 4.面向对象程序设计方法的基本原则
? 5,设计模式
本章主要研究内容
面向对象程序设计方法的起源
? ( 1)面向对象的程序设计( Objiect Oriented
Programming,OOP) 方法起源于信息隐藏和抽象数
据类型概念,其研究开始于 20世纪 70年代,到 80年
代开始进入使用(以 C++ 的使用为标志)。
? ( 2) OOP的基本思想是将要构造的软件表示为对象
集,每个对象是将一组数据和使用它的一组基本操
作或过程封装在一起而形成的实体,对象与对象之
间依靠消息的传递实现联系。
? ( 3)在 OOP中较好地体现了人类的两种思维方式:
从一般到特殊的演绎推理和从特殊到一般的归纳方
法。
面向对象程序设计方法的基本概念--对象和类
? ( 1)对象( Objiect) 是将一组数据和作用该组数
据的一组操作或过程封装而形成的实体。是 OOP中的
最基本单元。
? ( 2) 对象由对象名、状态、方法(操作)组成。其
中状态是指对象存储的数据结构的值的集合,状态
随对象的运行(即操作)而变化。
? ( 3)对象具有封装性,从外面只能看见其外部特性
(及具备的处理能,由操作实现),而处理能力是
如何实现的及对象的内部状态对外都是不可见的。
面向对象程序设计方法的基本概念--对象和类
? ( 4)对象的功能是比较基本的,对象要完成复杂的
功能,需要与其他对象协同工作,即一个对象可能
要引用另一个对象。对象之间的相互作用只能通过
消息的转递来实现。
? ( 5) 一个对象收到来自其它对象的消息后,就可以
激活(运行)对象中的某个操作,改变其内部状态,
必要时以消息回传的方式将运行的结果通知引用该
对象的对象。
? ( 6)在一个软件系统中,对象的个数及种类是很多
的,通常,把具有相同内部存储结构和相同的一组
操作的对象看作是同一, 类,,而将属于某一类的
对象称之为该类的实例。
面向对象程序设计方法的基本概念--对象和类
? ( 1)例如:狗是具有某些特性的动物的总称,,小
黄是一条狗,,则可以认为, 狗, 是一个类,而小
黄是一个狗的实例(对象)
? ( 2)教学楼是具有某种特定属性的建筑的总称,是
一个, 类,,则西 12教学楼则是一个具体的对象。
? ( 3)从方法学上看,类是一个抽象的概念,因为它
抽象了一类对象的数据及方法特征,从工程实现的
角度考虑,类是一种共享机制,属于该类的对象都
可以共享类中定义的数据及操作。
面向对象程序设计方法的基本概念--继承性
? 从对象到类是一个抽象的过程,类与类之间也可以
有许多共性,也即可以在抽象的基础上进一步抽象。
? ( 1)例 1:如, 小黄-》狗-》动物,,狗比小黄
抽象,动物比狗抽象。( 2)例 2:数的分类
类 A( 数)
类 D( 复数)
类 C( 双精度数)
类 B( 浮点数) 类 E( 整数)
面向对象程序设计方法的基本概念--继承性
? 一般地,上一层的类成为下一层类的超类
( superclass,或基类 baseclass),下一层的类成为
子类( subclass)。
? 上图中,C是 B的子类,也是 A的子类,B,D,E均是 A
的子类,A是超类。
? 子类自动继承超类(父类)的性质,如 B,D,E自动
继承 A的性质。继承性具有传递性,如 B继承 A的性质,
C继承 B的性质,则 C继承 A的性质。
? 总之,一个类除了具有该类自有的各种性质外,还
可以继承超类的所有性质。
面向对象程序设计方法的基本概念--继承性
? 继承性的主要作用是提供了共享机制,可以
缩短代码的长度,减轻编程人员的负担,节
省存储空间,提高程序的可阅读性及可靠性。
面向对象程序设计方法的基本概念--实例(字
符集合,用 C++表示 )
? #include <IOSTREAM.H>
? #include <STDIO.H>
? #define MAX_SET_SIZE 256
? class CCharSet
? {
? public:
? void Clear();//字符集的初始化
? void WriteSet();//显示字符集的内容
? void InsertMember(char c); //往字符集插入一个字符
? void DeleteMember(char c); //从字符集中删除一个字符
? bool CharInSet(char c); //判断一个字符是否是字符集的成员
? private:
? char m_Set[MAX_SET_SIZE];
? int m_nNum;
? };
面向对象程序设计方法的基本概念--实例
? int main(int argc,char* argv[])
? {
? CCharSet set;
? printf("Testing general set object:\n");
? set.Clear();
? set.InsertMember('A');
? set.InsertMember('h');
? set.InsertMember('4');
? set.InsertMember('+');
? cout << "Set is" << endl;
? set.WriteSet();
? set.DeleteMember('A');
? set.DeleteMember('4');
? set.DeleteMember('Y');
? cout << "After deleting A,4,and Y,set is" << endl;
? set.WriteSet();
? cout << "Is char '4' in set? " << (set.CharInSet('4')? "true", "false") << endl;
? cout << "Is char 'h' in set? " << (set.CharInSet('h')? "true", "false") << endl;
? return 0;
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::Clear()
? {
? m_nNum = 0;
? }
? void CCharSet::WriteSet()
? {
? cout << "[";
? for(int i=0; i<m_nNum; i++)
? {
? printf(" %c ",m_Set[i]);
? }
? cout << "]" << endl;
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::InsertMember(char c)
? {
? if (m_nNum == MAX_SET_SIZE)
? {
? //字符存储数量已经达到上限
? return;
? }
? if (!CharInSet(c))
? {
? m_Set[m_nNum] = c;
? m_nNum ++;
? }
? }
面向对象程序设计方法的基本概念--实例
? void CCharSet::DeleteMember(char c)
? {
? for(int i=0; i<m_nNum; i++)
? {
? if (m_Set[i] == c)
? {
? m_nNum --;
? for(int j=i; j<m_nNum; j++)
? {
? m_Set[j] = m_Set[j + 1];
? }
? break;
? }
? }
? }
面向对象程序设计方法的基本概念--实例
? bool CCharSet::CharInSet(char c)
? {
? for(int i=0; i<m_nNum; i++)
? {
? if (m_Set[i] == c)
? {
? return true;
? }
? }
? return false;
? }
面向对象程序设计方法的基本概念--实例
? 运行结果:
面向对象程序设计方法的基本概念--实例
? 试验 2:
? 1.用面向对象思想描述下列组合电路;
? 2.对给出的输入,计算输出。要求编程实现
( C++ 语言),打印运行结果。
? 作完后发到我的信箱
+
+
X
A
B
C
D
G
E
F
面向对象程序设计方法的基本概念--实例
输入 输出
A B C D E F G
0 0 0 0
0 0 0 1
0 0 1 0
0 0 1 1
0 1 0 0
0 1 0 1
0 1 1 0
0 1 1 1
1 0 0 0
1 0 0 1
1 0 1 0
1 0 1 1
1 1 0 0
1 1 0 1
1 1 1 0
1 1 1 1
面向对象程序设计方法--应用框架定义
? 应用框架( Application FrameWork) 是在特定应用领域中,
程序的共同结构,应用框架就是整个或部分挼件系统的可
重用行设计,表现为一组抽象组件及组件实例间交互的方
法。或者:框架是被应用开发者定制的应用骨架。
? 框架是组件技术、软件体系结构研究和应用软件开发三者
发展结合的产物。一个框架是一个可复用的设计组件,其
规定了应用的体系结构,规定了整个设计、协作组件之间
的依赖关系、责任分配和控制流程(利用抽象类与其实例
之间的协作实现),为组件复用提供了上下文关系。
? 框架以组件库的方式出现,但不同于组件库,组件库是框
架的一个重要部分,但框架更重要的是还提供了框架内对
象的交互模式和控制流模式 。
面向对象程序设计方法--应用框架中的重要概念
? ( 1)抽象:在同领域的软件程序中,将存在于相
关类中的共性提取出来,形成更为抽象的类,再
建立类与类之间的交付模式和控制流模式,得到
应用框架。
? ( 2)派生:以框架中抽象类为基础,添加特殊功
能,成为具体类,再创建对象。
? 例:矩形抽象图的形成
抽象
图
面向对象程序设计方法--应用框架的基本组成
? 应用框架的基本组成:
? ( 1)类库:含有抽象类、具体类、函数以及对象
等。抽象类有成员函数,函数内有指令,但主要
函数体内的指令缺省,预留给应用程序补充(重
载)。
? ( 2)抽象类之间的关系及控制流程(正向调用和
反向调用)
面向对象程序设计方法--应用框架与类库的联系
? 应用框架是在类库基础上的发展,已经包含了类
库的基本功能,但应用框架编程与调用类库编程
的方式有很大的不同。
面向对象程序设计方法--应用框架与类库的联
系
应用框架 类库
作用:供应用程序派生出具体类,
派生式时修正类, 必须将虚函数具
体化 ( 给出函数体 ) 才能创建对象 。
作用:供程序员用其中的类来产生对
象, 应用程序不能修改类库
应用框架中的类的成员函数与应用
程序中的函数之间可以双向调用
应用程序的函数可以调用类库中的函
数, 但类库中的函数不能调用应用程
序中的函数 ( 晚辈调用前辈, 及正向
调用, 不能倒过来 )
含有类间的关系 ( 预设了对象间的
互助合作关系 )
类是独立的, 没有预设对象间的通信
方式, 只能由用户 ( 应用程序 ) 确定
对象中常定义有预设行为, 应用程
序可以继承, 修改预设行为
类库中对象的行为是固定的, 无法修
正 。
面向对象程序设计方法--应用框架
应用框架的主要代表有 Smalltalk-80语言中
的 MVC框架,Macintosh推出的 MacApp框
架,Visual C++中的 MFC框架。
MFC类库介绍
MFC是什么?
微软基础类库( MFC,Microsoft
Foundation Class) 是微软为 Windows程序
员提供的一个面向对象的 Windows编程接
口 API,大部分以 C++形式封装。
MFC库中的类代表了窗口、对话框、设
备上下文、通用 GDI对象,例如画刷和笔、
控件以及其它标准 Windows项。这些类对
于它们封装的 Windows中的结构提供了一
个习惯的 C++成员函数接口。
为什么使用 MFC?
? 首先,MFC提供了一个标准化的结构,这样开发人员不
必从头设计创建和管理一个标准 Windows应用程序所需
的程序,而是, 站在巨人肩膀上,,从一个比较高的起
点编程,故节省了大量的时间;
? 其次,它提供了大量的代码,指导用户编程时实现某些
技术和功能。 MFC库充分利用了 Microsoft开发人员多年
开发 Windows程序的经验,并可以将这些经验融入到你
自己开发的应用程序中去。
? 对用户来说,用 MFC开发的最终应用程序具有标准的、
熟悉的 Windows界面,这样的应用程序易学易用;
? 另外,新的应用程序还能立即支持所有标准 Windows特
性,而且是用普通的、明确定义的形式。
MFC的特性
? 封装
? 继承
? 虚函数与多态
封装
? 对 Win32应用程序编程接口的封装
用一个 C++ Object来包装一个 Windows Object。
? 对应用程序概念的封装
MFC把许多基本处理封装起来,替程序员完成这些工作
MFC提出了以文档 -视图为中心的编程模式,MFC类库封
装了对它的支持。
? 对 COM/OLE特性的封装
MFC的 OLE类封装了 OLE API大量的复杂工作,这些类
提供了实现 OLE的更高级接口。尤其是 Interface接口
? 对 ODBC功能的封装
以少量的能提供与 ODBC之间更高级接口的 C++类,封
装了 ODBC API的大量的复杂的工作,提供了一种数据
库编程模式。
继承
? MFC抽象出众多类的共同特性,设计出一些基类作为实现
其他类的基础。
? 最重要的基类是 CObject和 CCmdTarget:
CObject是 MFC的根类,实现了动态类信息、动态创建、
对象序列化、对程序调试的支持等重要的特性
CCmdTarget通过封装一些属性和方法,提供了消息处理
的架构。 MFC中任何消息处理类都从 CCmdTarget派生。
? 针对每种不同的对象,MFC都设计了一组类对这些对象进
行封装,每一组类都有一个基类,从基类派生出众多更
具体的类。这些对象包括以下种类:窗口对象,基类是
CWnd; 应用程序对象,基类是 CWinThread; 文档对象,
基类是 CDocument,等等。
? 程序员可以结合自己的实际,从适当的 MFC类中派生出自
己的类,实现特定的功能,达到自己的编程目的。
虚函数与多态
? MFC以, C++”为基础,支持虚函数和多态
? 作为一个编程框架,MFC建立了消息映射机制,
以一种富有效率、便于使用的手段解决消息处
理函数的动态约束问题(多态性)
? 通过虚函数和消息映射,MFC类提供了丰富的
编程接口。程序员继承基类的同时,把自己实
现的虚拟函数和消息处理函数嵌入 MFC的编程
框架。 MFC编程框架将在适当的时候、适当的
地方来调用程序的代码。
MFC的构成
MFC可分为两个主要部分:
? ( 1)基础类(见下简图)
? ( 2)宏和全程函数。
MFC基础类
? 根类,CObject
? MFC应用程序框架类
? 窗口、对话和控件类
? 绘画和打印类
? 简单的数据类型类
? 数组、列表和映射类
? 文件和数据库类
? Internet和网络类
? OLE类
? 调试和异常类
MFC宏和全局函数
Microsoft基本宏和全程函数提供以下功能:
? 数据类型
? MFC类对象的强制类型转换
? 运行时刻对象类型服务
? 诊断服务
? 异常处理
? CString格式化及信息框显示
? 消息映射
? 应用消息和管理
? 对象连接和嵌入( OLE) 服务
? 标准命令和 Windows IDs
? 数据库
? ClassWizard向导
? 其他
基类,CObject类
? CObject 是大多数 MFC类的根类或基类。
? MFC从 CObject派生出许多类,具备其中的一个或者多个
特性。
? 程序员也可以从 CObject类派生出自己的类,利用
CObject类的特性。
? CObject类的特性
( 1)对运行时类信息的支持
提供 IsKindOf函数来实现这个功能
( 2)对动态创建的支持
窗口对象、视对象、文档对象
( 3)对序列化的支持
将对象内容存入一个文件或从一个文件中读取对象内容
MFC应用程序框架类
? 应用程序框架不仅提供了构建应用程序所需要
的类,还定义了程序的基本执行结构。所有的
应用程序都在这个基本结构基础上完成不同的
功能。
? MFC的应用程序结构体系,是以文档 -视结构
( Document/View) 为核心的编程模式。
? MFC除了定义程序执行结构之外,还定义了三
种基本的主窗口模型:单文档窗口,多文档窗
口和对话框作为主窗口。
MFC应用程序框架类
? MFC应用程序框架类用于一个应用程序的构造。它们对
大多数应用程序提供了通用功能。可以在框架中填写对
于应用特定的功能。典型地,可以从构造类派生新的类,
然后添加新成员和 /或覆盖已有的成员函数。
? AppWizard产生几种类型的应用,所有这些都以不同的方
式使用应用框架。 SDI( 单文档界面)和 MDI( 多文档界
面)应用充分利用了框架的一部分,即文档 /视结构。
? 文档 /视应用包含了一个或多个文档、视图和框架窗口集。
一个文档 -模板对象将类与每一个文档 /视图 /框架集联系
起来。
? 所有的 MFC应用都至少有两个对象:一个由 CWinApp派
生出的应用对象和一些派生自 CWnd的主窗口对象。
文件和数据库类
? 这些类允许将信息存储在一个数据库或一个磁盘文件中。包括:
? 文件 I/O类
对传统磁盘文件、内存文件,Ative流和 Windows套接字提供了一
个接口。所有从 CFile派生出来的类可以与 CArchive对象一起使用
来执行串行操作。
? DAO类
与其它应用框架一起工作,执行对 DAO( 数据访问对象)数据库
的访问。它使用的数据库引擎与 Microsoft VB和 Microsoft Access
相同。 DAO还能访问许多支持开放数据库链接( ODBC) 驱动程
序的数据库。
? ODBC类
和其它应用框架一起工作,执行对开放数据库链接( ODBC) 数
据库的访问。使用 ODBC数据库的程序至少有一个 CDatabase对象
和一个 CRecordset对象。
应用消息管理
? Windows消息的管理包括消息发送和消息处理
? 为了支持消息发送机制,MFC提供了三个函数:
SendMessage,PostMessage和
SendDlgItemMessage。
? MFC采用一种消息映射机制来决定如何处理特
定的消息。这种消息映射机制包括一组宏,用
于标识消息处理函数、映射类成员函数和对应
的消息等。
消息映射
? 消息映射就是让程序员指定要某个 MFC类(有
消息处理能力的类)处理某个消息。
? MFC处理三类消息:
Windows消息、控制通知消息、命令消息
? 使用 MSG结构来描述消息的 6个属性
? 应用程序通过窗口过程来传递和处理消息
? 应用程序通过消息循环来获得对消息的处理
? MFC 使用 ClassWizard帮助实现消息映射
应用框架的程序设计方法--多态性
? 追求软件的弹性(适用性)和可重用行是面向对象程序设
计方法的主要目的之一,多态性是实现程序适用性和可重用行
的关键技术。
多态性简单地说是为同一函数 (和运算符 )提供几个版本,
不同的对象收到相同的消息产生不同的动作,这种功能称为多
态性。
C++ 支持两种多态性:编译时的多态性通过函数 (和运算
符 )重载来实现,运行时的多态性通过虚函数来实现。
两个或两个以上的函数如果函数名相同,只要参数的个数
不同或参数的类型不同,编译系统便能够在函数调用时知道应
当实际调用哪个函数,这就叫做函数重载。
在派生一个类时不仅能添加新的数据成员和成员函数,还
可以重新定义基类的函数。
应用框架的程序设计方法--多态性
在教材的 P64举了一个将多态性用于自动售
货机及硬币接口的例子,请大家看看。
应用框架的程序设计方法--反向调用
传统意义上,应用程序可以调用库函数,
库函数不能反向调用应用程序中的函数,原
因是库函数开发在前,应用程序开发在后,
即只能晚辈调用前辈,也称正向通信。
反向调用是前辈(应用框架)调用晚辈
(用户应用程序),是应用框架的进行程序
设计的一个重要方法,也是与使用类库的重
要区别。
应用框架的程序设计方法--反向调用
反向调用的功能:
( 1)框架能事先定义许多预设函数;
( 2)程序员的主要工作设计函数,这些函数通过
反向调用,可以修改或取代框架中的同名函数
( 3)如果应用程序中的函数已经修改或取代了预
设函数,框架就优先调用程序中的函数,否则调
用预设函数。
应用框架的程序设计方法--反向调用
正
反
向
调
用
实
例
使用者
Windows
预设 f4()预设 f3()预设 f1()
f4()f2()f1()
事件 1 事件 3事件 2
消息 消息 (调用 )
消息 正向 消息
应用程序
应用框架
f2( )=0
消息
反向 消息
应用框架的程序设计方法--抽象类
抽象是指提取共同特征的过程,在软件工程中,通过
抽象后得到的具有相同属性的类就是抽象类。但在软件实
现时,定义抽象类为:有成员函数是不完整的类中。反之,
如果类中的所有成员函数都是完整的,则是具体类。
一般地,不完整是指一个函数的函数体缺省。
Class Person
{
public:
virtual void Display()=0;
}
Display()是一个纯粹虚拟函数,其函数内容为空,留待子类
补充。
在此基础上,C++有更为严格的定义:凡含有纯虚函数的类
就是抽象类,不含有纯虚函数的类就是具体类。
应用框架的程序设计方法--抽象类
Class Person
{
public:
virtual void Display()=0;
}
Class Employee:public Person
{……
public:
……
}
Employee是一个子类(派生类), 但他依旧没有将其函数
Display()内容补充完整,Employee也是一个抽象类。
应用框架的程序设计方法--抽象类
( 1) 纯虚函数的产生:将名称相同但内容不同的
函数提取出来,放进抽象类时,即为纯虚函数。
( 2)纯虚函数与虚拟函数的区别在于虚拟函数在抽
象类里面是由函数体的,而纯虚函数的函数体是
缺省的。在 C++语言里,用“= 0”表示纯虚函数,
如 virtual void Display()=0;
( 3)纯虚函数的作用一方面在于函数名共享,更重
要的是留给了编程人员很大的自主空间,编程人
员可以补充、完善本身的特殊性,得到合乎自己
要求的功能。
( 4)抽象类不能创建对象,只有具体类才能创建对
象。(为什么?)
应用框架的程序设计方法--抽象类
class Person
{
protected:
char name[20];
public:
void SetName(char *na) {strcpy(name,na);}
virtual void Display( )=0;
};
class Customer:public Person
{
public:
virtual void Display( )
{ cout<<“Customer:”<<name<<endl;}
};
抽象类
具体类
(继承)
应用框架的程序设计方法--抽象类
? 抽象类使用上的限制:抽象类由于自身含有不
完整的成员函数,不能创建对象,具体地说:
? ( 1)抽象类不能成为自变量的类型,如:
? Void disp( Person p) {p.SetName(“Tom”);}
? Void main()
? { Person x; disp(x);}
? 由于 Person为抽象类,无法创建对象 p,x;
应用框架的程序设计方法--抽象类
? ( 2)抽象类不能成为函数的类型,如:
? Person Sub(Person p)
? { return p;}
? Sub( )函数将返回对象 p,必须派生 Person对象
来接受返回值,由于 Person为抽象类,无法创
建对象,若采用具体类就可以:
? Customer Sub( Customer c)
? { return c;}
应用框架的程序设计方法--抽象类
? ( 3)抽象类不能用来转换对象的类型,如:
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? ((Person)cust).SetName(“Jack”);
? }
? 实际运行时,必须创建一个 Person类的临时对象,用于
存放 cust对象转换后的内容,但 Person不能派生出临时
对象。
应用框架的程序设计方法--抽象类
? 抽象类不能派生对象,但可以派生指针。
(比较好的用法)
? ( 1)使用抽象类指针指向子类的对象:
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? Person *p=&cust;
? p->Display( );
? }//输出结果为,Customer,Linda
“Linda”
指针 p 对象 cust
应用框架的程序设计方法--抽象类
? ( 2)使用抽象类指针作为函数的自变量,
? Void Set(Person *p ) {p->SetName(“Amy”);}
? void main( )
? {
? Customer cust;
? cust.SetName(“Linda”);
? Set(&cust);
? cust.Display( );
? }//输出结果为,Customer,Amy
应用框架的程序设计方法--抽象类
? ( 3)函数可以返回抽象类的指针
? Person *sub( )
? {
? Customer *pc=new Customer( );
? pc->SetName(“Tom”); //调用 Person类中的 SetName( )函数
? return(Person*)pc;
? }
? void main( )
? {
? sub( )->Display( );//调用 Customer类中的 Dispaly( )函数
? delete sub( );/ /delete pc
? }//输出结果为,Customer,Tom
应用框架的程序设计方法--双向通信
? 传统的链接库与类库都只提供单(正)向通信(调用),
而应用框架则提供双向通信。
? ( 1)传统的链接库提供的正向通信
? void main( )
? {
? char place[ ]=“Beautiful Beijing”;
? int len=strlen(place);//strlen( )是库函数,直接调用
? int x=abs(-8); //abs( )是库函数,直接调用
? cout<<len<<“,”<<x<<endl;
? }
应用框架的程序设计方法--双向通信
? void main( )
? {
? VIP v(“Tom”,“321-7777”);
? v.Display( );
? }
? 输出结果,Customer,Tom
? TEL,321-
7777
? ( 2)应用框架提供的正向通信
? Class VIP:public Customer
? {
? char tel[15];
? public:
? VIP(char *na,char*t) {
? SetName(na);//调用抽象类 Person
? 类中的成员函数
? strcpy(tel,t);
? }
? virtual void Display( ) {
? Customer::Display( ); //调用
? Customer类中的成员函数
? cout<<“TEL:”<<tel<<endl;
? }
? }
应用框架的程序设计方法--双向通信
class Product {
protected:
int pno; //产品编号
Customer *pc;
public,
Product(int no) { pno=no;} //Product类的构造函数
void SoldTo(Customer &c) {pc=&c;}; //在产品与顾
客之间建立对应关系
void Inquire( )
{ Print( );
cout<<“sold to…”<<endl;
pc->Display( );//调用 Customer类的 Display( )
}
virtual void Print( )=0;//纯粹虚拟函数
}
( 3)应用框架提供的反向通信
应用框架的程序设计方法--双向通信
( 3)应用框架提供的反向通信
Product,
Person,
Customer
三个类
之间的
关系
VIP
Product
Customer
Person
TV
……….,……………… ……………
应用框架的程序设计方法--双向通信
( 3)应用框架提供的反向通信
class TV:public Product
{
double price;
public:
TV(int no,double pr):Product( no )
{ price=pr;}
virtual void Print( )// 重载 Print( )
{
cout<<“TV No:”<<pno<<endl;
cout<<“Price:”<<price<<endl;
}
};
Void main( )
{
TV t(1100,1800.5)
VIP vp(“Peter”,”666-8899”);
t.SoldTo(vp);
t.Inquire( );
};
运行结果,TV No:1100
Price:1800.5
Sold to…
Customer:Peter
TEL:666-8899
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
反向通信有两种形式:
( 1)框架中的类调用同体系中的子类
( 2)框架中的类调用不同体系中的子类--跨体
系反向调用
问题:反向调用是前辈调用晚辈,先辈(如父类)
设计在前,晚辈(派生类或子类)设计在后,如何
保证先辈要调用的函数再晚辈中就一定存在?
反向通信的实现机理:
双向调用都是依赖虚拟函数来完成的,如上例中:
TV的 Print( ),VIP的 Display( )。( 请参看 41页)
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
如果 Print( )不是虚拟函数而是一般函数,能不能实现反向调用?
Class Product{
Int pno;
Public:
Product(int no) {pno=no;}
Void Inquire( ){Print( );}
Void Print( ){ cout<<“This is Product::Print( )\n”;}
};
Class TV:public Product{
Double price;
Public:
TV(int no,double pr):Product(no)
{price=pr;}
Void Print( ){ cout<<“This is TV::Print( )\n”;}
};
Void main( )
{
TV t(1100,1800.5);
t.Inquire( );
}//运行结果是什么?
应用框架的程序设计方法--双向通信
( 3)实现反向通信的关键技术--虚拟函数
Print( )是不是一定必须是纯粹虚拟函数才可能实现反向调用?
Class Product{
Int pno;
Public:
Product(int no) {pno=no;}
Void Inquire( ){Print( );}
Virtual Void Print( ){ cout<<“This is Product::Print( )\n”;}
};
Class TV:public Product{
Double price;
Public:
TV(int no,double pr):Product(no)
{price=pr;}
Virtual Void Print( ){ cout<<“This is
TV::Print( )\n”;}
};
Void main( )
{
TV t(1100,1800.5);
t.Inquire( );
}//运行结果是什么?
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
Product中的 Inquire( )要调用 VIP::Display( ),
Product与 VIP不在同一个类系,即实现的是跨体系的反向
调用,Inquire( )与 Display( )之间只能通过指针调用,
不能经由函数的形式调用。
( 1) VIP必须是具体类才能派生对象
( 2) pc指针指向派生的对象
( 3)由 pc所指向的对象来调用 Display( )函数
( 4) Inquire() 经过 pc而调用 Display( )函数
完成上述过程有两个关键点:
( 1) Display( )必须是虚拟函数(为什么?)
( 2) pc指针只能是 Customer* 类型(为什么不能是 VIP*
型?)
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
例子:先在框架中增加一个公用函数
void DoInquire(Product *p){p->Inquire( );}
class Computer:public Product
{
char model[20];
public:
Computer(int no,char *ml):Product(no)
{ strcpy(model,ml); }
virtual void Print( ) { Show( );}
virtual void Show( )
{ cout<<“This is Computer::Show( )\n”;}
};
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
class Notebook:public Computer
{
public:
Notebook(int no,char *ml),Computer(no,ml)
{ }
virtual void Show( )
{ cout<<“This is Notebook::Show( )\n”;}
};
Void main( )
{
Customer cust; cust.SetName(“****”);
Computer com(1288,”PowerPC”);
Com.SoldTo(cust);
DoInquire(&com);
}
输出结果:
This is Computer::Show()
Soldto:…
Customer:****
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
类的结构图:
DoInquire()
函数
Notebook
类
Computer
类
Product
类
Customer
类
Person
类
……………………………………………………
…
框架中
程序中
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
反向调用过程,a,利用 Customer类派生出对象 com,
执行 DoInquire(& com) 使指针 p指向对象 com
指针 p
Customer类
Print( )
Show( )
………………………………………………………
…
Com对象
Inquire( )
b,执行 DoInquire( )中的 p->Inquire( ) 就等于执行了 Product
类中的 Inquire() 函数。 Inquire( ) 反向调用 Computer类中的
Print(),Print() 再调用同一个类中的 Show()
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
另一种情况,使用 Computer的子类 Notebook:
void main( )
{
Customer cust;
cust.SetName(“****”);
Notebook nb(1288,“486DX66”);
nb.SoldTo(cust);//不能将 cust换成 nb
DoInquire(&nb);
}//输出结果,This is Notebook::Show( )
sold to…
Customer:****
执行 P->Inquire( )相当于执行 nb.Inquire( ),而 nb是由 Notebook创建出来
的,故能反向调用 Notebook中的函数,考虑:若在 Notebook类中只定义了
Print( )函数,而没有定义 Show( ) 函数,如何反向调用?
应用框架的程序设计方法--双向通信
( 4)跨体系的反向调用
另一种情况,使用 Computer的子类 Notebook:
void main( )
{
Customer cust;
cust.SetName(“****”);
Notebook nb(1288,“486DX66”);
nb.SoldTo(cust);//不能将 cust换成 nb
DoInquire(&nb);
}//输出结果,This is Notebook::Show( )
sold to…
Customer:****
执行 P->Inquire( )相当于执行 nb.Inquire( ),而 nb是由 Notebook创建出来
的,故能反向调用 Notebook中的函数,考虑:若在 Notebook类中只定义了
Print( )函数,而没有定义 Show( ) 函数,如何反向调用?按照先晚辈后
前辈的原则,此时只能调用父类 Computer中的 Show( ) 函数。
应用框架的程序设计方法--预设函数
( 1) 预设函数的定义
( 1) 反向调用是前辈调用晚辈,其技术核心是虚拟函数,即在
先辈(如应用框架)中事先定义了许多可能要用的函数或函数
名,这些函数是依惯例(或按常规、常识)事先定义的,也称
预设函数。
( 2) 惯例是指通常情况下处理问题的方法,如出租车的行驶路
线,一般政府机关办事的流程等。
( 3)惯例所对应的方法,一般都是经过实践检验的理想方法,
直接使用可使用户负担减轻,提高效率。
( 4)惯例只适合一般情况,若遇上特殊情况,应对惯例进行修
改,甚至取消。
应用框架的程序设计方法--预设函数
( 2) 预设函数的应用
在面向对象程序设计中,按照晚辈优先的原则,预设函数(通常存在应用框
架中)通常其备用函数的作用。当应用框架反向调用时,若发现用户的应用
程序中没有定义某函数,才逐步退回到上一级类中去找,如刚介绍过的
Computer:,Show( )。 再如 Display( )函数:
Person
类
……………………………………………………
Virtual Display()
{… };
Customer
类
Virtual Display()=0;
VIP
类
Virtual Display()
{… }; 框架
应用程序
应用框架的程序设计方法--预设函数
( 2) 预设函数的应用
例 1:
Class VIP:public Customer
{
char telphone[15];
Public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}//没有定义 Display( ),只能采用备用函数,
}; //Customer::Display( )
Void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果,Customer:Mr.White
应用框架的程序设计方法--预设函数
例 1:另一种形式
Class VIP:public Customer
{
char telphone[15];
Public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}
virtual void Display( ){ cout<<“TEL:”<<telphone<<endl;} //优先调用
};
void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果,TEL:888-2222
应用框架的程序设计方法--预设函数
在有些场合,晚辈类定义的函数并不一定要完全取代预设函数,也可以只是修正,如:
Class VIP:public Customer
{
char telphone[15];
public:
VIP(char *na,char *tel) {
SetName(na);
strcpy(telphone,tel);
}
virtual void Display( ){ Customer::Display( );
cout<<“TEL:”<<telphone<<endl;}
}; //不是覆盖预设函数,而是修正
Void main( )
{ VIP vip(“Mr.White”,“888-2222”);
Vip.Disply( );
}//输出结果:
Customer:Mr.White
TEL:888-2222
构造函数与反向调用
? 框架中的函数可以反向调用应用程序中的函数,但构
造函数不能反响调用。原因在于当执行父类的构造函
数时,由于子类部分尚未完成,有可能出现无法调用
子类函数的问题,例如:
? class Product {
? protected:
? int pno;
? public:
? Product(int no)//构造函数
? { pno=no;
? Print( );
? }
? virtual void Print( )=0;//纯虚函数
? };
构造函数与反向调用
? class TV:public Product
? {
? char model[20];
? public:
? TV(int no,char *m),Product(no)
? { strcpy(model,m);}
? virtual void Print( )
? {cout<<pno<<“,”<<model<<endl;}
? };
? void main( )
? {
? TV tv(100,“KS1100”);
? )
构造函数与反向调用
? 上例是 Product类的构造函数反向调用,是一种直接的
行为,间接反向也不行,如:
? class Product
? {
? Product( )//构造函数
? { ……
? func( );//过渡函数,用于反向调用
? }
? void func( )
? {……
? Print( );
? }
? virtual void Print( )=0;//纯虚函数
? };//思考题:构造函数的跨体系反向调用行吗?请举例
说明
构造函数与反向调用
? 利用反向调用可以实现对框架里中类的成员变量赋值,如对
Product类中的 pno赋值:
? class Product
? {
? protected:
? int pno;//只能是 protected 或 public型,不能是 private型
? public:
? virtual void Paint( )=0;
? };
? class TV:public Product
? {
? public:
? TV(int no) { pno=no;}
? Virtual void Paint( ) { cout<<pno<<endl;}
? };
? Void main( )
? {
? TV tv(1100); Tv.Print( );//不灵活(弹性不好),要改 pno的值,就必须改程序
? } //输出为,1100
构造函数与反向调用
? 另一种方法,pno可以是 private型:
? class Product
? {
? protected:
? int pno;
? public:
? Product(int no) {pno=no;}
? virtual void Paint( )=0;
? };
? class TV:public Product
? {
? public:
? TV(int no),Product(no) { }
? Virtual void Paint( ) { cout<<pno<<endl;}
? };
? Void main( )
? {
? TV tv(1100); Tv.Print( );//不灵活(弹性不好),要改 pno的值,就必须改程序
? } //输出为,1100
构造函数与反向调用
? 上述两种方法是一般意义的赋值方法,有两个明
显的缺点,
? ( 1)赋值是通过构造函数实现的,但构造函数是
不能重复定义的,即不能重载,应用程序无法经
过重载的方法修改构造函数。
? ( 2)程序的弹性不好,要修改初值必须要改程序
构造函数与反向调用
? 通过 InitPno( )和 SetPno()实现了
? 让子类 TV弹性地给 pno赋值,但
没有实现对构造函数 Product( )的
修改,原因在于 Product( )不能反
向调用 InitPno( ),程序有明显的错
误
? 为了让子类可以弹性地给 pno赋值,同
时实现有子类反向修改构造函数,可将
程序修改为,
? class Product
? { protected:
? int pno;
? public:
? Product() { InitPno( ); }
? Void SetPno(int no) { pno=no;}
? virtual void InitPno( )=0;
? };
? class TV:public Product
? { public:
? Virtual void InitPno( )
? { int no;
? cin>>no;
? SetPno(no);
? }
? }
? Void main( )
? { TV tv; tv.InitPno( );
? }
构造函数与反向调用
? 利用 Initialize()取代了
Product(),两者的作用是一致的,
通过 Initialize() 可以反向调用
应用程序中的 InitPno( ),也即
应用程序可以修正框架中的
Initialize()和 InitPno( ),同
时可以弹性地对 pno赋值。
? 为了让子类可以弹性地给 pno赋值的另一种方法,
? class Product
? { protected:
? int pno;
? public:
? Initialize() { InitPno( ); }
? Void SetPno(int no) { pno=no;}
? virtual void InitPno( ) { SetPno(0);}
? };
? class TV:public Product
? { public:
? Virtual void InitPno( )
? { int no;
? cout<<“Input No:”;
? cin>>no;
? SetPno(no);
? }
? };
? Void main( )
? { TV tv; tv,Initialize();
? }
小结:利用应用框架的程序设计方法
? 多态性
? 接口和抽象类
? 双向通信
? 预设函数和重载
? 模板和反射
预设函数与反向调用在 MFC中的具体实现
? 本节将以 MFC为例,分析预设函数与反向调用的具体实现过
程 MFC框架中共有 Cobject,CCmdTarget,CWnd,CWinApp、
CFrameWnd六个核心类。
? ( 1) CWinApp为具体类,可直接创建对象,如:
? CWinApp App;
? 框架中安排一个指针 appCurrentWinApp指向 App
? 框架中设置了一个公用函数 AfxGetApp() 来取得
appCurrentWinApp指针,如执行函数 AfxGetApp() ->Run(),
实际就是执行了 App中的 Run()函数
? 框架中预设了 WinMain()函数作为应用程序的 main()函数。
? WinMain()
? {……
? AfxGetApp->InitInstance( );
? ……
? AfxGetApp->Run( );
? }
预设函数与反向调用在 MFC中的具体实现
? Run( )利用 for循环反复接受
? Windows传来的消息并送给 CFrameWnd类
对象。问题:本例中没有为 CFrameWnd类
派生对象。
? InitInstance( )也是一个预设函数,内容为:
? Virtual InitInstance( ){ return TRUE;},主要作要是作为一个虚拟
函数,留待用户在应用程序中定义新程序来重载他。
? AfxGetApp->InitInstance( )实际上调用了 App的 InitInstance( ),
若在 App中没有定义,实际上就调用了 CWinApp中的 InitInstance( )。
? Run( )也是框架中预设的一个虚拟函数,要求用户重新定义函数体。
Run( )预设的函数体为:
? Run()
? {……
? For(;;)
? {
? ……
? if(!PumpMessage()) break;
? }
? }
预设函数与反向调用在 MFC中的具体实现
? 解决办法:定义一个新的 InitInstance( )来取代预设函数。
? Class Ywindow:public CFrameWnd
? {
? Public:
? Ywindow( );
? };
? Class YApp:public CwinApp
? {
? virtual BOOL InitInstance( )
? { m_pMainWnd=new Ywindow;
? m_pMainWnd->ShowWindow(m_nCmdShow);
? return 1;
? }
? Yapp App;
预设函数与反向调用在 MFC中的具体实现
WinMain( )
………………………………………………………
…
InitInstance( )
……
Run( )
CWinApp类
……
InitInstance( )
YApp类
框架中
程序中
反向调用
M_pMainWnd
指针 Ywindow类的对象
YApp类的对象 App
应用框架的发展
1,Smalltalk-80语言中的 MVC框架
2,Macintosh推出的 MacApp框架
3,Borland OWL框架
4,Microsoft Visual C++中的 MFC框架
5,JAVA J2EE规范
6,Microsoft,net构架
…………
面向对象程序设计方法--基本原则
1,开闭原则 (OCP)
2,里氏代换原则( LSP)
3,依赖倒转原则( DIP)
4,迪米特法则( LoD)
5,单责任原则( SRP)
开闭原则 (OCP)
开闭原则是面向对象程序设计的第一个主要的原
则原则,这个原则最早由 Bertrand Meyer提出:
,Software entities should be open for
extension,but closed for modification”,即一个
软件实体应该对扩展开放,对修改关闭。也就是说,
当一个软件需要增加或者修改某些功能时候,应该尽
可能的只是在原来的实体中增加代码,而不是修改代
码。开闭原则保证了系统具有一定的稳定性,同时保
证了系统的灵活性。开闭原则的另外一个叫法称为
,对可变性的封装原则, 。
开闭原则 (OCP)
实现开闭原则的最好办法时为软件提供一个抽象
层,用户通过抽象层的接口访问软件实体,该抽象层
根据用户请求的具体内容决定使用软件实体中的那些
具体功,而从用户角度只能看到抽象层,并且以为是
抽象层在提供服务。当软件实体需要增加新的功能是,
只需要在软件中增加新的实体,而增加后客服端代码
和抽象层不需改变或只需很小的改变。
里氏代换原则( LSP)
里氏代换原则最早由 Barbara Liskov提出:, 基类出
现的地方,子类一定可以出现, 。这是显而易见的,但
里氏原则反映了分类层次的一些本质属性 。问题是:
是不是有基类的地方,子类就一定可以出现?
例如:
正方形是否是长方形的子类?
里氏代换原则( LSP)
长方形类
public class Rectangle
{
long width;
long height;
public void setWidth(long
width){
this.width = width;
}
public long getWidth(){
return width;
}
public void setHeight(long
height){
this.height = height;
}
public long getHeight(){
return height;
}
}
正方形类
public class Square extends
Rectangle( JAVA)
{
public void setWidth(long
width){
this.width = width;
this.height = width;
}
public long getWidth(){
return width;
}
public void setHeight(long
height){
this.width = height;
this.height = height;
}
public long getHeight(){
return height;
}
}
上例中, 将正方形作为长方形的扩展 ( 子类 ) 来实现,
但出现下列代码时:
public void resize(Rectangle r){
while(r.getHeight()>=r.getWidth())
r.setWidth(r.getWidth()+1);
}
输入是一个合乎条件的长方形 ( 高度 <=宽度 ) 时, 是
没有问题的, 会使长方形的形状变得如何?
问题是:如果输入的是一个正方形, 会出现什么情况?
里氏代换原则( LSP)
问题是:如果输入的是一个正方形, 会出现什么情况?
会使正方形不停地变大直到溢出 。
原因:设计是违反了里氏代换原则, 解决方法:为正方
形和长方形设计一个共同的基类, 如 Shape类, 使正方
形不是长方形的子类 。
里氏代换原则( LSP)
依赖倒转原则( DIP)
依赖倒转原则要求用户尽量依赖于抽象而不是 ( 具体 ) 实现 。
传统的过程式的程序设计方法倾向于使高层次的模块依赖于低
层次的模块,依赖倒转原则把这个依赖关系倒转过来。一般而言,
抽象层次包含的是应用系统的常规逻辑和宏观的控制过程,而低层
次模块包含一些具体的算法。低层次代码经常会变动,高层次则相
对稳定一些。为了更有效的保持系统的稳定性,应该使得低层次的
模块依赖于高层次模块。从软件复用的角度来看,只有实现依赖倒
转原则,才会避免当低层次模块发生改变的时候不会导致高层次模
块的修改。
迪米特法则( LOD)
迪米特法则又称为最少知识法则,其含义是指一个对象应当尽可能少地了
解其它对象。这种思想是和模块化程序设计中的模块低耦合高内聚同样的道理。
public class Someone{
public void operation1(Friend frieng){
Stranger stranger = friend.provide();
Stranger.operation3();
}
public class Friend{
private Stranger stranger = new Stranger();
public void operation2(){}
public Stranger provide(){
return stranger;
}
}
//Someone中的函数 operation1引
//用了 frieng和 stranger两个对象,
//不符合迪米特法则。思考题:请
//修改上述代码,使之符合迪米特
单责任原则 (SRP)
一个类应该有且仅有一个职责。关于职责的含意,
面向对象大师 Robert.C Martin有一个著名的定义:所谓
一个类的职责是指引起该类变化的原因,如果一个类具
有一个以上的职责,那么就会有多个不同的原因引起该
类变化,其实就是耦合了多个互不相关的职责,就会降
低这个类的内聚性。
需要说明的是:上述五个原则是面向对象程序设计
的一般指导性原则,违背这些原则有可能会使程序的结
构、弹性、可维性、规范化变差,甚至出错,但没有遵
循这些原则与程序出错或语法错误没有必然的联系。
设计模式( Design Patterns)
? 模式的来源
? 模式的定义
? 模式的要素
? 模式的种类
? 模式的前景
设计模式的起源
? 设计模式产生于建筑学和人类学的疑问:, 质量可
以客观评价吗?,
? Alexander 发现,通过这样的方式 — — 观察解决
相似问题的不同解决方案 — — 缩小他的关注焦点,
他可以洞悉优质设计之间的相似之处。他把这些相
似之处称为 模式 。
? 模式是, 在某一个情景下的问题解决方案,
模式的发展
? 1987年。 Ward Cunningham和 Kent Beck在一起用 Smalltalk作设计用户界面的工
作。他们决定使用 Alexander的理论发展出一个有五个模式的语言来指导 Smalltalk
的新手,因此他们写成了一篇 "Using Pattern Languages for Object-Oriented
Programs( 使用模式语言作面向对象的程序)的论文(发表于 OOPSLA'87 in
Orlando )。
? Jim Coplien开始搜集 C++语言的成例 (idioms)。 成例是模式的一种;这些 C++成例
发表在 1991年出版的 Advanced C++ Programming Styles and Idioms( 高级 C++
编程风格和成例)一书中
? 从 1990到 1992年,由四个人组成的小组 开始他们搜集模式的工作。关于模式的讨
论和工作会议则一再举行。
? 在 1993年 8月份,Kent Beck和 Grady Booch主持了一个在科罗拉多的山区度假村
召开的第一次关于模式的会议。模式研究的主要人物都参加了这次会议,包括:
Jim Coplien,Doug Lea,Desmond D'Souze,Norm Kerth,Wolfgang Pree,等。
?, 1995年, Design Patterns,一书发表。
? 编程模式语言大会( Pattern Languages of Programming,或者 PLoP) 每年一次
定期在美国举行,大会的论文也汇编成书,公开发表 [PLOP95],[PLOP96]、
[PLOP98],[PLOP99]。
组成一个模式的要素
? 模式的名称( Pattern name)
? 问题 (problem)
? 解决方案 (solution)
? 效果 (consequences)
模式的种类
目的
创建型 结构型 行为型
范围 类 Factory Method Adapter Interpreter
Template Method
对象 Abstract Factory
Builder
Prototype
Singleton
Adapter
Bridge
Composite
Decorator
Fa?ade
Flyweight
Proxy
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor
模式的前景
? 模式的应用前景
? 一套通用的设计词汇
? 书写文档和学习的辅助手段
? 简化设计的手段
? 实现重构目标
? 模式的研究前景
? 模式的形式化定义
? 设计模式的支持工具
? 自动代码生成
框架、设计模式、组件
? 组件通常是代码重用,而设计模式是设
计重用,框架则介于两者之间,部分代
码重用,部分设计重用,有时分析也可
重用。
? 框架比模式更具体;模式比框架粒度小。
? 如果说框架是软件,则模式是软件的知
识
? 框架模式 ;设计模式 ;代码模式