2 对象模型
对象模型包括:抽象、封装、模块化、层次结构、类型、并发和持久
2.1 对象模型的演进
OO建立在以前技术的最佳思想之上。两大趋势:小规模→大规模;高级程序设计语言;
2.1.1 程序设计语言的换代
第一代(1954~1985):科学和工程应用,公式计算FORTRANⅠ: 数学表达式
第二代(1959~1961):算法抽象
FORTRANⅡ:子程序、单独编译
ALGOL60:块结构、数据类型
COBOL:数据描述、文件处理
Lisp:列表处理、指针、垃圾收集,基于λ演算的函数式编程语言,在CAD软件上应用广泛(AUTOCAD二次开发用),括号+语法树;程序看作数据;
第三代(1962~1970):数据抽象
Pascal:ALGOL60的简单继承者
Simula:类、数据抽象
代沟(1970~1980)
C:高效、可执行程序小
FORTRAN77:ANSI标准
OO兴盛(1980~1990)
Smalltalk:纯OO,在Smalltalk中所有的东西都是对象,或者应该被当作对象处理。例如表达式:2 + 3,应当被理解为:向对象2发送消息+,参数为对象3。 对Objective-C、Java 、Ruby等有推动;开发思想:模式、XP、重构等
C++:从C和Simula发展而来
FrameWork的出现(1990~现在)
VB,Java,Python,J2EE,.NET,C#,VB.NET
2.1.2 第一代和第二代早期程序设计语言的拓扑结构
基本物理构成单元:子程序(对COBOL而言,称之为段落)
物理结构 = 全局数据 + 子程序
2.1.3 第二代后期和第三代早期程序设计语言的结构
子程序作为一种抽象机制
→ 开始发明支持参数传递机制的语言
→ 奠定了结构化设计的基础
在语言上支持嵌套子程序,并在控制结构和声明的可见性方面发展
→ 出现了结构化设计方法,利用子程序作为基本构建块
2.1.4 第三代后期程序设计语言的结构
大规模编译 → 独立开发同一程序的不同部分 → 独立编译的模块
此阶段,模块只是用于最有可能同时改变的子程序分组,非抽象机制
虽支持模块化,但鲜有规则要求模块间接口的语义一致性;
对数据抽象和强类型支持得不好,错误只有在运行期才能被检测出来。
2.1.5 基于对象和面向对象的程序设计语言的结构
中小型应用 大型应用
构建块是模块,表现为逻辑上的一组类或对象,而非子程序。
过程、函数是动词,数据是名词 → 面向过程(procedure-oriented)围绕动词,OO围绕名词
中小型OO应用的物理结构表现为一个图,而非树;几乎无全局数据;数据和操作在单元中
对巨型编程,对象模型在规模上扩展;一组抽象可构建在另一组抽象层智商;一组抽象协作,实现更高层行为;即第一章中描述的复杂性的组成方式;
2.2 对象模型的基础
对象模型是普适的(UI/DB/computer architectures);
算法分解处理大量复杂问题时能力有限;
C++/JAVA作为面向算法使用会丧失威力;
何为对象:存在一些不变的特性,来刻画一个对象和它的行为;
2.2.1 面向对象编程
OOP的要点:
- 对象最为基本逻辑构建块;(组成层次结构)
- 每个对象都是类的一个实例;
- 类之间通过继承关联;(是一个层次结构)
支持OOP语言的需求:
- 支持对象,这些对象具有命名的操作结构和隐藏的内部状态的数据抽象
(It supports objects that are dataabstractions with an interface of named operations and a hidden local state.) - 对象有相关的类型[类]
- 类型[类]可以从超类型(supertypes)[超类(superclasses)]中继承属性
2.2.2 面向对象设计
两个要点:
- OOAD导致OO分解;(类和对象抽象VS结构化设计的算法抽象)
- 使用不同表示法来表达系统逻辑特征(类和对象结构)和物理设计(模块和处理架构)的不同模型,以及系统的静态和动态特征
2.2.3 面向对象分析
结构化分析关注系统中的数据流;OOA重点在构建真是世界的模型,开发更早阶段就考虑。
三方面的关系:OOA的结果作为开始OOD的模型,OOD的结果作为蓝图,利用OOP最终实现一个系统。
2.3 对象模型的要素
编程风格(programming style):一种组织程序的方式,基于某种编程概念模型和一种适合的语言,其目的是使得用这种风格编写的程序很清晰(away of organizing programs on the basis of some conceptual model of programming and an appropriate language to make programs written in the style clear)
- 面向过程(Procedure)算法;适用于计算密集型
- 面向对象(Object) 类和对象;应用范围广,常作为架构框架
- 面向逻辑(Logic) 目标,通常以谓词演算(predicate calculus)的方式表示;λ表达式,如Lisp,Prolog
- 面向规则(Rule)如果-那么(If–then)规则;适用于知识库
- 面向约束(Constraint)不变的关系(Invariant relationships)
对象模型的4个主要要素:抽象、封装、模块化、层次结构
3个次要要素:类型、并发、持久
2.3.1 抽象的意义
抽象描述了一个对象的基本特征,可以讲这个对象与所有其他类型的对象区分开来,因此提供了清晰定义的概念边界,它与观察者的视角有关。
抽象壁垒(abstraction barrier):分离对象的基本行为和它的实现
最少承诺(least commitment):对象的接口只提供它的基本行为
最少惊奇(least astonishment):抽象捕捉某对象的全部(entire)行为,不多不少,不提供抽象之外的惊奇效果或副作用
- 实体抽象问题域或解决方案域实体的一个有用的模型
- 动作抽象提供一组通用的操作,所有这些操作都有同类的功能
- 虚拟机抽象集中了某种高层控制要用到的所有操作,或这些操作将利用更低层的操作集
- 偶然抽象封装了一些相互间没有关系的操作
从客户端角度,集中关注对象的外部试图
→ 编程契约模型(contract model of programming)
→ 外部视图定义了契约,其他对象依赖该契约,该对象通过内部视图来实现契约。契约包含了对象的责任,即它的可靠的行为
协议:可调用的操作集 + 合法的调用顺序;→ 构成了完整的静态和动态外部视图;
抽象思想的核心:不变性;前置条件(客户的责任) 后置条件(服务器的责任)
C++:成员函数 = Smalltalk:方法(method) = Ada:操作(operation) C++:数据成员 = Smalltalk:实例变量(instance variable) = Java/Object Pascal:字段/域(filed) |
2.3.2 封装的意义
对象的抽象优先于实现决定;
信息隐藏:对象的结构和方法的实现都是隐藏的;
封装 → 不同抽象间提供了明确的边界 → 分离关注
类的两部门:接口(描述外部视图,共同行为的抽象)+实现(抽象的表示,实现行为的机制)
封装是一个过程,它分隔构成抽象的结构和行为的元素。封装的作用是分离抽象的概念接口及其实现。
2.3.3 模块化的意义
在程序内部创造一些定义良好的、有文档描述的边界(接口)
Smalltalk:无模块概念,类就组成了分解的唯一物理单元
Java:包;
C++:模块为独立的语言结构
将逻辑上相关的类和对象放在同一个模块中。随意模块化比不实现模块化更糟;
分解为模块的目标是:允许模块独立设计和修改,从而减少软件的成本。
每个模块应足够简单:能被完全理解;在不知道其他模块的实现,并不影响其他模块行为的情况下,修改其实现。
模块编译成本小,编译模块接口成本高 → 接口尽量小
模块化是一个系统的属性,这个系统被分解为一组高内聚、低耦合的模块。
2.3.4 层次结构的意义
层次结构是抽象的一种分级或排序。
- 单继承:
类的两种客户:对象(调用类的实例方法),子类(继承该类)
封装被打破的三种方式:
子类可能访问其超类的实例变量;可调用其超类的私有操作;直接引用其超类的超类;
- 多继承:
混入类的概念?
多重继承的问题:名字冲突、重复继承
名字冲突:不同超类具有同样名字的属性或操作
class A {
public:
A() { mVal = 1; }
void F() {cout<<"A::F()"<<endl; }
int mVal;
};
class B {
public:
B() { mVal = 2; }
void F() {cout<<"B::F()"<<endl; }
int mVal;
};
class C : public A, publicB {
};
void main()
{
C test;
test.A::F();
test.B::F();
cout<<test.A::mVal<<" "<<test.B::mVal<<endl;
}重复继承:钻石型继承
class A{
public:
A (intx) : m_x(x) {}
void F() {cout<<"A::F"<<endl; }
int m_x;
};
class B : public A {
public:
B (intx) :A(x) {}
void set(intx) {m_x =x; }
};
class C : public A {
public:
C (intx) :A(x) {}
int get(void) {returnm_x; }
};
class D : public B, publicC {
public:
D (intx) :B(x),C(x) {}
};
void main(void) {
D d(10);
d.set(20);
cout << d.get() << endl;
d.F();//<-error C2385: 对“F”的访问不明确
}在构建d对象时,里面存在两个A类基类子对象,尽管成员函数不存放在类中而在代码段,并且只会有一份,但是编译器不知道,他会作为两个继承函数来处理,用d.F()来访问时,编译器便不知道访问的是哪一个基类子对象里的F() 解决方案:
class B : virtual public A//虚继承
class C : virtual public A//虚继承
D(int x) : B(x),C(x),A(x) {}在这个过程中,A对象只在D的初始化表中A(x)进行构造(虚基类最先被构造),而在B和C的初始化表中不再对A进行构造(实际上是都有一个指针指向了D中的A(x),来对A进行构造)
- 聚合:
聚合允许对逻辑结构进行物理分组;而继承允许这些共同的部分在不同的抽象中被复用。
两种关系状态:
植物园与植物(植物可独立存在,生命周期无关)
植物园与培育计划(计划不可独立存在)
2.3.5 类型的意义
类型是关于一个对象的类的强制规定,不同类型的对象不能够互换使用,或至少它们的互换使用受到非常严格的限制。
利用多态可缓解运行时类型识别的需要。
【类型一致性】:强类型、弱类型、无类型
强/弱类型是指类型检查的严格程度的。语言有无类型,弱类型和强类型三种。无类型的不检查,甚至不区分指令和数据。弱类型的检查很弱,仅能严格的区分指令和数据。强类型的则严格的在编译期进行检查。
强类型语言:在没有强制类型转化前,不允许两种不同类型的变量相互操作。
强类型的好处:更安全、早期错误检查、增强可读性、编译器生成的目标代码的效率更佳
强类型往往成为类型安全的同义词,然而,类型安全与动态类型并不互相排斥。
【名字与类型绑定的时间】:静态类型(编译时固定)、动态类型(延迟绑定,运行时)
- Ada:强类型 + 静态类型
- C++: 强类型 +支持动态类型
- Java:强类型 + 支持动态类型
- Smalltalk:无类型 + 动态类型
- Perl、PHP:弱类型 + 动态类型
- Python:强类型 + 动态类型
一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
某些静态语言有一个“后门”,在这些编程语言中,能够编写一些不被静态类型所检查的代码。例如,Java和C-风格的语言有“转型”可用。在静态类型的编程语言中,不必然意味着缺乏动态类型机制。例如 Java 使用静态类型,但某些运算需要支持运行时期的类型测试,这就是动态类型的一种形式。
C++是一种使用非常广泛的电脑程序设计语言。它是一种静态数据类型检查的,支持多范型的通用程序设计语言。C++支持过程化程序设计、数据抽象化、面向对象程序设计、泛型程序设计、基于原则设计等多种程序设计风格。
C++ 的多态又分成静态多态(Static Polymorphism)与动态多态(DynamicPolymorphism)。动态多态必须结合继承和动态绑定(Dynamic Binding)方式实现。静态多态是指以模板(template)实现多态的方法,也就是参数化的型态(Parameterized Types),属于编译前(pre-compile)的多态,是使用宏(marco)的“代码膨胀法”达到多态效果。类型转换(type cast)也是一种区域(ad hoc)多态的概念,C++提供 dynamic_cast,static_cast等运算对象来实现类型转换(Coercion)。运算对象重载(operatoroverloading)或函数重载(function overloading)也算是多态的概念。
C++是部分动态的,是编译时静态绑定,通过嵌入类(多重继承)和虚函数(虚表)来模拟实现。
JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;生成动态代理。
2.3.6 并发的意义
并发三级别:语言原生支持、使用类库、利用中断实现假象
----------------------------------------------------------
【并发concurrent VS 并行parallelism】
并发性是指两个或多个事件在同一时间段内发生
并行性是指两个或多个事件在同一时刻发生
并发性,又称共行性,是指能处理多个同时性活动的能力;
并行,是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行,也亦是说并发事件之间不一定要同一时刻发生。(如在同一个cpu上同时运行多个程序,不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)
线程机制支持并发程序设计,在多处理器系统上,它也能保证真正的并行处理
2.3.7 持久的意义
持久是对象的属性,它使得对象可以跨越时空存在。
持久的谱系:
表达式计算的瞬时结果
局部变量
自由变量、全局变量、堆中的值
------------以上为传统编程语言关注的持久----------------
程序执行之间存在的数据
程序的不同版本之间存在的数据
比程序生命期长的数据
------------以上通常为数据库技术关注的领域----------------
持久层(Persistence)
只是一个逻辑概念,即在系统逻辑层面上,专著于实现数据持久化的一个相对独立领域。负责向(或者从)一个或者多个数据存储器中存储/获取数据的一组类和组件。
数据持久化
一个动作,将内存中的数据模型转换为存储模型,就是把数据长期保存起来;以及,将存储模型转换为内存中的数据模型的统称.
数据模型
可以是任何数据结构或对象模型,存储模型可以是关系模型、XML、二进制流等。
表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)
DAO(DataAccess Object) 封装了访问数据库对象的接口,相当于一个通道,它可能会使用JDBC、Hinernate、iBatisa等工具来实现。而DAL 是指 Data Access Layer,是把所有和数据库操作(类似Insert、update、select)的业务逻辑封装,是一个逻辑上的概念,是为了设计规范、系统扩展方便的需要
Hibernate框架
是一个开放源代码的对象关系映射框架,它对JDBC(Java Data Base Connectivity,java数据库连接)进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。对象上数据的修改,Hibernate框架会把这种修改同步到数据库中。
附表:
对象模型元素 | 特性 | SmallTalk | C++ | JAVA |
抽象(Abstraction) | 实例变量 | 是 | 是 | 是 |
实例方法 | 是 | 是 | 是 | |
类变量 | 是 | 是 | 是 | |
类方法 | 是 | 是 | 是 | |
封装(Encapsulation) | 变量的 | 私有 | 公保私 | 公保私包 |
方法的 | 公开 | 公保私 | 公保私包 | |
模块化(Modularity) | 模块的种类 | 无 | 文件 | 文件 |
层次(Hierarchy) | 继承 | 单 | 多 | 单 |
泛型单元 | 否 | 是 | 是 | |
元类(Metaclasses) | 是 | 否 | 否 | |
类型化(Typing) | 强类型化 | 否 | 是 | 否 |
多态(Polymorphism) | 是(单) | 是(单) | 是(单) | |
并发(Concurrency) | 多任务(Multitasking) | 间接(通过类) | 间接(通过类) | 是 |
持久化(Persistence) | 持久对象 | 否 | 否 | 否 |
2.4 应用对象模型
2.4.1 对象模型的好处
发挥基于对象和面向对象编程语言的能力
有利于软件复用,设计复用
有利于系统演化式发展,而非抛弃型重做
集成散布在生命周期的各个时刻
分离关注减少了开发风险
2.4.2 开放式问题
什么是类和对象? 第3章 类与对象
如何正确确定类和对象?第4章 分类
怎样的表示法适合OOD?第5章 表示法
怎样的过程可以得到结构良好的OO系统?第6章 过程
使用OOD对管理层意味着什么?第7章 实战