第 5 章 面向对象的设计

Post on 15-Mar-2016

107 views 11 download

description

Object Oriented Design. 第 5 章 面向对象的设计. 面向分析. 面向设计. 做什么 ? 需求 领域的调查. 如何做 ? 确定逻辑的解决方案. Object-Oriented Design. 内容 5.1 面向对象设计概述 5.2 软件设计的体系结构 5.3 面向对象设计的软件体系结构 5.4 对象设计 5.5 数据管理的设计 5.6 人 - 机交互的设计 5.7 任务管理的设计. 5.1.3 面向对象的设计方法. 5.1 面向对象设计概述. 5.1.1 面向对象设计内容. - PowerPoint PPT Presentation

Transcript of 第 5 章 面向对象的设计

1

第 5 章 面向对象的设计面向分析 面向设计做什么 ?需求领域的调查

如何做 ?确定逻辑的解决方案

Object Oriented Design

2

内容5.1 面向对象设计概述5.2 软件设计的体系结构5.3 面向对象设计的软件体系结构5.4 对象设计5.5 数据管理的设计5.6 人 - 机交互的设计5.7 任务管理的设计

Object-Oriented Design

3

5.1 面向对象设计概述5.1.1 面向对象设计内容5.1.2 面向对象分析与设计的制品5.1.3 面向对象的设计方法

4

5.1 面向对象设计概述在已建立概念类图 (对象分析模型 )的基础上, 进一步优化类图 ,确定实现的逻辑模型。

面向对象的设计包括 : 体系结构的设计 对象的设计 数据管理的设计 人 -机交互的设计 任务管理的设计

5.1.1 面向对象设计内容

5

分析阶段的制品 要回答的问题 用例、活动图 对象 ( 概念 ) 模型 顺序图等 功能模型

领域过程是什么 领域中的概念和术语是什么 系统事件和操作是什么 系统操作做了什么

设计阶段的制品 要回答的问题 协作图 状态图 设计类图

对象间的通讯细节 设计软件实现的类图

5.1.2 面向对象分析与设计的制品

6

public class Dialer{ private Vector digits; int nDigits; public void digit(int:n); protected boolean recordDigit(int n);}

类名属性 /成员变量操作 /成员函数

图 5-2 设计的类图

Dialer 拨号器 - digits:Vector - nDigits:int

+ digit(n:int) # recordDigit ( n:int):boolean

Dialer 拨号器 digits nDigits

概念记号一个 Dialer 代表了一次拨号的事件 ,它有 digits概念的内涵

Dialer1Dialer 2Dialer3

Dialer 4

概念的外延

概念应用的一组实例

软件类 ,不是概念的一部分分析 设计

图 5-1 分析的类图

实体实体信息实体职责

7

5.1.3 面向对象的设计方法问题域部分数据管理部分 人机交互部分 任务管理部分

(1) Coad & yourdon 方法 (COA91)

(2) Rumbaugh 方法 (RAM91)系统设计 (System Design)对象设计 (Object Design)

8

就一个类而言 ,应该仅有一个引起它变化的原因 . 职责 :“变化的原因” (a reason for change) (1) 分离类的职责 若一个类承担的职责过多 ,就等于把 这些职责耦合在一起 . 这种耦合会导致脆弱的 (fragile) 设计 当变化发生时 ,设计会遭到破坏 .如图 :

1) 单一职责原则 (SRP,Single-Responsibility Principle)

5.1.4 设计上的几个原则

9

图 5-3 多于一个的职责

ComputationaGeometryApplicationRectangle+draw()+area():double

GraphicalApplication

GUI

ComputationaGeometryApplicationGeometricRectangle+area():double

GraphicalApplication

GUIRectangle+draw()

图 5-4 分离的职责

计算和绘图在一起 .计算包含 GUI 代码 ,C++ 把 GUI 代码链接起来 ,Java GUI 的 Class 文件必须被部署到目标平台上 .

计算几何形状 绘图

10

(2) 分离接口中的职责 例 违反单一职责的程序 : inteface Modem { // 调制解调器的连接处理 public void dial ( string pno); public void hangup (); // 发送、接收函数的数据通信 public void send ( char c); public void recv (); } 该接口声明的 4个函数都是调制解调器具有的功能 .有问题 ?

11

调制解调器的连接处理 两个函数的数据通信 . 要不要分离 ?该接口程序有两个职责 :

若程序变化导致两个职责同时变化,就不要 分离.

若程序变化影响连接函数签名(signature), 调用和类要重新编译,增加了部署的次数, 这两个职责应分离.

变化实际发生了才有意义 , 若无征兆 ,去应 用单一职责 ,是不明智的 .

12

分离调制解调器 Modem的接口<<interface>>Data Channel+send(:char)+recv():char

<<interface>>Connection+dial(pno:String)+hangup()Modem

Implementation图 5-5 分离的 Modem 接口

ModemImplementation 类耦合了两个职责 ,这不是所 希望的 ,但 , 如硬件等原因 ,这种耦合可能是必要的 . 对于应用的其余部分 ,通过对接口的分离己经解耦了概念。 可以把 ModemImplementation 类看成是一个杂凑物 , 谁也不依赖它 ,除了 Main 外 ,谁也不知道它的存在 .

13

又如 ,被耦合在一起的持久化职责

PersistenceSubsystem

图 5-6 被耦合在一起的持久化职责

Employee +CalculatePay+Store

违反单一职责。为什么 ?Employee 类包含了业务规则和对于持久化的控制 .业务规则会多变,持久化的方式不会如此变化,且变化原因也不一样,不能将其放在一起。

14

思考怎样分离如下类的职责 Employee

+calculatePay 计算薪水 +calculateTaxes 计算税金 +writeToDisk 在磁盘上续写自己 +readFromDisk +createXML 进行 XML格式相互转换 +parseXML +displayOnEmployeeReport 显示各种报告 +displayOnPayrollReport +displayOnTaxReport

15

public class Employee { public double calculatePay(); public double calculateTaxes(); public void writeToDisk(); public void readFromDisk(); public string createXML(); public void parseXML(string xml); public void displayOnEmployeeReport( printStream stream); public void displayOnPayrollReport( printStream stream); public void displayOnTaxReport( printStream stream);}

16

一个可行的结构

图 5-7 业务的隔离类图

Employee+ calculatePay+ calculateTaxes

Employee XMLConverter+EmployeeTo XML+XMLToEmployee

Employee Database

+writeEmployee+readEmployeeTaxReport

EmployeeReportPayrollReport

17

2) 开放—封闭原则 ( OCP,The Open-Closed Principle )

典故 :一个被分割的两删门 ,每一部分都可 以独立的开放或封闭 . 遵循OCP原则设计的模块具有两个主要特征 :

怎样的设计 ,才能面对需求的变化只是添加 新的代码 ,保持相对稳定 ,不改动正在运行的程序 . 软件实体 (类、模块、函数等 )应该是可以扩 展的 ,但是不可修改 .

18

对于扩展是开放的 (Open for extension) 表示模块的行为是可扩展的 ,即当需求 变更时 ,可以改变模块的功能 . 对于更改是封闭的 (Closed for modification) 对模块进行扩展时,不必改动模块的 源代码。 以上两点出现了矛盾

19

关健是抽象 c++,Java 等 OOPL语言具有抽象类 ,其任意行为 可能出现在派生类中。Client Server

图 5-8 违背 OCP 原则Client

Server

<<interface>>Client Interface

图 5-9 STRATEGY 模式 : 开放封闭的 Client

Client 和 Server 类都是具体类 , Client 类使用 Server 类 .若 Client 类使用另一个 Server类 ,就要改动 Client 类使用Server 类的地方。

抽象接口命名为 ClientInterface 而不命名为 Abstract Server? 因为抽象类和它们的客户 Client关系要比实现它们的类关系更密切。

20

例 :一个数据库门面 EmployeeDB处理对象

+readEmployee+writeEmployeeEmployee

EmployeeDB 《 API 》TheDatabase

图 5-10 违背 OCP 原则 门面直接处理数据库的 API

修改 EmployeeDB类 ,必须重新编译 Employee 类 ,Employee 和数据库 ApI捆绑在一起。

21

+readEmployee+writeEmployeeEmployee

《 interface 》 EmployeeDB

《 API 》TheDatabase

图 5-11 遵守 OCP 原则

EmployeeDatabaseImplementation

UnitTestDatabase

把 GUI 管理和数据库操纵分开

22

违反 OCP 实例 Circle/Square 问题 shape.henum ShapeType { circle,square };Struct Shape{ ShapeType itsType;}circle.hStruct Circle{ ShapeType itsType; double itsRadius( 半径 ); point itsCenter( 中心点 );};

例 :Shape( 形状 ) 应用程序 . 在标准 GUI 上按照特定顺序绘制园和正方形 . 创建一个列表 ,列表由按适当顺序排列的园和正方 形组成 , 程序遍历该列表 ,依次绘制每个园和正方形 .

23

square.hStruct Square{ ShapeType itsType; double itsSide; ( 边 ) point itsTopLeft; ( 左边的顶点 )};drawAllShapes.ccTypedef struct Shape *ShapePointer;Void DrawAllShapes (ShapePointer list,int n)

DrawAllShapes 函数不符合 OCP, 它对于新的形状类型添加是不封闭的 . 每增加添加一个新的形状类型 , 就改变了这个函数 , 要对新类型判断 .

24

{ int i; for (i=0; i<n; i++) { struct Shape* s=list[i]; switch (s->itsType); { case square: DrawSquare ((struct Square*)s); break; case circle: DrawCircle ((struct Circle*)s); break; } } } 要想在另一个程序中复用 DrawAllShapes 函数都要带上 Square,Circle. 方法是牢固的 ,也很槽糕 .

在应用中 switch 函数重复出现 ,但完成工作略有差异 .不同形状依赖于 enum声明 .增加一个新成员都要重新编译、部署 (DLL、共享库、二进制组件 )一个简单行为导致连锁改动 ,是僵化的 .

25

遵循 OCP 规则 : 定义一个抽象类 Shape 及抽象方法 Draw. Circle 和 Square 都从 Shape 类派生 . class Shape { public: virtual void draw() const = 0; }; class Square: public Shape { public: virtual void draw() const; };

26

class Circle: public Shape{ public: virtual void draw () const;};// 不需要改动 DrawAllShapes 方法 ,增加 Shape 类的派生类 ,// 扩展其行为 .Void DrawAllShapes (vector<Shape*>& list){ vector<Shape*>::interator i; for (i=list.begin () ; i != list.end(); i++) (*i)->Draw();}

27

模块可以操作一个抽象体 .由于模块依赖于一 个固定的抽象体 ,所以它对于更改可以是封闭的 . 同时 ,通过从这个抽象体派生 ,也可以扩展此 模块的行为 .

上系统强调一个形状的顺序比强调类型更重要 ,很难做到严格封闭 . 一般而言 ,无论模块是多么的封闭 , 都会存在无法对所有情况都适用的模型 . 一般做法 :找出一个模块易变化的部分 , 构造抽象类 ,隔离其变化 .

28

OCP(开放 -封闭 ) 是面向对象的核心 ,遵守这个原则会使设计具有灵活性、可重用性、可维护性 . 但是 ,并不意味着对应用程序的每个部分都 要进行抽象 . 正确的做法是 ,开发人员应该仅仅对程序中 呈现出频繁变化的那些部分做出抽象 . 拒绝不成熟的抽象和 抽象本身一样重要 .

29

DIP 原则 高层模块不应依赖低层模块 ,二者都应依赖 于抽象 抽象不应依赖于细节 ,细节应该依赖于抽象 .

好处 : 底层模块改动不会影响到高层模块 增加各层模块的独立性

3) 依赖倒置原则 (DIP,Dependency-Inversion Principles)

30

层次化

Policy Layer

Mechanism Layer

Utility Layer

图 5-12 层次化方案这种层次化 ( 政策 - 机制 - 效用 ) 方案高层依赖于底层 , 这种依赖关系是传递的 .

31

Policy Layer<<interface>>Policy Service

Interface

<<interface>>Policy Service

InterfaceMechanism

Layer

UtilityLayer

图 5-13 政策 机制 效用 倒置的层次化方案

32

上图中每个较高层次都为它的服务声明一个 抽象接口 ,较低层次实现抽象接口

高层类通过抽象接口使用下一层 ,高层不依赖 于低层 ,低层依赖于在高层声明中的抽象接口 . 倒置不仅是依赖关系的倒置 ,也是接口所有权 的倒置 . 依赖倒置可以应用于任何存在于一个类向另一 个类发送消息的地方 .

33

Button .Java 代码Public class Button{ private Lamp itsLamp; public void poll () { if ( /* some condition */ ) itsLamp.turnOn(); }}

Button+Poll()

Lamp+TurnOn()+TurnOff()图 5-14 不成熟的 Button 和 Lamp 模型

高层策略依赖于低层模块 .抽象依赖于具体细节 .

Button 对象感知外部环境变化 .收到 poll消息 ,判断是否被用户按下。

依赖于抽象Lamp 对象会影响外部环境 .

又如 ,Button 对象控制 Lamp 对象的一个模型

34

找出潜在的抽象Button+poll()

<<interface>>ButtonServer+turnOff()+turnOn()

Lamp 图 5-15 对 Lamp 应用 依赖倒置原则

# 程序中所有依赖关系都应 该终止于抽象类或接口。# 任何类都不应当从具体类中派生。# 任何方法都不应该覆写它的任何 基类中己经实现了的方法。# 若一个具体类 (如描述字符串的类 ) 是稳定的 ,也不会创建其他类似的派生类 ,直接依赖它不会造成损害。

接口没有所有者 ,可以被许多不同的客户、服户者使用。这样接口需要放一个单独的组 (group) 中。 C++ 中把接口放在一单独的 namespace 和库中。在 Java 中把接口放在一单独的 package 中

启 发

35

熔炉示例一个控制 (Regulate)熔炉调节器软件 .从 IO通道中读取当前的温度 ,并通过向另一个通道发送命令来指示熔炉的开或关。#define TERMOMETER 0x86 // 炉子两个通道#define FURNACE 0x87#define ENGAGE 1 // 启动#define DISENGAGE 0 // 停止Void Regulate(double minTemp,double maxTemp){ for (;;) { while (in(TERMOMETER) > minTemp) wait(1); out (FURNACE, ENGAGE); while (in(TERMOMETER) < maxTemp) wait(1); out (FURNACE, DISENGAGE); }}

代码表示了底层细节 ,不能重用

36

倒置这种关系 :《 function 》

Regulate

IO ChannelThermometer

《 interface》Thermometer

+read()

《 interface》Heater

+engage() +disengage()

IO ChannelHeater

图 5-16 通用的调节器

调节器函数 Regulate接受两个接口参数 ,温度计 (Thermometer)接口可以读取 , 加热器(Heater) 接口可以启动和停止 .

37

Void Regulate(Thermometer& t,Heater& h, double minTemp, double maxTemp ){ for (;;) { while (t.read()> minTemp) wait(1); h.engage(); while (t.read() < maxTemp) wait(1); h.dinengage(); }}

倒置依赖关系,高层的调节策略不再依赖于任何温 度计或熔炉的特定细节,该程序有很好的可重用性。

38

上面使用动态的多态性 (抽象类或接口 ) 实现了通用的 调节器软件。同样 ,还可以使用 C++ 模板 (template) 提供静态形式的多态性。template <typename THERMOMETER,typename HEATER>class Regulate(Thermometer& t,Heater& h, double minTemp, double maxTemp ) { for (;;) { while (t.read()> minTemp) wait(1); h.engage(); while (t.read() < maxTemp) wait(1); h.dinengage(); }}

39

在 C++ 中, read , engaged , disengaged 方法可 以是非虚的。任何声明了这些方法的类都可以作为模 板参数使用,不必从一个公共基类继承。 在作为模板 Regulate 不依赖于这些函数的特定实现。 只要出替换类 HEADER,THERMPMETER 中的方法。 模板的缺点是 : HEADER,THERMPMETER的类型不能在运行中更改, 对于新类型的使用会重新编译和部署 。 应当先使用动态特性

40

程序的依赖关系没倒置就是过程化的设计。 程序的依赖关系倒置了,就是面向对象的设计。 正确应用依赖关系的倒置对于创建可重用的框架是 必须的。 对于构建在变化方面富有弹性的代码也是非常重要的。 抽象和细节分离,代码易维护。

使用传统的过程化程序设计所创建出来的依赖关系结 构是依赖于细节的。

41

目的 : 处理胖 (fat) 接口 (类的接口不是内聚的 cohesive), 把胖接口分解成多组方法 . 这样一些客户可以使用一组成员函数 . 若有一些对象不需要内聚的接口 ,ISP 建议 客户程序应看到具有内聚接口的抽象基类 .

4) 接口隔离原则 (ISP, see Interface Segregation Principles)

42

(1) 接口污染 安全系统中的 Door(门 ) 对象 ,可以被加锁 和解锁 ,且 Door 对象知道自己是开还是关 .class Door { public: virtual void Lock() = 0; virtual void Unlock() = 0; Virtual bool IsDoorOpen() = 0; }; 该类是抽象的 ,客户程序可以使用那些 符合 Door 接口的对象 ,而无需依赖 Door 的实现 .

43

如果门开着的时间过长 , 会发出警报声 ,设一个 TimedDoor 对象和 Timer( 定时器 )对象交互 . class Timer { public: void Register (int timeout,TimerClient* client); }; class TimerClient { public: virtual void TimeOut () = 0; }; 希望得到超时的通知可以调用 Register 函数 . TimeOut 函数会在超时到达时被调用 .

44

怎样将TimerClient类和 TimedDoor类联系起来 ? 才能在超时时通知到 TimedDoor中相应的处理? 给出一种方案 :

<<interface>>Timer Client+Timeout

Timer 0..*

Door

TimedDoor 图 5-17 位于层次结构 顶部的 TimerClient

TimerClient可以把自己注册到Timer 中 ,且可到接收 Timeout消息 . Door 类依赖于 TimerClient.可 是并不是所有的 种类的 Door 都需 要定时功能 .

如果创建了无需定时功能的Door派生类,则在这些派生类中就必须提供TimeOut方法的退化(degenerate)实现,违反了LSP(替换原则).

45

此外使用这些派生类的应用程序即使不 使用 TimerClient 类定义 ,也要引入它 ,具有 复杂性和不必要重复的臭味 .Door 的接口被一个它不需要的方法污染了 .

Door中加入这个方法只是为子类带来方 便 .若每次子类需要一个新方法时,就将其加 到基类中,使它变胖 . C++、 Java静态类型语言中是常见的.

46

(2) 分离客户就是分离接口 Timer使用 TimerClient, 而操作门的类 使用 Door.既然客户程序是分离的 ,接口也 应分离 .因为客户程序对于它们使用的接口 施加有作用力 . 客户对接口施加的反作用力 一般考虑软件中引起变化的作用时 ,通 常的变化怎样影响其使用者 . 考虑接口如果 TimerClient 的接口改变了 , TimerClient 的 使用者要做什么改变 ?

47

有时接口改变的 ,正是它们的使用者 . 如 Timer 的使用者会注册多个超时通知请求 , 当检测到门打开发送一个注册消息 ,请求一个超 时通知 .但在超时到达前 ,门又关上了 ,而后又被 打开 , 导致原先超时到达前又注册一个新的超时 请求 ,最后 ,最初的超时到达 , TimedDoor 的 TimeOut 方法被调用 , 错误发出报警 .

怎样改正上面的错误 ?

48

增加一个标识码 ,以便知道该响应哪个超时请求 class Timer { public: void Register (int timeout,int timeOutID, TimeClient* client); }; class TimerClient { public: virtual void timeOut (int timeOutID) = 0; };

49

(3) 分离接口的方法 不应强迫客户依赖于它们不用的方法 . 使用委托分离接口 Timer

<<interface>>Timer Client

+Timeout

Door

Door TimerAdapter

+Timeout()

TimedDoor+DoorTimeout()

0..*

图 5-18 定时器适配器<<creates>>

当 TimedDoor 要向 Tim 对象注册一个超时请求时 , 它就创建一个DoorTimerAdapter( 适配器 )并且把它注册给 Timer.

当 Timer 对象发送TimeOut消息给DoorTimerAdapter时 , DoorTimerAdapter把这个消息委托给TimedDoor.

DoorTimerAdapter会将 TimerClient 接口转换成 TimedDoor 接口 .

50

TimedDoor.cppclass TimedDoor: public Door{ public: // 注册一个超时请求 virtual void DoorTimeOut(int timeOutID);};Class DoorTimeAdapter: public TimerClient{ public: doorTimerAdapter (TimedDoor& theDoor): itsTimedDoor(theDoor) { } vistual void TimeOut (int timeOutID) { itsTimedDoor.DoorTimeOut(ID); } private: TimedDoor& itsTimedDoor;}

51

该方案不太优雅 : 每次注册一个超时请求时 ,都要创建一个新对象 . 委托处理会导致一些很小到仍然存在的运行时间 和内存开销 . 对于嵌入式实时控制系统值得考虑 . 当 DoorTimerAdapter 对象所做的转换是必须的 , 或者不同的时候会需要不同的转换时 ,会选择这 个方案 .

52

分离接口的方法 使用多重继承分离接口

Timer<<interface>>

Timer Client

+Timeout

Door

TimedDoor+TimeOut

0..*

图 5-19 多重继承 TimedDoor

53

TimedDoor.cppClass TimedDoor: public Door,public TimerClient{ public: virtual void DoorTimeOut (int timeOutID);}; 两个基类客户程序都使用 TimedDoor,但都 不依赖于 TimedDoor 类 ,它们通过分离的接口使 用同一个对象 .(通常会选择这个解决方案 ).

54

ATM 用户界面的例子 输出信息被转换成不同方式,显示在屏幕上;盲文书写 板上;语言合成说出来。

《 interface》ATM UI

Screen UI Braille UI Speech UI

图 5-20 ATM 界面层次结构

55

ATM 操作层次结构 :存款、取款、转帐Transaction{abstract}+Execute()

DepositTransaction

WithdrawalTransaction

TransferTransaction

图 5-21 ATM 操作 层次结构

《 interface》UI+RequestDepositAmount() +RequestWithdrawalAmount() +RequestTransferAmount() +InformInsufficientFunds()

这个设计如何 ?所有的类都依赖于UI接口

56

ATM 操作层次结构 :存款、取款、转帐Transaction+Execute()

DepositTransaction

WithdrawalTransaction

TransferTransaction

图 5-22 分离 ATM UI 接口

《 interface》UI+RequestDepositAmount() +RequestWithdrawalAmount() +RequestTransferAmount() +InformInsufficientFunds()

《 interface》Deposit UI+RequestDepositAmount()

《 interface》 Withdrawal UI+RequestWithdrawalAmount()+InformInsufficientFunds()

《 interface》Transfer UI+RequestTransferAmount()

57

思考 :下面是课程登记系统。有哪些方法不妥? 需求变化了 ,会出现什么问题?

EnrollmentReport

Generator

AccountsReceivable

StudentEnrollment+getName+getDate+prepareInvoice+postPayment

Course

《 parameter》 《 parameter》

0..*

图 5-23 未分隔的课程登记系统一个类图如何改进这个类图 ?

58

EnrollmentReport

Generator

AccountsReceivable

StudentEnrollment+getName+getDate+prepareInvoice+postPayment

Course

《 parameter》

《param

eter》

0..*

图 5-24 分开的课程登记系统一个类图

《 interface 》EnrollmentReport+getName+getDate

《 interface 》EnrollmentAccounts+ prepareInvoice+postPayment

59

* 单一职责原则 SRP (SRP,Single-Responsibility Principle) 就一个类而言,应该仅有一个引起它变 化的原因。分离类的职责,分离接口的职责。* 开放—封闭原则 OCP (The Open-Closed Principle) 软件实体 (类、模块、函数等 ) 对于扩 展的,是开放的。 对于更改是封闭的,关键是抽象。

小 结

60

* 依赖倒置原则 DIP (Dependency-Inversion Principles) 高层模块不应依赖于低层模块,即抽象 不应依赖于细节 ,细节应该依赖于抽象。 * 接口隔离原则 ISP (see Interface Segregation Principles) 对胖接口分解。 不应该强迫客户依赖于它们不用的方法。 接口属于客户,不属于它所在的类层次结构。

61

5.2.2 为什么要提出“软件体系结构” 5.2.3 体系结构风格

5.2 软件设计的体系结构5.2.1 什么是“软件体系结构”

62

“体系结构”一词起源于建筑学 如何使用基本的建筑模块构造一座完整的建筑?

5.2.1 什么是“体系结构” (Architecture)

包含两个因素:

建筑模块之间的粘接关系:如何把这些“砖、瓦、灰、沙、石、预制梁、柱、屋面板”有机的组合起来形成整体建筑?

基本的建筑模块:砖、瓦、灰、沙、石、 预制梁、柱、屋面板…

63

1) 计算机硬件系统的“体系结构” 如何将设备组装起来形成完整的计算机硬件系统?

(2) 计算机体系结构的风格: 以存储程序原理为基础的冯·诺依曼结构 存储系统的层次结构 并行处理机结构 ……

(1) 两个因素: 基本的硬件模块:控制器、运算器、内存储器、 外存储器、输入设备、输出设备… 硬件模块之间的连接关系:总线

64

(3) “体系结构”的共性 一组基本的构成要素—— 构件 要素之间的连接关系—— 连接件 要素连接之后形成拓扑结构—— 物理分布 作用于要素或连接关系上的限制条件——约束 质量——性能

65

2) “软件体系结构” (Software Architecture)

提供了对软件系统的结构、行为和属性的抽象 反映系统开发中具有重要影响的设计决策 便于各种人员的交流 完成系统既定的功能和性能需求。

软件体系结构 = 构件 + 连接件 + 约束 Architecture = Components + Connectors + Constrains

66

(1) 构件 (Component)

构件是具有某种功能的可复用的软件结构单元, 表示了系统中主要的计算元素和数据存储。

Component

Interface

Service

C

任何在系统运行中承担一定功能、发挥一定作用 的软件体都可看作是构件。

程序函数、模块 对象、类 文件 相关功能的集合 …. 图 5-25 构件

67

(2) 接口 (Interface) 构件作为一个封装的实体,只能通过其接口与外 部环境交互;

芯片的管脚 内部结构则被隐藏起来 (Black-box); 一个构件至少有一个接口, 一个构件可以提供多重接口:

接不同的管脚,实现不同的功能。 构件接口与构件实现严格分开 .

68

(3) 服务 (Service) 构件被看作一个“黑盒”(Black Box),其内部具体的实现机制被封装和隐藏起来。

构件的功能以服务(Service)的形式体现出来,并通过接口向外发布,进而产生与其它构件之间的关联。

69

(4) 连接件 (Connector) 连接件 (Connector):表示构件之间的交互并实现构件之间的连接,如:

管道 (pipe) 过程调用 (procedure call) 事件广播 (event broadcast) 客户机 -服务器 (client-server) 数据库连接 (SQL)

连接件也可看作一类特殊的构件,区别在于: 一般构件是软件功能设计和实现的承载体; 连接件是负责完成构件之间信息交换和行为联系 的专用构件。

C1 C2

70

(5) 连接的协议 (Protocol) 协议 (Protocol)是连接的规约 (Specification);

目的:使双方能够互相理解对方所发来的 信息的语义。

连接的规约是建立在物理层之上的有意义信息 形式的表达规定。

对过程调用 : 参数的个数和类型、 参数排列次序 对消息传送:消息的格式

71

(6) 连接的种类 从连接目的看:

操作 /过程调用; 控制 /事件 / 消息发送; 数据传输;

除了连接机制 /协议的实现难易之外,影响 连接实现复杂性的因素之一是“有无连接的 返回信息和返回的时间”,分为:

同步 (Synchronous) 异步 (Asynchronous)

72

体系结构是对复杂事物的一种抽象。 良好的体系结构是普遍适用的,它可以高 效地处理多种多样的个体需求。 体系结构在一定的时间内保持稳定。 只有在稳定的环境下,人们才能干点事情。 对需求变化,只做些皮皮毛毛的修改 .

5.2.2 为什么要提出“软件体系结构”

73图 5-26 按父子样式关系组织的构架样式分类简图

独立组件通信进程 事件系统

隐式调用 显示调用数据流 以数据为中心

成批顺序流 管道和过滤器 数据仓库 黑板虚拟机 调用和返回

解释器 基于规则的系统 主程序和子程序 面向

对象 分层

5.2.3 体系结构风格

74

1) 主程序 -子程序结构 主程序子过程 1 子过程 2 子过程 3

是结构化程序设计 的一种典型风格。 从功能的观点设 计系统,通过逐 步分解和逐步细 化,得到系统体 系结构。

构件: 主程序、子程序 连接件: 调用 -返回机制 拓扑结构:层次化结构

图 5-27 主程序 - 子程序结构

本质:将大系统分解为若干模块 ( 模块化 ),主程序 调用这些模块实现完整的系统功能。

75

(1) 现实世界里邮政系统2) 层次结构

通信者活动界面 通信者活动界面邮局服务业务 邮局服务业务

邮局转运业务 邮局转运业务

运输部门的(邮件)运输业务

书写信件 粘贴邮票 投递进信箱 收集信件 加盖邮戳

邮件分检 邮件打包 转送运输 部门

选择运输 路径(路由)

转送邮局接收邮件

接收邮件邮件拆包

邮件投递邮件分检

信箱取信阅读邮件

发信者 收信者

图 5-28 邮政系统

76

协议物理层协议

网络层协议分组流

比特流帧流

报文流应用层7

4

网络层

3

2

1

6

5

段流子网内部协议通信子网

转接节点 转接节点

报文流报文流

表示层会话层传输层

链路层链路层物理层

应用层表示层会话层传输层

网络层 网络层链路层物理层

网络层 网络层链路层 链路层物理层 物理层

图 5- 29 网络协议层

(2) 网络的分层模型

77

(3) 计算机操作系统的层次结构

计算机硬件 (CPU、存储器、 I/O等 )基本输入输出 (BIOS)

操作系统内核 (System kernel)系统调用

语言处理、系统工具、系统应用程序Shell 解释运行

用户

图 5-30 计算机操作系统的层次结构

78

小学生中学生大学生研究生举出一个层次结构的实例

图 5-31 学历 层次结构 图 5- 32 Java 的 I/O 层次图

+write()OutputStream

+write()FilterOutputStream

+write()DataOutputStream +write()

BufferedOutputStream+write()

FileOutputStream

79

两层 C/S 三层 C/S 多层 C/S

(4) 客户机 / 服务器结构( Client/Server, C/S) 客户界面 数据库服务器

客户界面客户界面

数据库服务器

数据库服务器

业务逻辑服务器业务逻辑服务器Web服务器

图 5-33 C/S 结构

80

1用户 2用户 3用户 4用户 5用户

Print Server

Link/Rx LPT1 LPT2 COMPower/TX

PWR

OK

WIC0ACT/CH0

ACT/CH1

WIC0ACT/CH0

ACT/CH1

ETHACT

COL 其他公用设备

打印机

调制解调器

I nternetI ntranet

数据库服务器

图 5-34 两层 C/S物理结构

两层 C/S物理结构

81

C/S 结构的处理流程

输出数据表示层 业务处理程序全部处理结束

业务处理请示和业务处理所需的全部输入数据

SQL请求开始DBMS执行 SQL

数据层

业务处理开始

数据存取程序

数据登录 / 更新 / 读取的请求数据登录 / 更新 /读取的结果

图 5-35 C/S 结构的一般处理流程SQL请求结束

数据存取请求业务处理结束

输入数据 请求按钮 业务处理开始数据存取请求业务处理结束

82

服务器的活动等待

等待连接开始监听 停止监听

接受连接处理连接

Do:响应消息处理连接断开

开始 停止监听 ,为己有的继续 服务 ,但不接受新的客户机连接。

图 5-36 服务器的活动

服务器的初始化 开始监听,和客机相连 处理随时发生的事件:客户机的连接; 对连接的客户机响应; 处理客户机的连接断开。

关闭

83

客户机的活动

终止 图 5-37 客户机的活动

连接失败或服务器报绝连接 ,再试或放弃 .

与用户交互必要时,向服务器发送消息

初始化服务器的网络地址

响应服务器激发的事件 do: 响应消息处理服务器断开连接

启动到服务器的连接

84

如,一个服务器程序同两个客户机程序的通信Server Client1 Client2

监听连接发送消息

发送响应 断开连接断开连接发送消息

连接连接

终止监听图 5-38 一个服务器程序同两个客户机程序的通信

85

客户 /服务器的连接方式 同步连接方式 异步连接方式 使用过程的连接 ; 使用消息的连接

客户 服务器

① Server Request() ③

② ④

⑤ RespondClient()异步

① Server Request()

③ ④ RespondClient()

同步

86

客户机与服务器进行通信时 ,实际上使用两 种语言进行对话 , 应当有些规则 (描述客户机与 服务器必须交换的消息序列 ) 来保证 . 两种语言与对话规则合在一起称为协议 (PROTOCOL). 在简单系统中协议仅仅是服务请求和响应的 列表 .

客户机 / 服务器系统中的消息 --- 通信协议

87

两层 C/S 结构优缺点 专用性、交互性强 存取数据安全 网络通讯量低、速度快

难以扩展至大型企业广域网或 Internet 客户端应用程序仍显肥胖 易造成网络瓶颈。

88

客户端

数据层功能层表示 层

三层 C/S 结构

表示层是应用的用户接口部分,它担负着用户与应用间的对话功能。

将具体的业 务处理逻辑 编入程序

管理对数 据库数据 的读写

图 5- 39 三层 C/S 结构

数据库服务器应用服务器

用户 1 用户 2...

用户n

InternetIntranet

数据库服务器 2

应用服务器

...

数据库服务器 1

898989

数据输出

请求 启动业务处理

数据存取请求

结束业务处理

启动数据请求过程

执行数据存取

结束数据请求过程

接受SQL请求

DBMS执行SQL

完成SQL请求

请求与输入数据

结果和状态

请求

结果

数据输入

功能层

三层 C/S 结构处理流程

图 图 5-40 5-40 三层 C/S 结构处理流程

表示层

数据层

90

Internet

数据服务器 应用服务器

远程用户远程用户远程用户

Internet防火墙

Web服务器

图 5- 41 B/S 体系结构

表现层: 浏览器 逻辑层:

Web服务器 应用服务器

数据层: 数据库 服务器

(5) 浏览器 / 服务器( Browser/Server, B/S) 体系结构

919191

B/S 结构处理流程

页面页面脚本

表现层

应用服务器层数据库访问层

客户端

浏览器

表现层

Web服务器

应用层

应用服务器

数据层

数据服务器数据库

图 5- 42 B/S 结构处理流程在 Web服务器端,程序员要用脚本语言编写响应页面 ,例如用 Microsoft 的 ASP 语言查询数据库服务器,将结果保存在Web 页面中,再由浏览器显示出来。

用户运行某个应用程序时 ,只需在客户端浏览器中键 入相应网址 (URL), 调用Web服务器上的应用程序 , 对数据 库进行操作 , 完成相应处理 , 结果通过浏览器显示给用户 .

92

结构的优点是: B/S 结构的客户端只是提供友好界面的浏览器 , 利于推广。 B/S成为真正意义上的“瘦客户端”,从而具备 了很高的稳定性、延展性和执行效率。 B/S 体系结构缺乏对动态页面的支持能力,没 有集成有效的数据库处理功能。 采用 B/S 体系结构的应用系统,在数据查询等 响应速度上,要远远地低于 C/S 体系结构。

缺点

93

(6) C/S+B/S 混合体系结构 为了克服 C/S 与 B/S各自的缺点,发挥各自的优点, 在实际应用中,通常将二者结合起来。

企业外部用户通过 Internet访问 Web服务器 /应用 服务器

B/S 结构; 用户不直接访问数据,数据安全。

企业内部用户通过局域网直接访问数据库服务器 C/S 结构; 交互性增强; 数据查询与修改的响应速度高。

94

C/S+B/S 模型

Web服务器数据库服务器

企业内部企业外部

内部局域网C/S 结构

B/S 结构

Internet

查询和浏览工作站 修改和维护工作站

查询和浏览工作站

修改和维护工作站

Internet用户图 5-43 C/S+B/S 结构

95

5.3 面向对象设计的软件体系结构5.3.1 逻辑结构5.3.2 物理结构

96

5.3 面向对象设计的软件体系结构5.3.1 逻辑结构

将功能合理的进行分组 说明它们是如何工作的 : 哪一个类存在 ? 类之间如何联系的 ? 如何协作来完成系统的功能 ? 有什么约束 ?

97

三层的逻辑构架UI

ActiveX组件Microsofte基类

应用窗口业务对象

控制业务对象《 Facade 》服务接口

外部业务对象实体业务对象

数据库SQL产生器《 Facade 》对象到关系转换包

图 5-44 设计的逻辑架构

98

客户支持系统订单输入子系统

库存管理子系统图 5-45 客户支持系统的一个包图

订单 退货条目订单条目 订单交易

订单执行子系统发运人 运输

客户维护子系统客户

目录维护子系统目录 订单

99

1) 包 (package) 的表示将设计元素分组的通用组织结构

包方便理解,处理和维护整个系统。TypesInteger

Time

图 5-46 包的表示

元素在包内 Types 包的内容隐藏

Imported

Importing

Abstract

Implementing

输入 (import)依赖输入包

被输入包 抽象包的所有 公有和受保的 元素对实现包都是可见的。

100

包拥有的元素类接口组件节点协作用例图以及其他包 .

包的依赖关系 :

包的泛化关系

包间的依赖关系包层依赖关系访问与引入依赖关系

101

顶层领域概念包

Products Sales

AuthorizationTransactions授权事物

领域概念核心 /混杂 Payment

图 5-47 顶层领域概念包

102

核心包核心 /混杂

Houses

图 5-48 核心包

StoreAddressname

POST Manager1 1..

* 1..*Employs1

SalesCapturesCore Elements::

POST Sale1 1

图 5-49 包中的一个引用类型

103

支付包 现金、信用卡、支票支付

Payment 支付

图 5-50 领域支付概念包

CashPayment CreditPayment CheckPayment CreditAuthorizationService

AuthorizationService

CheckAuthorizationServiceAccountsReceivable DriversLicense CreditCard Check

授权服务

104图 5-51 产品包图

ProductCatalog

Sales::SalesLineItem

Item

Described-by ProductSpecificationdescriptionpriceUPC

Core::Store

DescribesStocks

RecordsSale-of1 *

0..1

1*

1

*1

1 1..*

产品

105

Database Services

IPServer 《 Interface》IPServer commit() getObject()insert() rollback()包指明与客户端的接口如何实现

图 5-52 一个包的接口 “接口” (Interface) 是 OO中一个很重要的概念,它支持对 OO中“封装”的实现:接口与实现分离; 在使用接口的同时,需要为接口定义相应的 “实现”,使用 realization关系表示。 在使用接口的时候,可以和普通类一样使用。

继承、组合、聚合、关联、依赖

2)包的接口

106

3) 包的导入( import) 元素导入 :将包内任一个元素导入到另一个包中

Program

Types

《 Datatype 》Integer

图 5-53 包的元素及包导入

《 Datatype 》Time《 import 》

Program 包导入了 Time数据类型 包导入 : 一次导入整个包里的所有元素

Employee-address:AddressOrder-orderDate:Date

Order System

Address

Date

Domain DateType《 import 》

Employee和 Order可以直接使用Address和Date

107

4) 如何进行包的设计 在 UML的概念中,包可以用作包容一组类的容器。 通过把类组织成包,在更高层次的抽象上来理解 设计。通过包来管理软件的开发和发布。目的就 是根据一些原则对应用程序中的类进行划分,然 后把那些划分后的类分配到包中。 但是类之间存在依赖关系,这些依赖关系还经常 会跨越包的边界。因此,包之间也会产生依赖关 系。包之间的依赖关系展现了应用程序的高层组 织结构,应该对这些关系进行管理。

108

这就提出了很多问题: (1) 在向包中分配类时应该依据什么原则? (2) 应该使用什么设计原则来管理包之间的 关系? (3) 包的设计应该先于类呢(自顶向下) ? 还是类的设计应改先于包(自底向上)? (4) 如何实际表现出“包”? 在 C++ 中如何表现? 在 Java 中如何表现? 在某种开发环境中又如何表现? (5) 包创建好后,应当将它们用于何种目的?

109

现介绍包的 6个设计原则,涉及包的创建、相 互关系的管理以及包的使用。 前 3 个原则 ,包的内聚性原则 : 重用发布等价原则、 共同重用原则、 共同封闭原则 是用来指导如何把类划分到包中的。 后 3 个原则 ,包的耦合性原则 : 无环依赖原则、 稳定依赖原则、 稳定抽象原则 是用来处理包之间的相互关系的。

110

包的内聚性原则 帮助开发者决定如何把类划分到包中。 这些原则依赖于这样的事实: 至少已经存在一些类,并且它们之间的相 互关系也已经确定。因此,这些原则是根据 “自底向上”的观点对类进行划分的。 (1) 重用发布等价原则 REP (Reuse-Release Equivalence Principles) 由于重用性必须是基于包的,所以可重用 的包必须包含可重用的类。 重用的粒度就是发布的粒度

111

(2)共同重用原则 一个包中的所有类应该是共同重用的。 如果重用了包中的一个类,那么就要重用包 中的所有类。这个原则规定了趋向于共同重 用的类应该属于同一个包。 例 :容器类以及与它关联的迭代器类。 这些类彼此之间紧密耦合在一起,因此 必须共同重用。所以它们应该在同一个包中。

CRP (Common-Reuse Principle)

112

(3)共同封闭原则 包中的所有类对于同一类性质的变化应该 是共同封闭的。一个变化若对一个包产生 影响,则将对该包中的所有类产生影响, 而对于其他的包不造成任何影响。 这条原则规定了 : 一个包不应该包含多个引起变化的原因。

CCP (Common-Closure Principle)

如果从可维护性的角度 , 一个应用中的代码 必须更改,应把更改都集中在一个包中。

113

如果两个类之间有非常紧密的绑定关系,不 管是物理上的还是概念上的,它们总是会一 同进行变化,它们应该属于同一个包中。 这样做会减少软件的发布、重新验证、 重新发布、重新发行的工作量。

值得注意的是包的划分可能会动态的改变,如 当项目的重心从可开发性向可重用性转变时, 包的组成很可能会变动并随时间而演化。

114

包的耦合性原则 (Acyclic-Dependencies Principle)

MyApplication

MyTasksTaskWindow

Windows

Tasks MyDialogs

Database

MessageWindow

图 5-54 包结构是有向无环图

包是节点( node),依赖关系是有向边 ( directed edge)。

(4)无环依赖原则 ADP在包的依赖关系图中不允许存在环。

无论从哪个包开始,都无法沿着依赖关系而绕回 到这个包。该结构中没有环。它是一个有向无环图 DAG 。

115图 5-55 具有依赖环的包图

MyApplication

MyTasksMessageWindow

TaskWindow

Windows

Tasks MyDialogs

Database

如果依赖关系图中存在环,就很难确定包构建的顺序。

116

X

MyDialogs

Y

MyApplication

Y

MyApplicationMyDialogs

X 《 interface 》X Server

解除依赖环把包的依赖环恢复为一个 DAG 。有两个主要的方法: 方法1:使用依赖倒置原则

图 5-56 使用依赖倒置解除依赖环

117

MyApplication

MyTasksMessageWindow

TaskWindow

Windows

Tasks MyDialogs

Database

aNewPackage图 5-57 使用新包解除依赖环

方法 2:新创建一个 MyDialogs和 MyApplication 都依赖的包.把 MyDialogs和 MyApplication 都依赖的类移到这个新包中。

创建新的包,致 使依赖关系结构增长。 当需求改变时这个包 的结构是不稳定的。

118

(5)稳定依赖原则

稳定性,如果某物“不容易被移动” 就认为它 是稳定的 (韦伯斯特 ) 。 使软件包难以更改的因素有许多: 它的规模、复杂性、清晰程度等等。 要使一个软件包难以改变,一个肯定可行的方 法是让许多其他的软件包依赖于它。 如 ,具有很多输入依赖关系的包是非常稳定的, 它的包能够相容于对它所做的更改。

朝着稳定的方向进行依赖。 SDP (Stable-Dependencies

Principle)

119

X

图 5-58 X: 一个稳定的包X不依赖于任何包,X是无依赖性的。

Y

图 5-59 Y:一个不稳定的包 没有任何其他的包依赖于 Y;Y是不承担责任的,称 Y是有依赖性的。

120

• 并非所有的包都应该是稳定的 如果一个系统中所有的 包都是最大程度稳定的, 那么该系统就是不能改 变的 ,这不是所希望。 希望所设计出来的包结 构中,一些包是不稳定 的 ,而另外一些是稳定的。

Instable

Stable

Instable

图 5-60 理想的包配置

121

图 4-38 展示了违反稳 定依赖原则 SDP 的做法。 必须要以某种方式解 除 Stable 对 Flexible 的依赖。 为什么会存在这个依 赖关系呢?

Stable

Flexible

图 5-61 违反了 SDP

122

Stable

Flexible

C

图 5-62 糟糕依赖关系

假设 Flexible 中有一个类 C 被另一个Stable 中的类 U使用(参见图 4-39)

U

怎么解决 ?

123图 5-63 使用 DIP修正违规的稳定性

Stable

U

UInterface《 interface 》

IU

Flexible

C

可以使用 DIP(Dependency-Inversion Principles) 依赖倒置原则来修正这个问题。

稳定 保持它必需的不稳定性

接口 IU 中,声明了U 要使用的所有方法

124

(6)稳定抽象原则 包的抽象程度应该和其稳定程度一致。

SAP (Stable-Abstractions Principle)

该原则把包的稳定性和抽象性联系起来。 它规定,一个稳定的包应该也是抽象的, 这样它的稳定性就不会使其无法扩展。 另一方面,它规定,一个不稳定的包应 该是具体的,因此它的不稳定性使得其 内部的具体代码易于更改。

125

*重用发布等价原则 REP (Reuse-Release Equivalence Principles) 重用的粒度就是发布的粒度。

小结 :

*共同封闭原则 CCP (Common-Closure Principle) 包中的所有类对于同一类性质的变化应该 是共同封闭的。 *无环依赖原则 ADP (Acyclic-Dependencies Principle) 在包的依赖关系图中不允许存在环。

126

* 共同重用原则 CRP (Common-Reuse Principle) 一个包中的所有类应该是共同重用的。如果 重用了包中的一个类,那么就要重用包中 的所有类 .

* 稳定抽象原则 SAP (Stable-Abstractions Principle) 包的抽象程度与其稳定程度一致。

* 稳定依赖原则 SDP (Stable-Dependencies Principle) 朝着稳定的方向依赖。

127

包的设计结论: 对概念和语义上相互接近的元素所定义的组 块放到一个包中 对每一个包找出可以在包外访问的元素,将 这些元素标记为公有的,把其他的元素标记 为受保护的或私有的。如果不确定时, 就隐 藏该元素 确定包与包之间的依赖关系,特别是引入依 赖。包之间的依赖关系展现了应用程序的高 层组织结构 ,应该对这些关系进行管理。

128

不能自顶向下设计包的结构。

包的依赖关系图和描绘应用程序的功能之间 几乎没有关系。相反,它们是应用程序可构 建性的映射图。这就是为何不在项目开始时 设计它们的原因。

包结构不是设计系统时 ,首先考虑的事情 包结构应该是随着系统的增长、 变化而逐步 演化的。

随着实现和设计的深入 , 累积的类越来越多, 对依赖关系进行管理

129

随着应用程序的不断增长,开始关注创建可 重用的元素。于是,就开始使用 CRP (共同 重用原则 )来指导包的组合。 最后,当环出现时,就会使用 ADP(无环 依赖原则 ) 。

包的依赖关系结构是和系统的逻辑设计一 起增长和演化的。

130

回答问题1.列出抽象视窗工具 (Abstract Windowing Toolkit) 包装 (即 java.awt) 中的抽象类型

Component(部件 ),Container(容器 ),MenuComponent(菜单部件)

131

2. java.awt 包主要依赖哪几个包 ?画出包图。java.lang

Java 语言基本包java.io

java.awt.eventjava.awt.image

Java抽象视窗工具图像包

java.awt

图 5-64 java.awt包 的依赖包

132

描述系统软件、硬件

注重实现描述硬件结构:节点及节点间连接

5.3.2 物理结构

1) 构件图 (Component diagram)构件:定义了良好接口的物理实现单元 , 是系统中可替换部分 .表示构件类型的组织以及依赖关系的图

4.3.1 逻辑构架

133

(1) 构件和类的不同点 类是逻辑抽象; 构件是物理抽象,即构件可以位于节 点(node)上,是类的物理实现 类可以有属性和操作; 构件通常只有操作,而且这些操作只 能通过构件的接口才能使用

134

一般说来,构件就是一个实际文件,可以有 以下几种类型 :

deployment component,如 dll 文件、 exe 文件、 COM+ 对象、 CORBA 对象、 EJB、动态 Web 页、数据库表等 work product component,如源代码文件,数据文件等,这些构件可以用来产生 deployment component execution component,可执行的构件

(2) 软件构件类型

135

公共处理子程序(comhnd.obj)

图 4-65 源、二进制、可执行构件及关系

源代码构件 二进制代码构件 可执行代码构件

窗口处理子程序(wnnd.cpp)窗口处理子程序(wnnd.obj) 图形库 (graphic.dll )公共处理子程序(comhnd.cpp

Main 类(main.cpp ) Main 类(main.obj)

客户程序 (client.dll )

136

接口是被软件或硬件所支持的一个操作集。

Updatethread.java

图 5-66 带有接口的构件

update

实现 (Realization)接口

display.java

(3) 带有接口的构件

使用 依赖 (Dependency)

137

People

图 5-67 选课系统的组件图

MainProgram

FormObject ControlObject

Student Registrar Course DataBase

138

第 1 步 : 处理业务 / 领域类用户界面类

图 5-68 重要类的关系

业务 / 领域类持久类

控制器/处理器

持久存储

系统类

(4) 开发构件模型

139

第 2 步 : 定义类契约 —— 公有接口第 3 步 : 简化层次结构 —— 继承和聚合的层次结构第 4 步 :确定可能的领域构件 (domain Component)服务器类属于同一构件 ( 接收消息不发送消息 )

注意构件的合并

客户类不属于构件 ( 发送消息不接收消息 )紧密耦合的类属于同一构件使构件间的消息流的大小最小化构件应当内聚构件可能会发生变动

第 5 步 : 定义领域构件契约 —— 领域构件的公共接口

确定潜在构件的一些启发方法 :

140

2) 部署图 (展开图 )描述处理器、设备、软件组件在运行时的构架节点 :拥有某些计算机资源的物理对象 (设备 )《 printer 》HpLaserJet5MP

《 Router 》 Cisco Router

X2000

《 Controller 》SAAB9-5

Navigator

图 5-69 部署图 ( 设备节点和版类 )

物理构架物理构架-- -- 构件图

141

情况 1: 节点间存在通信路径 (关联 ) , 可以交换对象和发送消息 .

图 5-70 选课系统的布局图

《 Internet 》

《 Apache服务器》 《 Oracle 》

(1) 几种情况

客户端Web浏览器

打印机

DataBase数据服务器HTTP服务器

142

情况 2: 构件与节点

图 5-71 大学信息系统的部署图之一

《 http 》Client:Browser

StudentAdministration《 application 》

appServer:sunsolarisStudent

seminar

schedule

dbServer:AIXPersistence《 infrastructure 》

University DB《 database 》

《 RMI 》

JDBC

143

情况 3 : 节点包含对象实例

图 5-72 包含对象的节点

微波炉系统:微波炉控制器Guard.exe

《 process 》supervisorThermomeerController

144

情况 4: 有继承关系的节点

图 5-73 有继承关系的节点

Admin PCAdminPgm

客户客户 PCPCNetDrvApplClientWindow xp

连接到 服务器

备份站

1..* 1..2

BackupMedium1 1

145

资源利用 地理位置 设备访问 安全 性能 可扩展性和可移植性

(2) 将组件分配给节点考虑的因素

146

第 1 步 : 确定模型范围第 2 步 :确定分布结构第 3 步 :确定节点和他们的连接第 4 步 : 把构件分布到节点第 5 步:为不同组件之间的依赖建模

(3) 如何开发部署模型

147图 5-74 保险系统包图

例 1 保险系统配置图

系统内部保险单 客户

Oracle界面数据库界面

保险单填写界面

Sybase界面

148

后台服务器保险系统配置

配置用户

保险系统保险对象数据库

配置保险政策

保险单填写界面(Tcp/Ip)

图 5-75 保险系统配置图

149

例 2 一个模拟系统的体系结构图

图 5-76 一个模拟系统的体系结构

User Interface/Controller

预处理器 后处理器解题器

System Services

150

例 3 自动柜员机系统的体系结构图

图 5-77 自动柜员机系统的体系结构图

ATM Clients 可有多个 ATM 客户机

SessionMgrSession

ATM CentralSever

ATM中央服务器

AccountMGRAccount

Bank Server

银行服务器(多个 )Bank Database Nod

银行数据库结点

151

系统构架

逻辑构架

物理构架

显示类和对象及他们之间关系和协作。可以用 :用例、类、序列、状态、协作图、 活动图、包图来写逻辑架构文档 . 处理代码构件的结构和组成系统的硬件结构。详细描述逻辑架构中定义的概念的实现 .用构件图、节点图描述 .

小 结小 结

152

5.4 对象设计 5.4.1 对类的属性的处理5.4.4 应用设计模式定义操作5.4.2 关联的设计5.4.3 对结构的优化5.4.5 检查系统设计

对象设计的主要任务 : 精化类的属性和操作 明确类之间的关系 整理和优化设计模型

153

5.4.1 对类的属性的处理 1) 回顾属性的来源:

类所代表的现实实体的基本信息; 描述状态的信息; 派生属性 (derived attribute) 如: 类 CourseOffering 中的“学生数目” /numStudents:int 描述该类与其他类之间关联的信息;

其他常见的类型 : 地址 Address 、颜色 Color 、几何元素 Geometrics 、 电话号码 PhoneNumber 、通用商品代码 UPC 、 社会安全代码 Social Security Number 、 邮政编码 Postal Code 、枚举类型等

154

CourseOfferingcourseID : StringstartTime : TimeendTime : Timedays : Enum/ numStudents : IntofferingStatus : Enum

<<class>> new()addStudent(studentSchedule : Schdule)removeStudent(studentSchedule : Schdule)getNumberOfStudents() : intaddProfessor(theProfessor : Professor)removeProcessor(theProfessor : Professor)offeringStillOpen() : BooleancloseRegistration()cancelOffering()closeOffering()getCourseOffering()setCourseID(courseID : String)setStartTime(startTime : Time)setEndTime(endTime : Time)setDays(days : Enum)

<<entity>>

具体说明属性的名称、类型、 缺省值、可见性等 visibility attributeName: Type = Default Public: ‘+’; Private: ‘-’ Protected: ‘#’

2) 成员细节的表示

基本原则尽可能将所有属性的可见性设置为 private; 仅通过 set 方法更新属性; 仅通过 get 方法访问属性;

图 5-78 有细节的类图

155

3) 在哪里展示非简单数据类型和纯数据值 ? UPC 是一个纯数据值 ,但也可以是一个非简 单数据类型。

ProductSpecificationupc:UPCStore

Address:Address

ProductSpecification

UPC* 1

Store Address* 1

UPC 是非简单数据类型 (要对它进行验证 ,也可包括制造者属性 ),即有属性和关联 ,也可用概念来表示。

UPC 是纯数据值 ,在概念模型中当作属性看待。

图 5-78 用概念表示属性

156

4) 对属性的数量和单位建模Payment

amount:Number

Payment Quantityamount:Number* 1

Paymentamount:Quantity 数量是纯数据值 ,可作为属性

这个表示可用 ,但不灵活或不可靠 , 要知道支付货款时所用的货币单位。

UnitHas-amount

Is-in* 1将数量作为一个单独概念

图 5—80 对属性的数量和单位建模

157

5) 对属性的处理 保留派生属性,避免重复计算 描述其他事物 (航班和机场 ) 的规格说明Flightdata

numbertime

Airport

nameFlies-to

* 1

Flightdata time

Described-by* 1

Airportname

FlightDescriptionnumber*1Describes-flights-to

较好图 5-81 描述其他事物的规格说明

较差

(1)

158

规格说明型或描述型对象与它们所描述事物 密切相关。 Item

description price

serial-numberUPC

ProductSpecification description priceUPCDescribed-by

*1

较好图 5-81 描述其他事物的规格说明

较差

Item serial-number

(2)

159

分析阶段的数据类型只能是简单的、原始的 数据类型 ,并不表示如C++、 Java 、 Smalltalk 中的属性 (数据成员、实例变量 )

在软件构造和设计阶段,对象之间的关联通常 是用指向其他复杂类型的属性来表示 (但不是 唯一的解决方案 )

6) 外部键的属性

( 分析中的类图是对问题的存在建立的分析模 型 ,而不是软件实现的分析模型 )。

160

在分析阶段的对象模型中 , 不是使用属性 , 而是使用关联来联系两个概念的。

设计中的类图,使用外键实现对象间的联系

Cashiername

POSTnumber

Uses 11

图 5-82 不使用外部键的属性CashierNamecurrentPOSTNumber

使用外部键的属性与另一个对象发生联系 图 5-83 有外部键的属性

161

5.4.2 关联 (Association) 的设计 全局 (global):某个对象可以在全局范围内 直接被其他对象“引用”

参数(Parameter):某个对象作为另一个对象 的某个操作参数或者返回值.

局部 (Local):某个对象在另一个对象的某个 操作中充当临时变量 .

1) 四种连接

域 (Field):某个对象作为另一个对象的数 据成员 (聚合 /组合关系 ).

两个类间依赖关系

162

对象的链接方式Timer

attach(Timer observer obs) detach(Timer observer obs)notify()

Timer observer

tick( )Spriteinplay Position ( ):cpointtick ( )

Flay field

Cpoint撞击者

0..1被撞击者

0..1Velocity Movable

sprite

tick ( ) move ( )

Stationary sprite

观察者

图 5-84 对象的连接方式

163

关联源对象关联指针

属性关联对象指针 next

属性关联对象指针 next

被关联对象被关联对象

节点用指针或引用方式实现关联 :

0..*

图 5-85 单向关联的实现

公司 雇员 雇员雇主公司雇员

164

例 : 用 java 实现关联 保险公司 保险合同1 0..*有

涉及Public class Insurance - Company { /* 方法 */ Private Insurance- ContractVector Contracts }Public class Insurance - Contract { /* 方法 */ Private Insurance - Company refer - to; }

链属性的实现 —— 依赖于关联的阶数

165

2) 增加冗余关联,提供访问效率**公司Find-skill 雇员 技能

图 5-86 增加类3)对限定关联 (qualified association)的考虑 主要是减少限定符远端处的多重性 ,从多个 减少到 1个。 分析类图中的限定符只是区分两种不同类 型的事物。

166166

在设计类图中 ,关联的限定符是用某种键值或者 记号实现的 ,而不是用 Java 的引用实现的。

图 5-87 关联的限定符 Employee Login

Serveletempid

public class LoginServelet{ private String empid; public String getName(){ Employee e = DB.getEmp(empid); return e.getName(); }}

167

在设计类图中,导航是角色的一个特性,它 说明从源对象到目标对象沿着关联有一个单 向的链接,

4) 增加关联的导航 (navigability)

导航箭头的关联表示从源类到目标类的属性 可见性 在实现时,导航箭头用源类中所定义的一个 属性来实现,这个属性引用了目标类的一个 实例

168

POST SaledateisComplete:Booleantime

Captures1 1

becomeComplete() makeLineItem () makePayment()total()

endSale()enterItem()makePayment()

图 5-88 对象模型和设计类图

导航箭头说明对象 被单向链接到 Sale 对象 POST 类可能有一个指向 Sale 对象的属性

没有导航箭头说明从 Sale到 POST 方向没有链接

169

1

图 5-89 添加引用属性

根据类图中的关联和导航关系,可推导出一个类的引用属性 Product

Specification

description:Textprice:Quantityupc:UPC

SalesLineitemquantity:Integersubtotal():Quantity

*Described-by

public class SalesLineitem{ public SalesLineitem(ProductSpecification spec,int qty); public float subtotal(); private int quantity; 简单属性 private ProductSpecification prodSpec; 引用属性}

prodSpec

用属性名作为角色名

170

分析中的关联 ,是增进对问题的理解为目的 ( 不是对数据流、实例变量连接等所做的一条声明 )。说明 : 设计类图中的关联 ,是对软件构件的描述。 设计类图中关联的选择要依据交互图中的可 见性 ,和存储特性。 从 A到 B导航关联的确定 : # A向 B发送一个消息 # A 创建了 B的一个实例 # A 需要维持到 B之间的一个链接 根据协作图来确定导航

171

在类图中 ,依赖关系描绘类之间的非属性的 可见性时很有用 ,即描述参数、全局、局部可见性时很有用。 普通的属性可见性是用关联线和导航箭头来表示的。

在程序语言中,实现关联最普通的方法是使 用一个指向关联类的属性。

172

图 5-90 非属性可见性的依赖关系

POST

endSale()enterItem()makePayment()

Sales Lineitemquantity:Integersubtotal()

Saledate: DateisComplete:Booleantime: TimebecomeComplete()makeLineItem()makePayment()Total()

address: Addressname:TextaddSale()

Store

Paymentamount:Quantity

ProductCatalog

specification()

ProductSpecification

description:Textprice:Quantityupc:UPC

非属性可见性的依赖关系

Houses

1

1

Uses11

Looks-in

1

1

1..*1Contains

Captures1 1

1 1..*Contains

1Describes*

1 1Paid-by

*

1

Loge-completed

1

173

5) 对关联类的设计

关联类在实际设计中十分有用,但现在的 OO 语言 不直接支持关联类,可以这样设计 :

Supplier

BuyerTrade

Company

Personjob

Suppliercompany-name:Stringaddress:Stringbuyers: Buyer[*]trades: Trades[*]

Byuername:Stringaddress:Stringsupplier :Supplier [*]trades: Trades[*]

** *1..*

图 5-91 供应者与买家交易关联类的设计

174

上述设计有的不足 :

用二元关联类的方法来解决 把供应者和买家对象直接放到交易中作为它的属性。Suppliercompany-name:Stringaddress:Stringtrades: Trades[*] Tradeproduct:Stringquantity:IntegerTotal-price:Doublesupplier :SupplierByuer:Byuer

# 找出对应的一宗交易都要花费一定的力气# 违反了对象封装的原则 ,与交易相关的数据 (供应和买家 ) 没放在交易对象中

Byuername:Stringaddress:Stringtrades: Trades[*]

1 *

1 *

图 5-92 交易的一个通用方法

175

例.飞机(Airplane)在每次飞行中,有两个驾驶员 (pilot),在 规定的航班 (Flight)中 ,执行任务(Duty)。评价下面设计。Airplane

Person

Flight12 pilot

(a)

二元关联类的每个实例 ,描述两个类对象之间的 link的性质。 本例中航班对象包含航班号、时间等数据 ,对两个驾驶员是一样的 ,航班是飞机和人员的关联类是不恰当的。 每个航班对象与一架飞机和两个驾驶员有关系。在某一航班中 ,每个驾驶员有自己的任务 ,作为关联类来模拟。 (b) 设计允许一个驾驶员工作在不同航班 ,执行不同任务(时间不冲突便可 ) 。

Person

Duty2

*

(b)

Flight

Airplane

*

1 pilot

图 5-93 比较两个设计

176图 5-94 继承与委派机制

ListName表的 lengthAdd

RemoveFirstlast

Stack

PUSHPOP

stack:

List1PUSHPOP改为委派

AddRemove

Firstlast

List

1) 调整继承结构5.4.3 对结构的优化

177

2) 解决死板的继承Person

User Buyer supplier* *交易图 5-95 产生互不相容的子类

Role

User Buyer supplier

*

*交易Person 1

图 5-96 增加的角色类 (手柄样式 )*

用户与供应者的 交易受到了限制

一个人的对象可以有用户和买家的角色

Person

User Buyer

supplier* *

178

3) 关于关系环的问题A

B

FolderItem

Folder

正向容纳 反向容纳

图 5-97 文件夹单项与文件夹关系环

Version

Revision

图 5-98 原版与修订版

1

0..1

previous

next

FolderItem

Folder FolderItemFolder

图 5-99 文件夹单项与文件夹关系环分解

分解使关系明确 ,证实设计的正确 ,行为特性更为明显。但未分解的类图更为简洁 ,可直接与实施程序对应。

179

关系环能真实地反映实际应用情况 :

Permission

User

File

ComputerSystem

*

1

FolderItem

FileSystem

Folder

1..*

1

1..*

*1 *Owner0..*

1

0..1

**

0..1

*

如 电脑系统的局部类

图 5-100 电脑系统的局部类

180

图 5-101 属性、零件、部件组成的双重关系环Part

Component1

Attribute

*

1 *

containscontains

Group

Item

Square

1..*contains

Circle1

图 5-102 有叶结点类的关系环

181

例 有一个设计 ,描述某公司每个顾员的工作 (Job)说明 ,每个 顾员都有一个上级 (Supervisor) 。 但是一个人可能同时参加多个项目(Project),并在不同项 目中扮演同样或不同角色 (Role) 。

在同样或不同的项目中 ,一个人也可能担当不同的任务 (Task},从而有不同的角色。 一个项目中的某个角色可能有多个人参与。 根据上面要求,给出如下两个设计, 这两个设计有什么不同?

182

反向关系环 ;二元关联类 ;网络与结点是 Link 的对象管理器谁负责管理连接对象 ?

Module 1

*

例 , 如下是模块 (Module) 、网络 (Nework) 、结点 (Node) 、 连接 (Link) 的两个设计 ,试进行评价。

Nework Node

Link

1..*

**

1 1

1

Module *

Nework Node

Link1..*

1

*

反向关系环 ;递归关联 ;连接类与基类相连 ,复用源码结构简化 ,避免重复和模糊的连接

图 5-103 评价两个反向关系环设计

183

5.4.4 应用设计模式 (pattern) 定义操作1) 什么是模式? (pattern)

巧妙通用验证

Alexander给出了经典定义: “每个模式都描述了一个在我们的环境中不断出现的问题 ,然后描述了该问题的解决核心。通过这种方式,你可以无数次的使用那些已有的解决方案 ,无需在重复相同的工作。”2) 设计模式的特性

简单可重用面向对象

184

模式名问题情景约束

3) 模式的组成元素

4) 模式的分类创建性模式 :工厂模式、抽象工厂、单身等结构性模式 :适配器、桥接、外观、享元、代理等行为模式 :命令、职责链、解释器、迭代器、中介等

解决方案例子结果情景

185

5) 介绍职责分配中通用原则的模式描述将职责分配给对象的基本原则 专家 (Expert) 创建者 (Creator) 高聚合度或高内聚 (High cohesion) 低耦合度或低耦合 (Low coupling) 控制者 (Controller) 解决方案 : 将一个职责分配给信息专家。 问 题 : 面向对象的设计中什么是最 基本的职责分配原则。

五个模式(1)专家

将分析阶段漏掉的类型加入到对象模型中

186

SaleDatetime

quantity

SalesLineItem description priceUpc

Product specification1..* Contains (包含 )

* Describedby

Sale类 职责

知道销售总额 SalesLineItem 知道销售项记录了金额ProductSpecification 知道商品价格

确定销售总额 (grand total) 需要知道什么信息?

图 5-104 Sale 类的类图

例 :销售系统

187

Sli:SalesLineItem 2:st=substotal()

:Product Specification

2.1:p:=price()

1*:[for each] sli:=next():Sale

t:=total()

:SalesLineItem图 5-105 计算 Slae总额

Saledatetimetotal()

SalesLineItemquantitysubtotal()

Product Specificationdescription

PriceUPC

price()

188

说明 : 专家模式表明了“对象所能完成的工作要依赖 于它所掌握的信息。 为了履行职责需要获取散布在不同对象中的信 息。表明系统中存在许多“部分”专家,在完成 任务时需要它们的协作。 软件对象之间的合作就像人之间的合作一样。 对象所做的事情与它们所知道的信息相关。 “把责任交给数据” ;“谁知道谁来干” ;“自己 做” ;“把服务推给要操作的属性”。 封装性好;高内聚。

189

方案:将创建一个类 A的实例的职责指派给类 B 的实例,若下列条件满足 : B是 A对象的 创建者。 B聚集了 A对象 B包含了 A对象 B记录了 A对象的实例 B要经常使用 A对象 当 A的实例被创建时 , B具有要传递给 A的初始化数据。

(2) 创建者 (Builder) 模式

问题:谁负责创建一个类的新实例 ?

190

:Sale

:SalesLineItem1:create(quantity)

makelineItem(quantity)

图 5-106 创建一个 SalesLineItem 的实例

datetime

Sale

makeLineItem()total()新方法

例,在销售点应用中,哪个类负责创建一个 SaleLineItem实例?

根据创建者模式 , 应当找出一个类 ,这个类聚 集、包含了 SaleLineItem的实例。

191

解决方案:在分配一个职责时 ,要保持低耦合度。 问题:如何支持低依赖关系和增加重用。

(3) 低耦合度模式

结论 : 创建者模式 , 用于指导实例的创建任务分配 , 要找 到一个与被创建对象之间有关联关系的创建者。 职责的确定和分配在绘制协作图的语境中进行。 职责的履行由方法调用来具体实现。创建者模式说明聚合 /包容器类 /记录类是一个承 担创建被包含或被记录的事物职责好的候选者。

192

如:从销售终端应用系统中抽取如下三个类

Sale 实例创建 payment 的实例 ( 方案 2):Post

:Payment

:Sale1:makePayment()makePayment()

1.1:create()

图 5-109 Sale 实例创建 payment实例

Post 实例创建 Payment 的实例 ( 方案 1):Post P:Payment

:Sale

1:Create()makePayment()2:addpayment(p)

图 5-108 Post 实例创建 payment 实例

Payment Post Sale图 5-107 销售终端系统部分类

193

在 C++ 、 Java 中从类型 X到类型 Y的耦合有 : # 类型 X有一个属性 ( 数据成员或实例变量 ) 引用 类型 Y的实例或类型 Y本身。 # 类型 X有一个方法以各种方式引用了一个类型 Y的实例或类型 Y本身 (Y的局部变量或参数 ), 或从一个消息返回的对象是类型 Y的实例。 # 类型 X是类型 Y的一个直接的或间接的子类。 # 类型 Y是一个接口,类型 X实现了这个接口。

说明

194

低耦合度在设计过程中是要记住的一个原则, 它是一个时刻要注意的隐含设计目标。

说明

低耦合度支持更独立的类设计,支持重用 , 如果不以重用为目的,耦合就不重要了 。 不能孤立地考虑低耦合度,与专家、高内聚 模式有关。 完全没有耦合的设计也是不可取的,类之间 应当保持中等程度的耦合是合理的。

195

解决方案:在分配一个职责时 ,要保持高度聚合 问题:如何将复杂性保持在可控制范围内?

解决方案:将处理系统事件消息的职责分派 给代表下列事物的类代表整个“系统”的类代表整个企业或组织的类代表真实世界中参与职责 (角色控制者 ) 的主动对象类代表一个用况中所有事件的人工处理者类

(4) 高聚合度模式

(5) 控制者

196

问题:谁来负责处理一个系统事件? 系统事件 (system event) 由某个参与者发起的指向系统的输入事件 系统操作 (system operation) 系统响应这些事件而执行的操作 控制者 (controller) 控制者定义了系统操作的方法如: :System

enterItem(UPC,quantity)enterItem 为系统事件 ,触发了一个同名的系统操作 ,系统操作带有参数。

事件是命名的动因;操作是对动因的响应图 5-110 系统事件

197

:System

enterItem(UPC,quantity)enterKerPressed(UPC,quantity)较差的名称

较好的名称

对操作的命名 : 要抽象出最高层次或使用操作 的最终目标耒对操作的命名。

图 5-111 在比较抽象层次上对事件和操作命名如对一个记录支付信息的操作命名如下 : enterAmountTendered (amount) 较差 enterPayment(amount) 较好 makePayment(amount) 更好一些

198

如:在销售终端系统应用中:系统操作 System

endSale()enterItem()makePayment图 5-112 一个名为 System 类型中的系统操作

这个图与概念模型的区别:概念模型中的元素是真实世界中的概念 ,显示的只是静态信息

System类型像人为的概念 ,描述系统行为 , 动态信息

199

代表整个系统的类 代表整个企业组织者的类 代表真实世界参与职责的主动对象类 代表一个用况中所有事件的人工处理类

哪个类对象应该是系统事件的控制者?根据控制者模式,可选则

system:

post

endsale()enterItem()makepayment图 5-113 系统操作的分配

enterItem(upc, quantity) :postenterItem(upc, quantity) :storeenterItem(upc, quantity):cashier

enterItem(upc, quantity) :BuyItemsHandler

200

说明 : 无论采用何种输入手段 ,必须由控制者来处理 系统输入事件 . 一个用况中的所有系统事件都应由同一个控制 者来处理 ,这样才能维持一个用况的状态信息 . “系统类”是虚包的控制者 .把它看成是整个系 统代表或一个物理单元 .只有当不能将系统事 件消息发向替代的控制者时才使用虚包类.

控制者类也不能承担过多的职责 .

注意角色控制的使用 : 所创建的角色易出现低聚合的危险 ; 不要使角色像人一样承担了所有工作 .

201

例 从表示层到领域层的耦合Object store

UPC QuantityTotal

TenderedBalance

EnterItem EndSale MakePayment出纳员:Postapplet

:Post :Sale

onEenterItem()1:enterItem(upc,qty)

1.1:makeLineItem(upc,qty)

系统事件消息

控制者

表示层(JavaApplet)

领域层

×_

图 5-114 可取的从表示层到领域层的耦合

202

5.4.5 检查系统设计 1) 检查“正确性”

每个子系统都能追溯到一个用例或一个非功能需求吗? 每一个用例都能映射到一个子系统吗? 系统设计模型中是否提到了所有的非功能需求? 每一个参与者都有合适的访问权限吗? 系统设计是否与安全性需求一致?

2) 检查“一致性” 是否将冲突的设计目标进行了排序? 是否有设计目标违背了非功能需求? 是否存在多个子系统或类重名?

203

检查系统设计3) 检查“完整性”

是否处理边界条件? 是否有用例走查来确定系统设计遗漏的功能? 是否涉及到系统设计的所有方面(如硬件部署、 数据存储、访问控制、遗留系统、边界条件)? 是否定义了所有的子系统?

4) 检查“可行性” 系统中是否使用了新的技术或组件?是否对这些 技术或组件进行了可行性研究? 在子系统分解环境中检查性能和可靠性需求了吗? 考虑并发问题了吗?

204

总结 1. 设计阶段要做的工作 : 1) 逻辑结构和物理结构的设计 ; 2) 对象设计

属性的优化 关联的优化 使用设计模式确定和优化操作

205

2. 面向对象的设计原则1) 采用了模型化的观点2) 使用了抽象、封装、继承等机制3) 模块化机制 : 交互耦合、继承耦合 服务内聚;类内聚; 4) 可重用机制、灵活性机制、可移植性机制

206

(1) 提高可重用性设计的策略 使设计泛化 ; 提高抽象 ,提高内聚性 ,降低耦合的设计原则 ; 设计要包含钩子 ,使他人可添加功能 ; 简化设计 . 注意 :克隆 (clone)并不能看作是重用 . 克隆是将代码从一个地方复制到另一 个地方 .

207

(2) 提高灵活性设计的策略 积极地预测将来设计发生的变化 .策略是 : @ 提高内聚性 ,降低耦合 ;

@ 不要将代码写死 . 应消除代码中的常量 . 应在服务器启动时从配置文件中读取最大值 或向用户提供一个选项对话框 ,可修改该值 ;

@ 建立抽象 ,建立多态操作的接口和超类 ,功能 易扩展 ;

@ 打开所有选项 . 方法中有异常时 ,应抛出异常 , 而不是执行特定动作处理该异常 ;@ 使用并创建可重用代码 .

208

(3) 提高可移植性设计的策略 让软件在可能多的平台上运行 . # 避免使用特定环境的专有功能 ;

# 语言的一些功能依赖于硬件体系结构(C++) 要知道一个字中字符的顺序,以及整型变量 的位数.

# 类库也存在着差别 (Java);

# 移植性问题与文本文件有关 ,各种平台中用 来终止一行的字符都不尽相同 .

209

图 5-115 类的实现顺序

POST

endSale()enterItem()makePayment()

Sales Lineitemquantity:Integersubtotal()

Saledate: DateisComplete:Booleantime: TimebecomeComplete()makeLineItem()makePayment()Total()

address: Addressname:TextaddSale()

Store

Paymentamount:Quantity

ProductCatalog

specification()

ProductSpecification

description:Textprice:Quantityupc:UPC

3. 设计的结果 -- 面向实现的类图

Houses

1

1

Uses11

Looks-in

1

11..*1

Contains

Captures

1 11 1..*Contains

1Describes

*

1 1Paid-by

*

1

Loge-completed

1

1

23

45

6

7

210

Class Payment package post; public class Payment { private float amount; public payment (float cashTendered ) { this.amount = cashTendered; } public float getAmount() { return amount;} }

211

Class ProductCatalog package post; import java.util.*; public class ProductCatalog { private Hashtable productSpecifications = new hashtable(); public productCatalog() { ProductSpecification ps = new productSpecification( 100,1,〃 product 1〃 ); productSpecifications.put( new Integer(100),ps); ps = new productSpecifications( 200,1, ,〃 product 2〃 ); productSpecifications.put( new Integer(200),ps); } public ProductSpecification getSpecification(int upc) {return (ProductSpecification) productSpecifications.get( new Integer( upc)); } }

212

Class POST package post; import java.util.*; class POST { private ProductCatalog productCatalog; private Sale sale; public POST( ProductCatalog catalog) { productCatalog = catalog; } public void endSale() { sale.becomeComplete(); }

213

public void enterIntem(int upc, int quantity) { if( isNewSale() ) { sale = new Sale(); } ProductSpecification spec = productCatalog. specification (upc); sale.makeLineItem( spec, quantity ); } public void makePayment( float cashTendered ) { sale.make Payment( cashTendered ); } private boolean isNewSale() { return (sale == null) ||(sale.isComplete() ) } }

214

Class ProductSpecification package post; public class ProductSpecification { private int upc = 0; private float price = 0; private String description = “ ”; public ProductSpecification ( int upc, float price ,String description ) { this. upc = upc; this.price = price this. description = description; } public int getUPC() {return upc;} public float getPrice() { return price;} public String getDescription() {return description;} }

215

Class Sale package post; import java.util.*; class Sale { private Vector lineItem = new Vector(); private Date date = new Date(); private boolean isComplete = false; private Payment payment; public float getBalance() { return payment.getAmount() – total(); } public void becomeComplete() { isComplete = true;} public boolean isComplete() {return isComplete;}

216

public void makeLineItem ( ProductSpecification spec, int quantity ) { lineItems.addElement( new SaleLineItem( spec, quantity ) ); } public float total() { float total = 0; Enumeration e = lineItems.element(); while( e.hasMoreElements() ) { total += ( (SaleLineItem) e.nextElement() ).subtotal(); } return total; } public void makePayment(float cashTendered ) { payment = new payment( cashTendered ); } }

217

Class SaleLineItem package post; class SaleLineItem { private int quantity; private ProductSpecification productSpec; public SaleLineItem (ProductSpecification Spec, int quantity ) { this. productSpec = spec; this. quantity = quantity; } pumlic float subtotal() { return quantity * productSpec.getPrice(); } }

218

Class Store package post; class Store { private ProductCatalog productCatalog = new productCatalog(); private POST post = new POST(productCatalog); public POST getPOST () {return post;) }

219

软件开发的典型阶段使用的模型 业务模型

Use Case图Activity图 (工作流 )分析级的Class图 (业务实体)

分析模型用例事件流补充规范Sequence Collaboration 图Class 图

Sequence, Collaboration 图Class 图StatechartComponentDeployment

设计模型 测试模型

Use Case 图StatechartClass 图