Object Oriented Design
This presentation is the property of its rightful owner.
Sponsored Links
1 / 219

第 5 章 面向对象的设计 PowerPoint PPT Presentation


  • 175 Views
  • Uploaded on
  • Presentation posted in: General

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 面向对象设计内容.

Download Presentation

第 5 章 面向对象的设计

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


5

Object Oriented Design

第5章 面向对象的设计

面向分析

面向设计

做什么?需求领域的调查

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


5

Object-Oriented Design

内容

5.1 面向对象设计概述

5.2 软件设计的体系结构

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

5.4 对象设计

5.5 数据管理的设计

5.6 人-机交互的设计

5.7 任务管理的设计


5 1 3

5.1.3 面向对象的设计方法

5.1 面向对象设计概述

5.1.1 面向对象设计内容

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


5

5.1 面向对象设计概述

5.1.1 面向对象设计内容

在已建立概念类图(对象分析模型)的基础上,

进一步优化类图,确定实现的逻辑模型。

面向对象的设计包括:

  • 体系结构的设计

  • 对象的设计

  • 数据管理的设计

  • 人-机交互的设计

  • 任务管理的设计


5

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

要回答的问题

  • 领域过程是什么

  • 用例、活动图

分析阶段的制品

  • 对象(概念)模型

  • 领域中的概念和术语是什么

  • 顺序图等

  • 系统事件和操作是什么

  • 功能模型

  • 系统操作做了什么

设计阶段的制品

要回答的问题

  • 协作图

  • 状态图

  • 对象间的通讯细节

  • 设计类图

  • 设计软件实现的类图


5

设计

软件类,不是

概念的一部分

分析

Dialer拨号器

Dialer拨号器

实体

类名

- digits:Vector

- nDigits:int

digits

nDigits

实体

信息

属性/成员变量

+ digit(n:int)

# recordDigit

( n:int):boolean

实体

职责

操作/成员函数

概念记号

一个Dialer代表了一次拨号的事件,它有digits

  • 图5-2 设计的类图

public class Dialer

{

private Vector digits;

int nDigits;

public void digit(int:n);

protected boolean

recordDigit(int n);

}

概念的内涵

概念应

用的一

组实例

Dialer1

Dialer 2

Dialer3

Dialer 4

图5-1 分析的类图


5 1 31

5.1.3 面向对象的设计方法

(1) Coad & yourdon 方法 (COA91)

问题域部分

数据管理部分

人机交互部分

任务管理部分

(2)Rumbaugh方法(RAM91)

系统设计(System Design)

对象设计(Object Design)


5

5.1.4 设计上的几个原则

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

就一个类而言,应该仅有一个引起它变化的原因.

职责:“变化的原因”(a reason for change)

(1) 分离类的职责

若一个类承担的职责过多,就等于把

这些职责耦合在一起.

这种耦合会导致脆弱的(fragile)设计

当变化发生时,设计会遭到破坏.如图:


5

Computationa

Geometry

Application

Graphical

Application

Rectangle

+draw()

Geometric

Rectangle

+area():double

GUI

图 5-4 分离的职责

Computationa

Geometry

Application

Rectangle

+draw()

+area():double

Graphical

Application

绘图

计算几何形状

GUI

计算和绘图在一起.

计算包含GUI代码,

C++把GUI代码链接起来,Java GUI的Class文件必须被部署到目标平台上.

图 5-3 多于一个的职责


5

(2) 分离接口中的职责

例 违反单一职责的程序:

inteface Modem

{

//调制解调器的连接处理

public void dial ( string pno);

public void hangup ();

//发送、接收函数的数据通信

public void send ( char c);

public void recv ();

}

该接口声明的4个函数都是调制解调器具有的功能.

有问题?


5

该接口程序有两个职责:

要不要分离?

调制解调器的连接处理

两个函数的数据通信.

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

    分离.

  • 若程序变化影响连接函数签名(signature),

    调用和类要重新编译,增加了部署的次数,

    这两个职责应分离.

  • 变化实际发生了才有意义,若无征兆,去应

    用单一职责,是不明智的.


Modem

分离调制解调器Modem的接口

<<interface>>

Connection

+dial(pno:String)

+hangup()

<<interface>>

Data Channel

+send(:char)

+recv():char

Modem

Implementation

图 5-5 分离的Modem接口

ModemImplementation类耦合了两个职责,这不是所

希望的,但,如硬件等原因,这种耦合可能是必要的. 对于

应用的其余部分,通过对接口的分离己经解耦了概念。

可以把ModemImplementation类看成是一个杂凑物,

谁也不依赖它,除了Main外,谁也不知道它的存在.


5

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

Employee

+CalculatePay

+Store

Persistence

Subsystem

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

违反单一职责。为什么?

Employee类包含了业务规则和对于持久化的控制.

业务规则会多变,持久化的方式不会如此变化,且变化原因也不一样,不能将其放在一起。


5

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

Employee

+calculatePay 计算薪水

+calculateTaxes 计算税金

+writeToDisk 在磁盘上续写自己

+readFromDisk

+createXML 进行XML格式相互转换

+parseXML

+displayOnEmployeeReport 显示各种报告

+displayOnPayrollReport

+displayOnTaxReport


5

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);

}


5

一个可行的结构

Employee XMLConverter

Employee

+EmployeeTo XML+XMLToEmployee

+calculatePay+ calculateTaxes

PayrollReport

Employee Database

TaxReport

+writeEmployee

+readEmployee

EmployeeReport

图 5-7 业务的隔离类图


2 ocp the open closed principle

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

典故:一个被分割的两删门,每一部分都可

以独立的开放或封闭.

遵循OCP原则设计的模块具有两个主要特征:

怎样的设计,才能面对需求的变化只是添加

新的代码,保持相对稳定,不改动正在运行的程序.

软件实体(类、模块、函数等)应该是可以扩

展的,但是不可修改.


5

  • 对于扩展是开放的

    (Open for extension)

    表示模块的行为是可扩展的,即当需求

    变更时,可以改变模块的功能.

  • 对于更改是封闭的

    (Closed for modification)

    对模块进行扩展时,不必改动模块的

    源代码。

    以上两点出现了矛盾


5

Server

Client

图 5-8 违背OCP原则

<<interface>>

Client Interface

Client

Server

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

关健是抽象

Client和Server类都是具体类, Client类使用Server类.

若Client类使用另一个Server

类,就要改动Client类使用

Server类的地方。

  • c++,Java等OOPL语言具有抽象类,其任意行为

    可能出现在派生类中。

因为抽象类和它们的客户Client关系要比实现它们的类关系更密切。

抽象接口命名为ClientInterface而不命名为Abstract Server?


Employeedb

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

EmployeeDB

《API》

TheDatabase

Employee

+readEmployee

+writeEmployee

门面直接处理

数据库的API

图 5-10 违背OCP原则

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


5

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

《interface》

EmployeeDB

Employee

+readEmployee

+writeEmployee

《API》

TheDatabase

Employee

Database

Implementation

UnitTest

Database

图 5-11 遵守OCP原则


5

例:Shape(形状)应用程序.

在标准GUI上按照特定顺序绘制园和正方形.

创建一个列表,列表由按适当顺序排列的园和正方

形组成,程序遍历该列表,依次绘制每个园和正方形.

违反OCP实例 Circle/Square问题

shape.h

enum ShapeType { circle,square };

Struct Shape

{

ShapeType itsType;

}

circle.h

Struct Circle

{

ShapeType itsType;

double itsRadius(半径);

point itsCenter(中心点);

};


5

square.h

Struct Square

{

ShapeType itsType;

double itsSide; (边)

point itsTopLeft; (左边的顶点)

};

drawAllShapes.cc

Typedef struct Shape *ShapePointer;

Void DrawAllShapes (ShapePointer list,int n)

DrawAllShapes函数不符合OCP,它对于新的形

状类型添加是不封闭的.每增加添加一个新的形状

类型,就改变了这个函数,要对新类型判断.


5

在应用中switch函数重复

出现,但完成工作略有差异.

不同形状依赖于enum声明.

增加一个新成员都要重新编

译、部署(DLL、共享库、二

进制组件)一个简单行为导致连锁改动,是僵化的.

{

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.方法是牢固的,也很槽糕.


5

  • 遵循OCP规则: 定义一个抽象类Shape

    及抽象方法Draw.Circle和Square都从Shape类派生.

    class Shape

    {

    public:

    virtual void draw() const = 0;

    };

    class Square: public Shape

    {

    public:

    virtual void draw() const;

    };


5

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();

}


5

  • 模块可以操作一个抽象体.由于模块依赖于一

    个固定的抽象体,所以它对于更改可以是封闭的.

    同时,通过从这个抽象体派生,也可以扩展此

    模块的行为.

上系统强调一个形状的顺序比强调类型更重

要,很难做到严格封闭.

一般而言,无论模块是多么的封闭,都会存在

无法对所有情况都适用的模型.

一般做法:找出一个模块易变化的部分,构造

抽象类,隔离其变化.


5

OCP(开放-封闭)是面向对象的核心,遵守这个原则会使设计具有灵活性、可重用性、可维护性.

但是,并不意味着对应用程序的每个部分都

要进行抽象.

正确的做法是,开发人员应该仅仅对程序中

呈现出频繁变化的那些部分做出抽象.

拒绝不成熟的抽象和

抽象本身一样重要.


5

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

DIP原则

  • 高层模块不应依赖低层模块,二者都应依赖

    于抽象

  • 抽象不应依赖于细节,细节应该依赖于抽象.

    好处:

    • 底层模块改动不会影响到高层模块

    • 增加各层模块的独立性


5

Policy Layer

层次化

Mechanism Layer

Utility Layer

图 5-12 层次化方案

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


5

<<interface>>

Policy Service

Interface

Policy Layer

<<interface>>

Policy Service

Interface

Mechanism

Layer

Utility

Layer

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


5

  • 上图中每个较高层次都为它的服务声明一个

    抽象接口,较低层次实现抽象接口

  • 高层类通过抽象接口使用下一层,高层不依赖

    于低层,低层依赖于在高层声明中的抽象接口.

  • 倒置不仅是依赖关系的倒置,也是接口所有权

    的倒置.

  • 依赖倒置可以应用于任何存在于一个类向另一

    个类发送消息的地方.


5

依赖于抽象

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

Button对象感

知外部环境变

化.收到poll消

息,判断是否被

用户按下。

Lamp

+TurnOn()

+TurnOff()

Lamp对象

会影响外部环境.

Button

+Poll()

图 5-14 不成熟的Button和Lamp模型

Button .Java 代码

Public class Button

{

private Lamp itsLamp;

public void poll ()

{

if ( /* some condition */ )

itsLamp.turnOn();

}

}

高层策略依赖于低层模块.

抽象依赖于具体细节.


5

<<interface>>

ButtonServer

+turnOff()

+turnOn()

Button

+poll()

# 程序中所有依赖关系都应

该终止于抽象类或接口。

# 任何类都不应当从具体类中派生。

# 任何方法都不应该覆写它的任何

基类中己经实现了的方法。

# 若一个具体类(如描述字符串的类)

是稳定的,也不会创建其他类似的派生类,直接依赖它不会造成损害。

  • 找出潜在的抽象

Lamp

图 5-15 对Lamp应用

依赖倒置原则

接口没有所有者,可以被许多不同的客户、服户者使用。

这样接口需要放一个单独的组(group)中。C++中把接口放在一单独的namespace和库中。在Java中把接口放在一单独的package中


5

一个控制(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);

}

}

熔炉示例

代码表示了底层

细节,不能重用


5

《 function 》

Regulate

  • 倒置这种关系:

调节器函数Regulate

接受两个接口参数,

温度计(Thermometer)

接口可以读取,加热器

(Heater)接口可以启动

和停止.

《interface》

Thermometer

+read()

《interface》

Heater

+engage()

+disengage()

IO Channel

Thermometer

IO Channel

Heater

图 5-16 通用的调节器


5

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();

}

}

  • 倒置依赖关系,高层的调节策略不再依赖于任何温

    度计或熔炉的特定细节,该程序有很好的可重用性。


5

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();

}

}

  • 上面使用动态的多态性(抽象类或接口)实现了通用的

    调节器软件。同样 ,还可以使用C++模板(template)

    提供静态形式的多态性。


5

  • 在C++中,read ,engaged ,disengaged方法可

    以是非虚的。任何声明了这些方法的类都可以作为模

    板参数使用,不必从一个公共基类继承。

  • 在作为模板Regulate不依赖于这些函数的特定实现。

    只要出替换类HEADER,THERMPMETER中的方法。

  • 模板的缺点是:

    HEADER,THERMPMETER的类型不能在运行中更改,

    对于新类型的使用会重新编译和部署 。

  • 应当先使用动态特性


5

  • 使用传统的过程化程序设计所创建出来的依赖关系结

    构是依赖于细节的。

  • 程序的依赖关系没倒置就是过程化的设计。

    程序的依赖关系倒置了,就是面向对象的设计。

  • 正确应用依赖关系的倒置对于创建可重用的框架是

    必须的。

    对于构建在变化方面富有弹性的代码也是非常重要的。

    抽象和细节分离,代码易维护。


5

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

目的:

  • 处理胖(fat)接口(类的接口不是内聚的cohesive),

    把胖接口分解成多组方法.

    这样一些客户可以使用一组成员函数.

  • 若有一些对象不需要内聚的接口,ISP建议

    客户程序应看到具有内聚接口的抽象基类.


5

(1)接口污染

安全系统中的Door(门)对象,可以被加锁

和解锁,且 Door对象知道自己是开还是关.

class Door

{

public:

virtual void Lock() = 0;

virtual void Unlock() = 0;

Virtual bool IsDoorOpen() = 0;

};

该类是抽象的,客户程序可以使用那些

符合Door接口的对象,而无需依赖Door的实现.


5

  • 如果门开着的时间过长,会发出警报声,设一个

    TimedDoor对象和Timer(定时器)对象交互.

    class Timer

    {

    public:

    void Register (int timeout,TimerClient* client);

    };

    class TimerClient

    {

    public:

    virtual void TimeOut () = 0;

    }; 希望得到超时的通知可以调用Register函数.

    TimeOut函数会在超时到达时被调用.


5

<<interface>>

Timer Client

+Timeout

  • 怎样将TimerClient类和TimedDoor类联系起来 ?

    才能在超时时通知到TimedDoor中相应的处理?

    给出一种方案:

Timer

如果创建了无需定

时功能的Door派生

类,则在这些派生

类中就必须提供

TimeOut方法的退

化(degenerate)实

现,违反了LSP

(替换原则).

0..*

TimerClient可

以把自己注册到

Timer中,且可到

接收Timeout消息.

Door

Door类依赖于

TimerClient.可

是并不是所有的

种类的Door都需

要定时功能.

TimedDoor

图 5-17 位于层次结构

顶部的TimerClient


5

此外使用这些派生类的应用程序即使不

使用TimerClient类定义,也要引入它,具有

复杂性和不必要重复的臭味.

Door的接口被一个它不需要的方法污染了.

Door中加入这个方法只是为子类带来方

便.若每次子类需要一个新方法时,就将其加

到基类中,使它变胖.

C++、Java静态类型语言中是常见的.


5

(2) 分离客户就是分离接口

Timer使用TimerClient,而操作门的类

使用Door.既然客户程序是分离的,接口也

应分离.因为客户程序对于它们使用的接口

施加有作用力.

客户对接口施加的反作用力

一般考虑软件中引起变化的作用时,通

常的变化怎样影响其使用者. 考虑接口如果

TimerClient的接口改变了, TimerClient的

使用者要做什么改变?


5

有时接口改变的,正是它们的使用者.

如Timer的使用者会注册多个超时通知请求,

当检测到门打开发送一个注册消息,请求一个超

时通知.但在超时到达前,门又关上了,而后又被

打开,导致原先超时到达前又注册一个新的超时

请求,最后,最初的超时到达, TimedDoor的

TimeOut方法被调用, 错误发出报警.

怎样改正上面的错误?


5

增加一个标识码,以便知道该响应哪个超时请求

class Timer

{

public:

void Register (int timeout,int timeOutID,

TimeClient* client);

};

class TimerClient

{

public:

virtual void timeOut (int timeOutID) = 0;

};


5

(3) 分离接口的方法

不应强迫客户依赖于它们不用的方法.

  • 使用委托分离接口

<<interface>>

Timer Client

+Timeout

Door

Timer

0..*

当TimedDoor要

向Tim对象注册一

个超时请求时,它就

创建一个

DoorTimerAdapter

(适配器)并且把它

注册给Timer.

当Timer对象发送

TimeOut消息给

DoorTimerAdapter

时, DoorTimer

Adapter把这个消

息委托给

TimedDoor.

TimedDoor

+DoorTimeout()

Door Timer

Adapter

+Timeout()

<<creates>>

DoorTimerAdapter会将TimerClient接口转换成

TimedDoor接口.

图 5-18 定时器适配器


5

TimedDoor.cpp

class TimedDoor: public Door

{

public: //注册一个超时请求

virtual void DoorTimeOut(int timeOutID);

};

ClassDoorTimeAdapter: public TimerClient

{

public:

doorTimerAdapter (TimedDoor& theDoor):

itsTimedDoor(theDoor)

{ }

vistual void TimeOut (int timeOutID)

{ itsTimedDoor.DoorTimeOut(ID); }

private:

TimedDoor& itsTimedDoor;

}


5

该方案不太优雅:

  • 每次注册一个超时请求时,都要创建一个新对象.

  • 委托处理会导致一些很小到仍然存在的运行时间

    和内存开销.

    对于嵌入式实时控制系统值得考虑.

  • 当DoorTimerAdapter对象所做的转换是必须的,

    或者不同的时候会需要不同的转换时,会选择这

    个方案.


5

分离接口的方法

  • 使用多重继承分离接口

<<interface>>

Timer Client

+Timeout

Door

Timer

0..*

TimedDoor

+TimeOut

图 5-19 多重继承TimedDoor


5

TimedDoor.cpp

Class TimedDoor: public Door,public TimerClient

{

public:

virtual void DoorTimeOut(int timeOutID);

};

两个基类客户程序都使用TimedDoor,但都

不依赖于TimedDoor类,它们通过分离的接口使

用同一个对象.(通常会选择这个解决方案).


5

输出信息被转换成不同方式,显示在屏幕上;盲文书写

板上;语言合成说出来。

  • ATM用户界面的例子

《interface》

ATM UI

Screen UI

Braille UI

Speech UI

图 5-20 ATM 界面层次结构


5

Transaction

{abstract}

+Execute()

这个设计如何?

所有的类都依赖于

UI接口

  • ATM 操作层次结构:存款、取款、转帐

Deposit

Transaction

Withdrawal

Transaction

Transfer

Transaction

《interface》

UI

+RequestDepositAmount()

+RequestWithdrawalAmount()

+RequestTransferAmount()

+InformInsufficientFunds()

图 5-21 ATM 操作

层次结构


5

Transaction

+Execute()

  • ATM 操作层次结构:存款、取款、转帐

Withdrawal

Transaction

Deposit

Transaction

Transfer

Transaction

《interface》

Withdrawal UI

+RequestWithdrawalAmount()

+InformInsufficient

Funds()

《interface》

Transfer UI

+RequestTransfer

Amount()

《interface》

Deposit UI

+RequestDeposit

Amount()

《interface》

UI

+RequestDepositAmount()

+RequestWithdrawalAmount()

+RequestTransferAmount()

+InformInsufficientFunds()

图 5-22 分离

ATM UI接口


5

下面是课程登记系统。有哪些方法不妥?

需求变化了,会出现什么问题?

《parameter》

《parameter》

思考:

Enrollment

Report

Generator

Student

Enrollment

+getName

+getDate

+prepareInvoice

+postPayment

Accounts

Receivable

0..*

Course

图 5-23 未分隔的课程登记系统一个类图

如何改进这个类图?


5

《interface》

Enrollment

Accounts

+ prepareInvoice

+postPayment

《interface 》

Enrollment

Report

+getName

+getDate

《parameter》

《parameter》

Enrollment

Report

Generator

Student

Enrollment

+getName

+getDate

+prepareInvoice

+postPayment

Accounts

Receivable

0..*

Course

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


5

小 结

* 单一职责原则

SRP (SRP,Single-Responsibility Principle)

就一个类而言,应该仅有一个引起它变

化的原因。分离类的职责,分离接口的职责。

* 开放—封闭原则

OCP (The Open-Closed Principle)软件实体(类、模块、函数等) 对于扩

展的,是开放的。

对于更改是封闭的,关键是抽象。


5

* 依赖倒置原则

DIP (Dependency-Inversion Principles)

高层模块不应依赖于低层模块,即抽象

不应依赖于细节,细节应该依赖于抽象。

* 接口隔离原则

ISP (see Interface Segregation Principles)

对胖接口分解。

不应该强迫客户依赖于它们不用的方法。

接口属于客户,不属于它所在的类层次结构。


5 2 2

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

5.2 软件设计的体系结构

5.2.1 什么是“软件体系结构”

5.2.3 体系结构风格


5

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

  • “体系结构”一词起源于建筑学

    • 如何使用基本的建筑模块构造一座完整的建筑?

  • 包含两个因素:

  • 基本的建筑模块:砖、瓦、灰、沙、石、

    预制梁、柱、屋面板…

  • 建筑模块之间的粘接关系:如何把这些

    “砖、瓦、灰、沙、石、预制梁、柱、

    屋面板”有机的组合起来形成整体建筑?


5

1) 计算机硬件系统的“体系结构”

(1) 两个因素:

  • 基本的硬件模块:控制器、运算器、内存储器、

    外存储器、输入设备、输出设备…

  • 硬件模块之间的连接关系:总线

  • 如何将设备组装起来形成完整的计算机硬件系统?

(2) 计算机体系结构的风格:

  • 以存储程序原理为基础的冯·诺依曼结构

  • 存储系统的层次结构

  • 并行处理机结构

  • ……


5

(3) “体系结构”的共性

  • 一组基本的构成要素——构件

  • 要素之间的连接关系——连接件

  • 要素连接之后形成拓扑结构——物理分布

  • 作用于要素或连接关系上的限制条件——约束

  • 质量——性能


2 software architecture

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

  • 软件体系结构 = 构件 + 连接件 + 约束

    • Architecture = Components + Connectors + Constrains

  • 提供了对软件系统的结构、行为和属性的抽象

  • 反映系统开发中具有重要影响的设计决策

  • 便于各种人员的交流

  • 完成系统既定的功能和性能需求。


1 component

Interface

Component

C

Service

(1) 构件(Component)

  • 任何在系统运行中承担一定功能、发挥一定作用

    的软件体都可看作是构件。

  • 构件是具有某种功能的可复用的软件结构单元,

    表示了系统中主要的计算元素和数据存储。

  • 程序函数、模块

  • 对象、类

  • 文件

  • 相关功能的集合

  • ….

图 5-25 构件


2 interface

(2) 接口(Interface)

  • 构件作为一个封装的实体,只能通过其接口与外

    部环境交互;

    • 芯片的管脚

  • 内部结构则被隐藏起来(Black-box);

  • 一个构件至少有一个接口,

  • 一个构件可以提供多重接口:

    • 接不同的管脚,实现不同的功能。

  • 构件接口与构件实现严格分开.


3 service

(3) 服务(Service)

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

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


4 connector

C2

C1

(4) 连接件(Connector)

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

    • 管道(pipe)

    • 过程调用(procedure call)

    • 事件广播(event broadcast)

    • 客户机-服务器(client-server)

    • 数据库连接(SQL)

  • 连接件也可看作一类特殊的构件,区别在于:

    • 一般构件是软件功能设计和实现的承载体;

    • 连接件是负责完成构件之间信息交换和行为联系

      的专用构件。


5 protocol

(5) 连接的协议(Protocol)

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

  • 连接的规约是建立在物理层之上的有意义信息

    形式的表达规定。

    • 对过程调用: 参数的个数和类型、

      参数排列次序

    • 对消息传送:消息的格式

  • 目的:使双方能够互相理解对方所发来的

    信息的语义。


5

(6) 连接的种类

  • 从连接目的看:

    • 操作/过程调用;

    • 控制/事件/消息发送;

    • 数据传输;

  • 除了连接机制/协议的实现难易之外,影响

    连接实现复杂性的因素之一是“有无连接的

    返回信息和返回的时间”,分为:

    • 同步 (Synchronous)

    • 异步 (Asynchronous)


5

体系结构是对复杂事物的一种抽象。

良好的体系结构是普遍适用的,它可以高

效地处理多种多样的个体需求。

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

  • 体系结构在一定的时间内保持稳定。

  • 只有在稳定的环境下,人们才能干点事情。

  • 对需求变化,只做些皮皮毛毛的修改.


5 2 3

5.2.3 体系结构风格

独立组件

事件系统

通信进程

隐式调用 显示调用

数据流

以数据为中心

成批顺序流 管道和过滤器

数据仓库 黑板

虚拟机

调用和返回

主程序

和子程序

解释器 基于规则的系统

面向

对象

分层

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


5

主程序

子过程3

子过程1

子过程2

1) 主程序-子程序结构

  • 是结构化程序设计

    的一种典型风格。

    从功能的观点设

    计系统,通过逐

    步分解和逐步细

    化,得到系统体

    系结构。

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

  • 构件: 主程序、子程序

  • 连接件: 调用-返回机制

  • 拓扑结构:层次化结构

  • 本质:将大系统分解为若干模块(模块化),主程序

    调用这些模块实现完整的系统功能。


5

2) 层次结构

(1) 现实世界里邮政系统

发信者

收信者

书写信件

通信者活动

界面

通信者活动

界面

信箱取信

粘贴邮票

投递进信箱

阅读邮件

收集信件

邮件投递

邮局服务业务

邮局服务业务

加盖邮戳

邮件分检

邮件分检

接收邮件

邮件打包

邮局转运业务

邮局转运业务

转送运输

邮件拆包

部门

转送邮局

选择运输

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

接收邮件

路径(路由)

图 5-28 邮政系统


5

通信子网

(2) 网络的分层模型

网络层

应用层

7

应用层

报文流

6

表示层

表示层

报文流

会话层

5

会话层

报文流

传输层

传输层

4

段流

转接节点

转接节点

分组流

网络层

网络层

3

网络层

网络层

网络层

协议

链路层

帧流

链路层

2

链路层

链路层

链路层

协议

物理层

1

物理层

物理层

比特流

物理层

物理层

协议

图 5-29 网络协议层


5

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

用户

Shell解释运行

语言处理、系统工具、系统应用程序

系统调用

操作系统内核(System kernel)

基本输入输出(BIOS)

计算机硬件(CPU、存储器、I/O等)

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


5

OutputStream

+write()

FilterOutputStream

+write()

研究生

大学生

DataOutputStream

BufferedOutputStream

FileOutputStream

+write()

+write()

+write()

中学生

图 5-32 Java的I/O层次图

小学生

举出一个层次结构的实例

图 5-31 学历

层次结构


5

(4) 客户机/服务器结构(Client/Server, C/S)

  • 两层C/S

  • 三层C/S

  • 多层C/S

客户

界面

数据库

服务器

客户

界面

业务逻辑

服务器

数据库

服务器

客户

界面

Web

服务器

业务逻辑

服务器

数据库

服务器

图 5-33 C/S 结构


5

两层C/S物理结构

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


5

C/S 结构的处理流程

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

业务处理开始

输入数据

请求按钮

数据存取请求

输出数据

业务处理结束

全部处理结束

表示层

业务处理程序

业务处理开始

SQL请求开始

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

数据存取请求

DBMS执行SQL

SQL请求结束

业务处理结束

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

数据存取程序

数据层

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


5

服务器的活动

等待

  • 服务器的初始化

  • 开始监听,和客机相连

开始监听

停止监听

  • 处理随时发生的事件:

  • 客户机的连接;

等待连接

对连接的客户机响应;

处理客户机的连接断开。

接受

连接

  • 停止监听,为己有的继续

  • 服务,但不接受新的客户

  • 机连接。

处理连接

Do:响应消息

  • 关闭

处理连接断开

图 5-36 服务器的活动


5

客户机的活动

初始化

服务器的网络地址

连接失败或服务器

报绝连接,再试或放弃.

启动到服务器的连接

响应服务器激发

的事件do:响应

消息处理服务器

断开连接

与用户交互必

要时,向服务

器发送消息

终止

图 5-37 客户机的活动


5

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

Client1

Client2

Server

监听连接

连接

发送消息

连接

发送响应

断开连接

发送消息

断开连接

终止监听

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


5

  • Server Request()

异步

RespondClient()

  • Server Request()

同步

  • RespondClient()

客户/服务器的连接方式

同步连接方式 异步连接方式

使用过程的连接; 使用消息的连接

服务器

客户


5

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

客户机与服务器进行通信时,实际上使用两

种语言进行对话,应当有些规则(描述客户机与

服务器必须交换的消息序列)来保证.

两种语言与对话规则合在一起称为协议

(PROTOCOL).

在简单系统中协议仅仅是服务请求和响应的

列表.


5

两层C/S结构优缺点

  • 专用性、交互性强

  • 存取数据安全

  • 网络通讯量低、速度快

  • 难以扩展至大型企业广域网或Internet

  • 客户端应用程序仍显肥胖

  • 易造成网络瓶颈。


5

数据

库服

务器

应用服务器

数据库

服务器2

数据库

服务器1

应用

服务

Internet

Intranet

...

...

用户2

用户n

用户1

三层C/S结构

管理对数

据库数据

的读写

将具体的业

务处理逻辑

编入程序

客户端

图 5-39 三层C/S结构

表示层是应用的用户接口部分,

它担负着用户与应用间的对话功能。


5

三层C/S结构处理流程

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

89


5

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

  • 表现层:

    浏览器

  • 逻辑层:

    • Web服务器

    • 应用服务器

  • 数据层:

    数据库

    服务器

图 5-41 B/S体系结构


5

B/S 结构处理流程

表现层

应用层

数据层

客户端

页面

脚本

应用服

务器层

页面

数据库

访问层

表现层

数据库

数据服务器

浏览器

Web服务器

应用服务器

图 5-42 B/S 结构处理流程

在Web服务器端,程序员要用脚本语言编写响应页面,例如

用Microsoft的ASP语言查询数据库服务器,将结果保存在

Web页面中,再由浏览器显示出来。

用户运行某个应用程序时,只需在客户端浏览器中键

入相应网址(URL),调用Web服务器上的应用程序,对数据

库进行操作, 完成相应处理,结果通过浏览器显示给用户.

91


5

结构的优点是:

  • B/S结构的客户端只是提供友好界面的浏览器,

    利于推广。

  • B/S成为真正意义上的“瘦客户端”,从而具备

    了很高的稳定性、延展性和执行效率。

缺点

  • B/S体系结构缺乏对动态页面的支持能力,没

    有集成有效的数据库处理功能。

  • 采用B/S体系结构的应用系统,在数据查询等

  • 响应速度上,要远远地低于C/S体系结构。


6 c s b s

(6) C/S+B/S 混合体系结构

  • 企业内部用户通过局域网直接访问数据库服务器

    • C/S结构;

    • 交互性增强;

    • 数据查询与修改的响应速度高。

为了克服C/S与B/S各自的缺点,发挥各自的优点,

在实际应用中,通常将二者结合起来。

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

    服务器

    • B/S结构;

    • 用户不直接访问数据,数据安全。


C s b s

企业外部

企业内部

B/S结构

数据库

服务器

修改和维护

工作站

Web服务器

Internet

C/S结构

查询和浏览

工作站

内部局域网

Internet

用户

查询和浏览工作站

修改和维护工作站

C/S+B/S 模型

图 5-43 C/S+B/S 结构


5

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

5.3.1 逻辑结构

5.3.2 物理结构


5

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

5.3.1 逻辑结构

  • 将功能合理的进行分组

  • 说明它们是如何工作的:

    哪一个类存在?

    类之间如何联系的?

    如何协作来完成系统的功能?

    有什么约束?


5

业务对象

《Facade》服务接口

控制业务对象

外部业务对象

实体业务对象

数据库

《Facade》

对象到关系转换包

SQL产生器

三层的逻辑构架

UI

Microsofte基类

ActiveX组件

应用窗口

图 5-44 设计的逻辑架构


5

客户支持系统

订单执行子系统

订单输入子系统

订单

退货条目

发运人

运输

订单条目

订单交易

客户维护子系统

目录维护子系统

订单

目录

客户

库存管理子系统

图 5-45 客户支持系统的一个包图


5

1)包 (package)的表示

将设计元素分组的通用组织结构

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

Types

元素

在包内

包的内

容隐藏

Integer

Types

Time

被输

入包

抽象包的所有

公有和受保的

元素对实现包

都是可见的。

Imported

Abstract

输入(import)依赖

输入包

Importing

Implementing

图 5-46 包的表示


5

  • 包拥有的元素

  • 包的依赖关系:

接口

组件

节点

协作

用例图

以及其他包.

包间的依赖关系

包层依赖关系

访问与引入依赖关系

包的泛化关系


5

Products

Sales

Authorization

Transactions

授权事物

领域概念

  • 顶层领域概念包

核心/混杂

Payment

图 5-47 顶层领域概念包


5

核心/混杂

Store

Address

name

  • 核心包

Houses

POST

Manager

1

1..*

1..*

Employs

1

图 5-48 核心包

Sales

Core Elements::

POST

Captures

Sale

1

1

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


5

支付

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

Payment

Authorization

Service

授权

服务

Credit

Authorization

Service

Cash

Payment

Credit

Payment

Check

Payment

Check

Authorization

Service

Accounts

Receivable

Drivers

License

Credit

Card

Check

图 5-50 领域支付概念包


5

产品

Sales::

SalesLineItem

0..1

*

Described-by

1

Product

Specification

description

price

UPC

1

1..*

ProductCatalog

1

Describes

RecordsSale-of

*

Core::

Store

Stocks

Item

1

1

*

图 5-51 产品包图


5

《Interface》

IPServer

2)

IPServer

Database Services

commit()

getObject()

insert()

rollback()

包指明与客户端

的接口如何实现

图 5-52 一个包的接口

  • “接口”(Interface)是OO中一个很重要的概念,它支持对OO中“封装”的实现:接口与实现分离;

  • 在使用接口的同时,需要为接口定义相应的

    “实现”,使用realization关系表示。

  • 在使用接口的时候,可以和普通类一样使用。

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


3 import

3) 包的导入(import)

Types

  • 元素导入:将包内任一个元素导入到另一个包中

Program包导入了Time

数据类型

《Datatype》

Integer

《 import 》

《Datatype》

Time

Program

  • 包导入:一次导入整个包里的所有元素

Employee

和Order可

以直接使用

Address和

Date

Order System

Domain DateType

Employee

-address:Address

Address

《 import 》

Order

-orderDate:Date

Date

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


5

4) 如何进行包的设计

  • 在UML的概念中,包可以用作包容一组类的容器。

    通过把类组织成包,在更高层次的抽象上来理解

    设计。通过包来管理软件的开发和发布。目的就

    是根据一些原则对应用程序中的类进行划分,然

    后把那些划分后的类分配到包中。

  • 但是类之间存在依赖关系,这些依赖关系还经常

    会跨越包的边界。因此,包之间也会产生依赖关

    系。包之间的依赖关系展现了应用程序的高层组

    织结构,应该对这些关系进行管理。


5

这就提出了很多问题:

(1) 在向包中分配类时应该依据什么原则?

(2) 应该使用什么设计原则来管理包之间的

关系?

(3) 包的设计应该先于类呢(自顶向下) ?

还是类的设计应改先于包(自底向上)?

(4) 如何实际表现出“包”?

在C++中如何表现?

在Java中如何表现?

在某种开发环境中又如何表现?

(5) 包创建好后,应当将它们用于何种目的?


5

  • 现介绍包的6个设计原则,涉及包的创建、相

    互关系的管理以及包的使用。

  • 前3个原则,包的内聚性原则:

    重用发布等价原则、

    共同重用原则、

    共同封闭原则

    是用来指导如何把类划分到包中的。

  • 后3个原则,包的耦合性原则:

    无环依赖原则、

    稳定依赖原则、

    稳定抽象原则

    是用来处理包之间的相互关系的。


5

包的内聚性原则

  • 帮助开发者决定如何把类划分到包中。

    这些原则依赖于这样的事实:

    至少已经存在一些类,并且它们之间的相

    互关系也已经确定。因此,这些原则是根据

    “自底向上”的观点对类进行划分的。

(1)重用发布等价原则 REP(Reuse-Release Equivalence Principles) 由于重用性必须是基于包的,所以可重用 的包必须包含可重用的类。 重用的粒度就是发布的粒度


5

(2)共同重用原则

CRP (Common-Reuse Principle)

  • 一个包中的所有类应该是共同重用的。

  • 如果重用了包中的一个类,那么就要重用包

    中的所有类。这个原则规定了趋向于共同重

    用的类应该属于同一个包。

  • 例:容器类以及与它关联的迭代器类。

    这些类彼此之间紧密耦合在一起,因此

    必须共同重用。所以它们应该在同一个包中。


5

(3)共同封闭原则

CCP (Common-Closure Principle)

  • 包中的所有类对于同一类性质的变化应该

    是共同封闭的。一个变化若对一个包产生

    影响,则将对该包中的所有类产生影响,

    而对于其他的包不造成任何影响。

  • 这条原则规定了:

    一个包不应该包含多个引起变化的原因。

  • 如果从可维护性的角度,一个应用中的代码

    必须更改,应把更改都集中在一个包中。


5

  • 如果两个类之间有非常紧密的绑定关系,不

    管是物理上的还是概念上的,它们总是会一

    同进行变化,它们应该属于同一个包中。

    这样做会减少软件的发布、重新验证、

    重新发布、重新发行的工作量。

  • 值得注意的是包的划分可能会动态的改变,如

    当项目的重心从可开发性向可重用性转变时,

    包的组成很可能会变动并随时间而演化。


5

MyApplication

MyTasks

Task

Window

Windows

Tasks

MyDialogs

Database

Message

Window

包的耦合性原则

(4)无环依赖原则ADP

(Acyclic-Dependencies Principle)

在包的依赖关系图中不允许存在环。

包是节点(node),

依赖关系是有向边

(directed edge)。

无论从哪个包开始,都无法沿着依赖关系而绕回

到这个包。该结构中没有环。它是一个有向无环图DAG 。

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


5

MyApplication

MyTasks

Message

Window

Task

Window

Windows

Tasks

MyDialogs

Database

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

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


5

MyDialogs

MyApplication

X

Y

MyApplication

MyDialogs

Y

X

《interface》

X Server

解除依赖环

把包的依赖环恢复为一个DAG。有两个主要的方法:

方法1:使用依赖倒置原则

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


5

MyApplication

MyTasks

Message

Window

Task

Window

Windows

Tasks

MyDialogs

Database

aNewPackage

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

方法2:新创建一个MyDialogs和MyApplication

都依赖的包.把MyDialogs和MyApplication

都依赖的类移到这个新包中。

创建新的包,致

使依赖关系结构增长。

当需求改变时这个包

的结构是不稳定的。


5

(5)稳定依赖原则

SDP (Stable-Dependencies Principle)

朝着稳定的方向进行依赖。

  • 稳定性,如果某物“不容易被移动” 就认为它

    是稳定的(韦伯斯特) 。

  • 使软件包难以更改的因素有许多:

    它的规模、复杂性、清晰程度等等。

  • 要使一个软件包难以改变,一个肯定可行的方

    法是让许多其他的软件包依赖于它。

    如,具有很多输入依赖关系的包是非常稳定的,

    它的包能够相容于对它所做的更改。


5

Y

X

X不依赖于任何包,

X是无依赖性的。

没有任何其他的包依赖于Y;

Y是不承担责任的,称Y是

有依赖性的。

图 5-59 Y:一个不稳定的包

图 5-58 X:一个稳定的包


5

Instable

Stable

Instable

并非所有的包都应该是稳定的

  • 如果一个系统中所有的

    包都是最大程度稳定的,

    那么该系统就是不能改

    变的,这不是所希望。

    希望所设计出来的包结

    构中,一些包是不稳定

    的,而另外一些是稳定的。

图 5-60 理想的包配置


5

Stable

Flexible

  • 图4-38展示了违反稳

    定依赖原则SDP的做法。

  • 必须要以某种方式解

    除Stable对Flexible

    的依赖。

    为什么会存在这个依

    赖关系呢?

图 5-61 违反了SDP


5

Flexible

C

Stable

假设Flexible中有

一个类C 被另一个

Stable中的类U使

用(参见图4-39)

U

怎么解决?

图 5-62 糟糕依赖关系


5

Stable

UInterface

Flexible

U

《interface》

IU

C

  • 可以使用DIP(Dependency-Inversion Principles)

    依赖倒置原则来修正这个问题。

接口IU中,声明了

U要使用的所有方法

稳定

保持它必需

的不稳定性

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


5

(6)稳定抽象原则

SAP (Stable-Abstractions Principle)

  • 包的抽象程度应该和其稳定程度一致。

  • 该原则把包的稳定性和抽象性联系起来。

  • 它规定,一个稳定的包应该也是抽象的,

    这样它的稳定性就不会使其无法扩展。

  • 另一方面,它规定,一个不稳定的包应

    该是具体的,因此它的不稳定性使得其

    内部的具体代码易于更改。


5

小结:

*重用发布等价原则

REP (Reuse-Release Equivalence Principles)

重用的粒度就是发布的粒度。

*共同封闭原则

CCP (Common-Closure Principle)

包中的所有类对于同一类性质的变化应该

是共同封闭的。

*无环依赖原则

ADP (Acyclic-Dependencies Principle)

在包的依赖关系图中不允许存在环。


5

* 共同重用原则

CRP (Common-Reuse Principle)

一个包中的所有类应该是共同重用的。如果

重用了包中的一个类,那么就要重用包中

的所有类.

* 稳定依赖原则

SDP (Stable-Dependencies Principle)

朝着稳定的方向依赖。

* 稳定抽象原则

SAP (Stable-Abstractions Principle)

包的抽象程度与其稳定程度一致。


5

包的设计结论:

  • 对概念和语义上相互接近的元素所定义的组

    块放到一个包中

  • 对每一个包找出可以在包外访问的元素,将

    这些元素标记为公有的,把其他的元素标记

    为受保护的或私有的。如果不确定时, 就隐

    藏该元素

  • 确定包与包之间的依赖关系,特别是引入依

    赖。包之间的依赖关系展现了应用程序的高

    层组织结构,应该对这些关系进行管理。


5

  • 包结构不是设计系统时,首先考虑的事情

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

  • 包结构应该是随着系统的增长、 变化而逐步

    演化的。

  • 包的依赖关系图和描绘应用程序的功能之间

    几乎没有关系。相反,它们是应用程序可构

    建性的映射图。这就是为何不在项目开始时

    设计它们的原因。

  • 随着实现和设计的深入, 累积的类越来越多,

    对依赖关系进行管理


5

  • 随着应用程序的不断增长,开始关注创建可

    重用的元素。于是,就开始使用CRP (共同

    重用原则)来指导包的组合。

    最后,当环出现时,就会使用ADP(无环

    依赖原则) 。

  • 包的依赖关系结构是和系统的逻辑设计一

    起增长和演化的。


5

回答问题

1.列出抽象视窗工具(Abstract Windowing Toolkit)

包装(即java.awt)中的抽象类型

Component(部件),

Container(容器),

MenuComponent(菜单部件)


5

java.lang

java.io

java.awt.event

java.awt.image

java.awt

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

Java语言基本包

图 5-64 java.awt包

的依赖包

Java抽象视窗工具图像包


5

4.3.1 逻辑构架

5.3.2 物理结构

描述系统软件、硬件

描述硬件结构:节点及节点间连接

注重实现

1) 构件图 (Component diagram)

构件:定义了良好接口的物理实现单元,

是系统中可替换部分.

表示构件类型的组织以及依赖关系的图


5

(1) 构件和类的不同点

  • 类是逻辑抽象;

    构件是物理抽象,即构件可以位于节

    点(node)上,是类的物理实现

  • 类可以有属性和操作;

    构件通常只有操作,而且这些操作只

    能通过构件的接口才能使用


5

(2) 软件构件类型

  • 一般说来,构件就是一个实际文件,可以有

    以下几种类型:

    • deployment component,如 dll 文件、

      exe 文件、 COM+ 对象、CORBA 对象、EJB、动态 Web 页、数据库表等

    • work product component,如源代码文件,数据文件等,这些构件可以用来产生

      deployment component

    • execution component,可执行的构件


5

  • 二进制代码构件

  • 源代码构件

  • 可执行代码构件

窗口处理子程序

(wnnd.cpp)

窗口处理子程序

(wnnd.obj)

图形库

(graphic.dll )

公共处理子程序

(comhnd.cpp

公共处理子程序

(comhnd.obj)

客户程序

(client.dll )

Main类

(main.cpp )

Main类

(main.obj)

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


5

接口是被软件或硬件

所支持的一个操作集。

(3) 带有接口的构件

实现(Realization)

接口

update

Updatethread.java

使用

依赖(Dependency)

display.java

图 5-66 带有接口的构件


5

MainProgram

ControlObject

FormObject

People

DataBase

Course

Student

Registrar

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


5

用户界面类

控制器

/处理器

业务/领域类

持久类

持久存储

图 5-68 重要类的关系

(4) 开发构件模型

第1步 : 处理业务/ 领域类


5

第2步: 定义类契约——公有接口

第3步: 简化层次结构——继承和聚合的层次结构

第4步 :确定可能的领域构件 (domain Component)

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

服务器类属于同一构件( 接收消息不发送消息)

客户类不属于构件 (发送消息不接收消息)

紧密耦合的类属于同一构件

使构件间的消息流的大小最小化

注意构件的合并

构件应当内聚

构件可能会发生变动

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


5

物理构架

-- 构件图

2) 部署图 (展开图)

描述处理器、设备、软件组件在运行时的构架

节点:拥有某些计算机资源的物理对象(设备)

《printer》

HpLaserJet

5MP

《Router》

Cisco Router

X2000

《Controller》

SAAB9-5

Navigator

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


5

(1) 几种情况

情况1:节点间存在通信路径(关联),

可以交换对象和发送消息.

《Internet》

客户端

Web浏览器

DataBase

数据服务器

HTTP

服务器

《Oracle》

打印机

《Apache服务器》

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


5

情况2: 构件与节点

《http》

《RMI》

dbServer:AIX

appServer:

sunsolaris

Client:Browser

Student

Persistence

《infrastructure》

seminar

Student

Administration

《application》

University DB

《database》

schedule

JDBC

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


5

微波炉系统:微波炉控制器

Guard.exe

《process》

supervisor

Thermomeer

Controller

图 5-72 包含对象的节点

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


5

客户PC

NetDrv

连接到

1..*

1..2

服务器

ApplClient

Window xp

Admin PC

Backup

Medium

1

1

AdminPgm

备份站

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

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


5

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

  • 资源利用

  • 地理位置

  • 设备访问

  • 安全

  • 性能

  • 可扩展性和可移植性


5

(3) 如何开发部署模型

第1步 : 确定模型范围

第2步 :确定分布结构

第3步 :确定节点和他们的连接

第4步 : 把构件分布到节点

第5步:为不同组件之间的依赖建模


5

例1 保险系统配置图

保险单填写界面

系统内部

保险单

客户

Oracle界面

数据库界面

Sybase界面

图 5-74 保险系统包图


5

保险单填写

界面

(Tcp/Ip)

后台服务器

保险系统配置

保险系统

配置

保险政策

保险对象

数据库

配置用户

图 5-75 保险系统配置图


5

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

User Interface/Controller

预处理器

解题器

后处理器

System Services

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


5

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

可有多个ATM客户机

ATM Clients

ATM中央

服务器

SessionMgr

ATM

Central

Sever

Session

AccountMGR

银行

服务器

(多个)

Bank

Server

Account

Bank Database Nod

银行数据

库结点

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


5

小 结

逻辑构架

显示类和对象及他们之间关系和协作。

可以用:用例、类、序列、状态、协作图、

活动图、包图来写逻辑架构文档.

系统

构架

物理构架

处理代码构件的结构和组成系统的硬件

结构。详细描述逻辑架构中定义的概念

的实现.用构件图、节点图描述.


5 4 1

5.4.1 对类的属性的处理

5.4 对象设计

5.4.2 关联的设计

对象设计的主要任务:

  • 精化类的属性和操作

  • 明确类之间的关系

  • 整理和优化设计模型

5.4.3 对结构的优化

5.4.4 应用设计模式定义操作

5.4.5 检查系统设计


5 4 11

5.4.1 对类的属性的处理

  • 类所代表的现实实体的基本信息;

1) 回顾属性的来源:

  • 描述状态的信息;

  • 描述该类与其他类之间关联的信息;

  • 派生属性(derived attribute)如:

    类CourseOffering中的“学生数目”/numStudents:int

  • 其他常见的类型:

    地址Address、颜色Color、几何元素Geometrics、

    电话号码PhoneNumber、通用商品代码UPC、

    社会安全代码Social Security Number、

    邮政编码Postal Code 、枚举类型等


5

  • 具体说明属性的名称、类型、

    缺省值、可见性等

    visibility attributeName:

    Type = Default

  • Public: ‘+’;

  • Private: ‘-’

  • Protected: ‘#’

2) 成员细节的表示

  • 基本原则

    • 尽可能将所有属性的可见性设置为private;

    • 仅通过set方法更新属性;

    • 仅通过get方法访问属性;

图 5-78 有细节的类图


5

3)在哪里展示非简单数据类型和纯数据值?

UPC 是一个纯数据值,但也可以是一个非简

单数据类型。

UPC是纯数据值,

在概念模型中当

作属性看待。

Product

Specification

upc:UPC

Store

Address:Address

*

1

UPC是非简单数据类

型(要对它进行验证,

也可包括制造者属

性),即有属性和关联,

也可用概念来表示。

Product

Specification

UPC

*

1

Store

Address

图 5-78 用概念表示属性


5

Payment

amount:Number

这个表示可用,但不灵活或不可

靠,要知道支付货款时所用的货

币单位。

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

Has-amount

Is-in

Unit

Payment

Quantity

amount:Number

1

*

*

1

将数量作为一个单独概念

Payment

amount:Quantity

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

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


5

5) 对属性的处理

  • 保留派生属性,避免重复计算

  • 描述其他事物(航班和机场)的规格说明

Flight

data

number

time

Airport

name

Flies-to

较差

*

1

FlightDescription

number

Flight

data

time

Described-by

*

1

*

Describes-flights-to

(1)

1

Airport

name

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

较好


5

  • 规格说明型或描述型对象与它们所描述事物

  • 密切相关。

Item

description

price

serial-number

UPC

较差

ProductSpecification

description

price

UPC

Item

serial-number

Described-by

*

1

较好

(2)

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


5

6) 外部键的属性

  • 分析阶段的数据类型只能是简单的、原始的

    数据类型,并不表示如C++、Java 、Smalltalk

    中的属性(数据成员、实例变量)

(分析中的类图是对问题的存在建立的分析模

型,而不是软件实现的分析模型)。

  • 在软件构造和设计阶段,对象之间的关联通常

    是用指向其他复杂类型的属性来表示(但不是

    唯一的解决方案)


5

  • 在分析阶段的对象模型中, 不是使用属性,

    而是使用关联来联系两个概念的。

Uses

Cashier

name

1

1

POST

number

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

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

Cashier

Name

currentPOSTNumber

使用外部键的属性与另一个对象发生联系

图 5-83 有外部键的属性


5 4 2 association

5.4.2 关联 (Association)的设计

1)四种连接

  • 全局(global):某个对象可以在全局范围内

    直接被其他对象“引用”

两个

类间

依赖

关系

  • 参数(Parameter):某个对象作为另一个对象

    的某个操作参数或者返回值.

  • 局部(Local):某个对象在另一个对象的某个

    操作中充当临时变量.

  • 域(Field):某个对象作为另一个对象的数

    据成员(聚合/组合关系).


5

对象的链接方式

观察者

0..1

被撞击者

0..1

撞击者

图 5-84 对象的连接方式


5

0..*

公司

雇员

公司

雇员

雇主

雇员

图 5-85 单向关联的实现

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

节点

属性

关联源对象

属性

关联对象指针 next

关联对象指针 next

关联指针

被关联对象

被关联对象


5

例 : 用java实现关联

0..*

1

保险公司

保险合同

涉及

Public class Insurance - Company

{

/*方法*/

Private Insurance-ContractVector Contracts

}

Public class Insurance -Contract

{

/*方法*/

Private Insurance - Company refer - to;

}

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


5

2) 增加冗余关联,提供访问效率

公司

Find-skill

雇员

技能

*

*

图 5-86 增加类

3)对限定关联(qualified association)的考虑

主要是减少限定符远端处的多重性,从多个

减少到1个。

分析类图中的限定符只是区分两种不同类

型的事物。


5

在设计类图中,关联的限定符是用某种键值或者

记号实现的,而不是用Java的引用实现的。

Login

Servelet

Employee

empid

  • 图 5-87 关联的限定符

public class LoginServelet{

private String empid;

public String getName(){

Employee e = DB.getEmp(empid);

return e.getName();

}

}

166


5

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

  • 在设计类图中,导航是角色的一个特性,它

    说明从源对象到目标对象沿着关联有一个单

    向的链接,

  • 导航箭头的关联表示从源类到目标类的属性

    可见性

  • 在实现时,导航箭头用源类中所定义的一个

    属性来实现,这个属性引用了目标类的一个

    实例


5

POST类可能有一个

指向Sale对象的属性

导航箭头说明对象

被单向链接到Sale对象

Sale

Captures

POST

date

isComplete:Boolean

time

1

1

endSale()

enterItem()

makePayment()

becomeComplete()

makeLineItem () makePayment()

total()

没有导航箭头说明从Sale

到POST方向没有链接

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


5

根据类图中的关联和导航关系,可推导出

一个类的引用属性

Product

Specification

1

SalesLineitem

Described-by

*

quantity:Integer

subtotal():Quantity

prodSpec

description:Text

price:Quantity

upc:UPC

图 5-89 添加引用属性

用属性名

作为角色名

public class SalesLineitem

{

public SalesLineitem(ProductSpecification spec,int qty);

public float subtotal();

private int quantity; 简单属性

private ProductSpecification prodSpec; 引用属性

}


5

说明:

  • 分析中的关联 ,是增进对问题的理解为目的

    (不是对数据流、实例变量连接等所做的一条声明)。

  • 设计类图中的关联 ,是对软件构件的描述。

  • 设计类图中关联的选择要依据交互图中的可

    见性 ,和存储特性。

  • 从A到B导航关联的确定:

    # A向B发送一个消息

    # A创建了B的一个实例

    # A需要维持到B之间的一个链接

  • 根据协作图来确定导航


5

  • 在程序语言中,实现关联最普通的方法是使

    用一个指向关联类的属性。

  • 在类图中,依赖关系描绘类之间的非属性的

  • 可见性时很有用,即描述参数、全局、局部

  • 可见性时很有用。

普通的属性可见性是用关联线和导航箭头来

表示的。


5

  • 非属性可见性的依赖关系

Store

Product

Specification

Uses

1

address: Address

name:Text

addSale()

1

Contains

description:Text

price:Quantity

upc:UPC

ProductCatalog

specification()

1

1

1..*

1

1

Looks-in

1

1

Describes

Houses

Sale

*

date: Date

isComplete:Boolean

time: Time

becomeComplete()

makeLineItem()

makePayment()

Total()

1

1

Sales Lineitem

POST

endSale()

enterItem()

makePayment()

quantity:Integer

subtotal()

Captures

1

1..*

Contains

1

1

Payment

amount:Quantity

*

  • Loge-completed

1

1

Paid-by

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


5

5) 对关联类的设计

Supplier

Company

*

*

Trade

job

*

1..*

  • 关联类在实际设计中十分有用,但现在的OO语言

    不直接支持关联类,可以这样设计:

Buyer

Person

Byuer

name:String

address:String

supplier :Supplier [*]

trades: Trades[*]

Supplier

company-name:String

address:String

buyers: Buyer[*]

trades: Trades[*]

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


5

上述设计有的不足:

# 找出对应的一宗交易都要花费一定的力气

# 违反了对象封装的原则,与交易相关的数据(供应和买家)没放在交易对象中

  • 用二元关联类的方法来解决

    把供应者和买家对象直接放到交易中作为它的属性。

Supplier

company-name:String

address:String

trades: Trades[*]

1

*

Trade

product:String

quantity:Integer

Total-price:Double

supplier :Supplier

Byuer:Byuer

Byuer

name:String

address:String

trades: Trades[*]

1

*

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


5

*

*

Flight

Duty

pilot

1

2

Airplane

Person

(b)

图 5-93 比较两个设计

二元关联类的每个实例,描述

两个类对象之间的link的性质。

本例中航班对象包含航班号、

时间等数据,对两个驾驶员是一样

的,航班是飞机和人员的关联类是

不恰当的。

Airplane

例.飞机(Airplane)在每次飞行中,有两个驾驶员(pilot),在

规定的航班(Flight)中,执行任务(Duty)。评价下面设计。

1

Flight

2

pilot

Person

(a)

每个航班对象与一架飞机

和两个驾驶员有关系。在某一

航班中,每个驾驶员有自己的任

务,作为关联类来模拟。

(b)设计允许一个驾驶员工

作在不同航班,执行不同任务

(时间不冲突便可)。


5

List

Name

表的length

stack

:

List1

Add

Remove

First

last

PUSH

POP

List

Add

Remove

First

last

5.4.3 对结构的优化

1) 调整继承结构

改为委派

Stack

  • PUSH

  • POP

图 5-94 继承与委派机制


5

*

*

supplier

Person

User

Buyer

2) 解决死板的继承

Person

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

交易

*

*

User

Buyer

supplier

图 5-95 产生互不相容的子类

1

*

Person

Role

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

交易

*

*

supplier

User

Buyer

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


5

3) 关于关系环的问题

正向容纳

反向容纳

previous

1

Version

A

FolderItem

0..1

Revision

B

Folder

next

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

图5-98 原版与修订版

分解使关系明确,

证实设计的正确,

行为特性更为明显。

FolderItem

Folder

FolderItem

但未分解的类图更

为简洁,可直接与

实施程序对应。

Folder

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


5

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

如 电脑系统的局部类

1

1

Computer

System

1..*

Permission

FileSystem

0..1

1..*

*

*

FolderItem

*

User

1

*

Owner

0..*

0..1

1

File

Folder

*

*

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


5 101

contains

contains

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

*

1

Component

1

Part

Attribute

*

contains

1..*

Item

1

Group

Circle

Square

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


5

但是一个人可能同时参加多个项目(Project),并在不同项

目中扮演同样或不同角色(Role) 。

例 有一个设计,描述某公司每个顾员的工作(Job)说明,每个

顾员都有一个上级(Supervisor)。

在同样或不同的项目中,一个人也可能担当不同的任务

(Task},从而有不同的角色。

一个项目中的某个角色可能有多个人参与。

根据上面要求,给出如下两个设计,

这两个设计有什么不同?


5

例,如下是模块(Module)、网络(Nework)、结点(Node)、

连接(Link)的两个设计,试进行评价。

1..*

1..*

*

1

Module

Module

Link

*

1

1

Nework

Node

Nework

Node

1

1

*

*

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

Link

*

反向关系环;

递归关联;

连接类与基类相连,复用源码

结构简化,避免重复和模糊的连接

反向关系环;

二元关联类;

网络与结点是Link的对象管理器

谁负责管理连接对象?


5 4 4 pattern

5.4.4 应用设计模式(pattern)定义操作

1) 什么是模式?(pattern)

Alexander给出了经典定义:

“每个模式都描述了一个在我们的环境中不断出

现的问题,然后描述了该问题的解决核心。通过这种

方式,你可以无数次的使用那些已有的解决方案,无

需在重复相同的工作。”

2) 设计模式的特性

  • 巧妙

  • 通用

  • 验证

  • 简单

  • 可重用

  • 面向对象


5

3) 模式的组成元素

  • 解决方案

  • 例子

  • 结果情景

  • 模式名

  • 问题

  • 情景

  • 约束

4) 模式的分类

  • 创建性模式:工厂模式、抽象工厂、单身等

  • 结构性模式:适配器、桥接、外观、享元、代理等

  • 行为模式:命令、职责链、解释器、迭代器、中介等


5

5) 介绍职责分配中通用原则的模式

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

描述将职责分配给对象的基本原则

  • 专家(Expert)

  • 创建者(Creator)

  • 高聚合度或高内聚(High cohesion)

  • 低耦合度或低耦合(Low coupling)

  • 控制者(Controller)

五个

模式

(1)

解决方案:将一个职责分配给信息专家。

问 题:面向对象的设计中什么是最

基本的职责分配原则。


5

例:

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

Sale

Date

time

Contains (包含)

Product specification

1..*

Describedby

Sales

LineItem

*

description

price

Upc

quantity

图 5-104 Sale类的类图

职责

Sale

知道销售总额

SalesLineItem

知道销售项记录了金额

ProductSpecification

知道商品价格


5

Sale

date

time

SalesLineItem

quantity

Product Specification

description

Price

UPC

subtotal()

total()

price()

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

t:=total()

:Sale

2:st=substotal()

Sli:SalesLineItem

:SalesLineItem

2.1:p:=price()

图 5-105 计算Slae总额

:Product Specification


5

  • 专家模式表明了“对象所能完成的工作要依赖

    于它所掌握的信息。

说明 :

  • 为了履行职责需要获取散布在不同对象中的信

    息。表明系统中存在许多“部分”专家,在完成

    任务时需要它们的协作。

  • 软件对象之间的合作就像人之间的合作一样。

  • 对象所做的事情与它们所知道的信息相关。

  • “把责任交给数据” ;“谁知道谁来干” ;“自己

    做” ;“把服务推给要操作的属性”。

  • 封装性好;高内聚。


5

(2) 创建者(Builder)模式

方案:将创建一个类A的实例的职责指派给类B

的实例,若下列条件满足: B是A对象的

创建者。

  • B聚集了A对象

  • B包含了A对象

  • B记录了A对象的实例

  • B要经常使用A对象

  • 当A的实例被创建时,

  • B具有要传递给A的初始化数据。

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


5

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

SaleLineItem实例?

根据创建者模式,应当找出一个类,这个类聚

集、包含了SaleLineItem的实例。

:Sale

Sale

date

time

makelineItem

(quantity)

新方法

makeLineItem()

total()

1:create(quantity)

:SalesLineItem

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


5

结论:

  • 创建者模式,用于指导实例的创建任务分配,要找

  • 到一个与被创建对象之间有关联关系的创建者。

  • 职责的确定和分配在绘制协作图的语境中进行。

  • 职责的履行由方法调用来具体实现。

  • 创建者模式说明聚合/包容器类/记录类是一个承

  • 担创建被包含或被记录的事物职责好的候选者。

(3) 低耦合度模式

解决方案:在分配一个职责时,要保持低耦合度。

问题:如何支持低依赖关系和增加重用。


5

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

Payment

Post

Sale

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

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

1:Create()

makePayment()

:Post

P:Payment

2:addpayment(p)

:Sale

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

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

1:makePayment()

makePayment()

:Post

:Sale

1.1:create()

:Payment

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


5

说明

# 类型X有一个属性(数据成员或实例变量)引用

类型Y的实例或类型Y本身。

  • 在C++、Java中从类型X到类型Y的耦合有:

# 类型X有一个方法以各种方式引用了一个类型

Y的实例或类型Y本身(Y的局部变量或参数),

或从一个消息返回的对象是类型Y的实例。

# 类型X是类型Y的一个直接的或间接的子类。

# 类型Y是一个接口,类型X实现了这个接口。


5

说明

  • 低耦合度在设计过程中是要记住的一个原则,

    它是一个时刻要注意的隐含设计目标。

  • 低耦合度支持更独立的类设计,支持重用 ,

    如果不以重用为目的,耦合就不重要了 。

  • 不能孤立地考虑低耦合度,与专家、高内聚

    模式有关。

  • 完全没有耦合的设计也是不可取的,类之间

    应当保持中等程度的耦合是合理的。


5

(4) 高聚合度模式

解决方案:在分配一个职责时,要保持高度聚合

问题:如何将复杂性保持在可控制范围内?

(5) 控制者

解决方案:将处理系统事件消息的职责分派

给代表下列事物的类

  • 代表整个“系统”的类

  • 代表整个企业或组织的类

  • 代表真实世界中参与职责(角色控制者)的主动对象类

  • 代表一个用况中所有事件的人工处理者类


5

问题:谁来负责处理一个系统事件?

  • 系统事件(system event)

  • 由某个参与者发起的指向系统的输入事件

  • 系统操作(system operation)

  • 系统响应这些事件而执行的操作

  • 控制者(controller)

  • 控制者定义了系统操作的方法

如:

:System

enterItem(UPC,quantity)

事件是命名的动因;

操作是对动因的响应

enterItem为系统事件,触发了一个

同名的系统操作,系统操作带有参数。

图 5-110 系统事件


5

对操作的命名:要抽象出最高层次或使用操作

的最终目标耒对操作的命名。

:System

enterItem(UPC,quantity)

较好的名称

enterKerPressed(UPC,quantity)

较差的名称

图 5-111 在比较抽象层次上对事件和操作命名

如对一个记录支付信息的操作命名如下:

enterAmountTendered (amount) 较差

enterPayment(amount) 较好

makePayment(amount) 更好一些


5

System

endSale()

enterItem()

makePayment

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

图 5-112 一个名为System类型中的系统操作

这个图与概念模型的区别:

概念模型中的元素是真实世界中的概念,

显示的只是静态信息

System类型像人为的概念,描述系统行为,

动态信息


5

enterItem(upc,quantity)

:cashier

enterItem(upc,quantity)

:store

enterItem(upc,quantity)

:post

system

post

:

endsale()

enterItem()

makepayment

图 5-113 系统操作的分配

enterItem(upc,quantity)

:BuyItems

Handler

哪个类对象应该是系统事件的控制者?

根据控制者模式,可选则

  • 代表整个系统的类

  • 代表整个企业组织者的类

  • 代表真实世界参与职责的主动对象类

  • 代表一个用况中所有事件的人工处理类


5

说明:

  • 无论采用何种输入手段,必须由控制者来处理

    系统输入事件.

  • 一个用况中的所有系统事件都应由同一个控制

    者来处理,这样才能维持一个用况的状态信息.

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

  • “系统类”是虚包的控制者.把它看成是整个系

    统代表或一个物理单元.只有当不能将系统事

    件消息发向替代的控制者时才使用虚包类.

注意角色控制的使用:

所创建的角色易出现低聚合的危险;

不要使角色像人一样承担了所有工作.


5

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

Object store

_

×

Quantity

UPC

Total

Balance

Tendered

MakePayment

EndSale

EnterItem

出纳员

onEenterItem()

系统事件消息

表示层

(JavaApplet)

:Postapplet

1:enterItem(upc,qty)

1.1:makeLineItem(upc,qty)

领域层

:Post

:Sale

控制者

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


5 4 5

5.4.5 检查系统设计

1)检查“正确性”

  • 每个子系统都能追溯到一个用例或一个非功能需求吗?

  • 每一个用例都能映射到一个子系统吗?

  • 系统设计模型中是否提到了所有的非功能需求?

  • 每一个参与者都有合适的访问权限吗?

  • 系统设计是否与安全性需求一致?

2)检查“一致性”

  • 是否将冲突的设计目标进行了排序?

  • 是否有设计目标违背了非功能需求?

  • 是否存在多个子系统或类重名?


5

检查系统设计

3) 检查“完整性”

  • 是否处理边界条件?

  • 是否有用例走查来确定系统设计遗漏的功能?

  • 是否涉及到系统设计的所有方面(如硬件部署、

    数据存储、访问控制、遗留系统、边界条件)?

  • 是否定义了所有的子系统?

4) 检查“可行性”

  • 系统中是否使用了新的技术或组件?是否对这些

    技术或组件进行了可行性研究?

  • 在子系统分解环境中检查性能和可靠性需求了吗?

  • 考虑并发问题了吗?


5

总结

1.设计阶段要做的工作:

1) 逻辑结构和物理结构的设计;

2)对象设计

  • 属性的优化

  • 关联的优化

  • 使用设计模式确定和优化操作


5

2. 面向对象的设计原则

1) 采用了模型化的观点

2)使用了抽象、封装、继承等机制

3)模块化机制: 交互耦合、继承耦合

  • 服务内聚;类内聚;

4) 可重用机制、灵活性机制、可移植性机制


5

(1) 提高可重用性设计的策略

使设计泛化;

提高抽象,提高内聚性,降低耦合的设计原则;

设计要包含钩子,使他人可添加功能;

简化设计.

注意:克隆(clone)并不能看作是重用.

克隆是将代码从一个地方复制到另一

个地方.


5

(2) 提高灵活性设计的策略

积极地预测将来设计发生的变化.策略是:

@ 提高内聚性,降低耦合;

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

易扩展;

@ 不要将代码写死.应消除代码中的常量.

应在服务器启动时从配置文件中读取最大值

或向用户提供一个选项对话框,可修改该值;

@ 打开所有选项.方法中有异常时,应抛出异常,

而不是执行特定动作处理该异常;

@ 使用并创建可重用代码.


5

(3) 提高可移植性设计的策略

让软件在可能多的平台上运行.

# 避免使用特定环境的专有功能;

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

# 语言的一些功能依赖于硬件体系结构(C++)

要知道一个字中字符的顺序,以及整型变量

的位数.

# 移植性问题与文本文件有关,各种平台中用

来终止一行的字符都不尽相同.


5

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

2

7

Store

Product

Specification

Uses

1

3

address: Address

name:Text

addSale()

1

Contains

description:Text

price:Quantity

upc:UPC

ProductCatalog

specification()

1

1

1..*

1

1

Looks-in

1

1

5

Describes

Houses

Sale

*

4

date: Date

isComplete:Boolean

time: Time

becomeComplete()

makeLineItem()

makePayment()

Total()

1

1

6

Sales Lineitem

POST

endSale()

enterItem()

makePayment()

quantity:Integer

subtotal()

Captures

1

1..*

Contains

1

1

1

Payment

amount:Quantity

*

  • Loge-completed

1

1

Paid-by

图 5-115 类的实现顺序


5

Class Payment

package post;

public class Payment

{

private float amount;

public payment (float cashTendered )

{

this.amount = cashTendered;

}

public float getAmount() { return amount;}

}


5

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));

}

}


5

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();

}


5

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() )

}

}


5

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;}

}


5

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;}


5

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 );

}

}


5

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();

}

}


5

Class Store

package post;

class Store

{

private ProductCatalog productCatalog

= new productCatalog();

private POST post = new POST(productCatalog);

public POST getPOST () {return post;)

}


5

  • 业务模型

  • 分析模型

Use Case图

Activity图(工作流)

分析级的Class图(业务实体)

用例事件流

补充规范

Sequence

Collaboration图

Class图

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

  • 设计模型

Sequence,

Collaboration图

Class图

Statechart

Component

Deployment

  • 测试模型

Use Case图

Statechart

Class图


  • Login