瑞星卡卡安全论坛

首页 » 技术交流区 » 系统软件 » 【转载】软件开发人员必备工具书 【代码大全】
TinKingTZW - 2006-8-22 19:11:00
type
EVENT = integer;
var
EventAvailable:boolean; { true if an event is available }
function HighestPriorityEvent:Event;
function LowestPriorityEvent:Event;
end Events;
面向对象的语言支持
  面向对象的语言,如C++,对模块化的支持是直接的,模块化是面向对象编程的核心。以
下是一个C++来实现排序的模块的例子:
class Buffer
{
public;
typedef intEVENT;
BOOL Eventavailable; /*true if an event is available */
EVENT HighestPriorityEvent(void);
EVENT LowestPriorityEvent(void);
Private;
...
};
Pascal 的支持
  某些版本的Pascal,即 4.0 版和随后的Turbo Pascal,利用单元的概念来支持模块化。“单
元”是一个可以包括数据、数据类型和子程序的数据文件。单元中有一个说明了可供模块外部
使用的子程序和数据接口。数据也可以被说明为在这个文件内部的函数和过程使用,而且是仅
在其内部使用。这为Turbo Pascal 提供了可以在局部、模块和全局层次上的数据可存取性。以
下是在Turbo Pascal 5.0版中的排序模块形式:
unit Events;
INTERFACE
type
EVENT=integer;
var
EventAvailable:boolean;{ true if an event is available }
TinKingTZW - 2006-8-22 19:11:00
function HighestPriorityEvent: Event;
function LowestPriorityEvnet:Event;
IMPLEMENTATION
...    ——文件中这部分的子程序数据,如果没有在上面INTERFACE 中说明
                    的话,则对其它文件来说是隐蔽的
end.{unit Events}
  属于Generic Pascal标准的Pascal实现并不直接支持模块化,不过你可以通过扩展它们来达
到模块化,这将在后面讨论。
C 的支持
  虽然用C语言编程的程序员们并不习惯在C中使用模块,但事实上C也直接支持模块化。
每一个C 源文件都可以同时含有数据和函数,可以把这些数据和函数说明为Static,这将使它
们只在源文件内部才能使用。也可以不把它们说明为Static,此时它们在源文件外也可以使用。
当每一个源文件都被当作模块时,C 就完全支持模块化了。
  由于源文件和模块并不完全相同,你需要为每一个源文件创建两个标题文件——一个作为
公用、模块标题文件,另一个作为专用的、源文件标题文件。在源文件的公用标题文件中,只
放入公用数据和函数说明,下面是一个例子:
/* File:Event.h 本文件仅包含公共的可用类型、数据和函数说明
Contains public declarations for the "Event" module. */
typedef int EVENT;
extern BOOL EventAvailable; /* true if an event is available */
EVENT HighestPriorityEvent(void);
EVENT LowestPriorityEvent(void);
而在源文件专用标题文件,只放入供内部使用的数据和函数说明。用#include命令把标题
文件只放入组成模块的源文件中,不要允许其它文件含有它。以下是一个例子:
/* File:_Event.h
Contains private declarations for the "Event" module. */
/* private declarations */ ——这里是专用类型、数据和函数
...

  C 源文件中,要使用#include 来把两个标题文件都包含在其中。在其它使用了Event模块
中的公用子程序模块中,用#include命令来只把公用的模块标题包含进去。
  如果在单独一个模块中,需要使用一个以上源文件,可能要为每一个源文件都加一个专用
标题文件;但对组成模块的子程序组,应该只加一个公共的模块标题文件。对于模块中的源
TinKingTZW - 2006-8-22 19:11:00
文件来说,利用#include来包含同一个模块中其它源文件的标题文件是可以的。
  Fortran 的支持
    Fortran90为模块提供了全部支持。而Fortran77如果在规定范围内使用,则只对模块提供
了有限的支持。它拥有创建一组对数据具有独占存取权的机制,这个机制就是多重入口点,
对这种机制的随便使用,往往会产生问题。而如果小心使用的话,它则提供了一种不必使数
据成为公用的,就可以使一组子程序对同数据进行存取的途径。
    可以说明数据为局部的、全局的或者是COMMON 的。这个特性又提供了使子程序对数
据进行受限制存取的方法,定义一组组成一个模块的子程序。Fortran 编译程序无论如何也不
会认为它们组成了一个模块,但是从逻辑上来说,它们的确组成了一个模块。对模块中每一
个允许对数据进行直接操作的子程序,都应该用COMMON 作为其前缀来命名。不要让模块
以外的子程序使用COMMON 作为前缀。利用编程标准来弥补程序语言不足这一办法,将在
后面讨论。
6.4.3  伪模块化
    在像通用Basic,通用Pascal和其它既不直接也不间接支持模块化的语言中,该如何进行模
块化呢?答案在前面已经提到过了,利用编程标准来代替语言的直接支持。即使你的编译程序
并不强制你采用好的编程应用,还可以采用能够达到这一目的的编码标准。以下的讨论涉及了
模块化所要求的每一方面。
把数据和子程序装入模块
    这个过程的难易程度取决于编程语言,即它是允许使用各种源文件的还是要求所有的代码
必须是在一个源文件中的?如果你需要用10 个模块,那就创建10 个源文件。如果环境要求所
有代码都要在一个源文件中,那就按模块把代码划分为几部分,同时用注释来区分每一个模块
的开头和结尾。
保证模块的内部子程序是专用的
    如果所采用的程序语言不支持限制内部子程序的可见性,使得所有的子程序对其它子程序
来说都是公用的,可以利用编码规定来限定只有标明公用的子程序才能被模块外的子程序使用。
不管是什么样的编译程序,下面都是你可以做的:
    通过在说明的地方加注释,来明确区分公用和专用子程序。明确区分与每个子程序相关的
模块。如果别人不得不通过查询子程序说明来使用子程序,要确保在说明中注释了它是公用的
还是专用的。
    不允许子程序调用其它模块的内部子程序。使用注释把属于同一个模块的子程序联系到一
起。
    采用命名约定来表明一个子程序是内部的还是外部的。可以让所有的内部子程序名称前面
都带有下划线(_)。虽然这种方法无法区分不同模块的内部子程序,但是绝大多数人还是可以
区分一个子程序是否是属于它们自己的模块。如果它不是自己模块中的子程序,那么显然它是
其余模块中的子程序。
TinKingTZW - 2006-8-22 19:12:00
采用表明它是内部的还是外部的命名规定。所采用约定的细节,往往取决于编程语言所赋
予你对子程序命名的灵活性。例如,一个DataRetrieval 模块的内部子程序名称可能会以dr_作
为前缀。而UserInterface模块内部子程序的名称前缀则可以是ui_。而属于同一模块的外部子程
序则分别会以DR_和UI_作为前缀,如果对名称的字节长度是有限制的(如,ANSI FORTRAN77
中的6个字母),那么命名的约定就会用掉其中相当一部分,这时,在制定命名约定时就要注意
这一长度限制。
保证子程序的内部数据是专用的。
保证模块层次上的内部数据的专用性与保证模块层次上子程序的专用性是相似的。通常,
要采用清楚表明只有特定数据才能在文件外部使用的编码标准。不管编译程序允许你做的是什
么,以下是一些可以采用的步骤:
首先,利用注释来说明数据是公用的还是专用的。明确区分模块所涉及到的每一个数据的
可存取性。
其次,不允许任何子程序使用其它模块中的专用数据,即使编译程序把此数据作为全局变
量也不可以。
第三,采用可以使你注意一个文件是专用还是公用的命名约定。为了保持连贯性,应该使
这一命名原则与子程序的命名原则类似。
第四,采用表明数据属于哪一个模块,即它是外部还是内部的命名约定。
6.4.4  检查表
模块的质量
· 模块是否有一个中心目的?
· 模块是否是围绕着一组公用数据进行组织的?
· 模块是否提供了一套相互联系的功能?
· 模块功能是否足够完备,从而使得其它模块不必干预其内部数据?
· 一个模块相对其它模块是否是独立的?它们之间是松散耦合的吗?
· 一个模块的实现细节,对其它模块来说,是隐含的吗?
· 模块的接口是否抽象到了不必关心其功能实现方式的地步?它是作为一个黑盒子来设
计的吗?
· 是否考虑过把模块再划分为单元模块?是否对其进行了充分的再划分工作?
· 如果用不完全支持模块的语言编程,你是否制定了编程约定以使这种语言支持模块?
6.5  小  结
· 不管调用哪一个,子程序与模块的不同是很重要的,要认真考虑子程序与模块的设计。
· 从模块数据是被几个子程序使用的这一角度来说,它与全局数据是相同的,但从可以
使用它的子程序是有限的,而且清楚地知道是哪些子程序可以使用它这一角度来说,
模块数据与全局数据又是不同的。因此,可以使用模块数据而没有全局数据的危险。
TinKingTZW - 2006-8-22 19:12:00
· 信息隐蔽总是有益的。其结果是可以产生可靠的易于改进的系统,它也是目前流行的
设计方法的核心。
· 创建模块的原因有许多是与创建子程序相同的。但模块概念的意义要比子程序深远得
多,因为它可以提供一整套而不是单独一个功能,因此,它是比子程序更高层次的设
计工具。
· 可以在任何语言中进行模块设计。如果所采用的语言不直接支持模块,可以用编程约
定对其加以扩展,以达到某种程度的模块化。
炫Oo逍遥oO - 2006-8-24 9:30:00
楼主是强人``` 学习了`
TinKingTZW - 2006-8-25 16:26:00
呵呵,继续


第七章  高级结构设计

目录
7.1  软件设计引论
7.2  结构化设计
7.3  面向对象
7.4  对目前流行设计方法的评论
7.5  往返设计
7.6  小结
相关章节
高质量子程序的特点:见第5 章
高质量模块的特点:见第6 章
软件结构设计:见3.4 节

有些人可能认为高层次设计不是真正的实现活动,但是在小规模项目中,许多活动都被认
为是实现活动,其中包括设计。在一些较大的项目中,一个正式的结构设计可能只是分解系统,
而其余大量的设计工作往往留在创建过程中进行。在其它的大型项目中,设计工作可能是非常
详细的,以致于编码不过是一项机械的工作而已,但是设计很少有这样详尽的。编码人员常常
要进行一些设计工作。
高层次设计是一个很大的主题,同时也由于它只部分地与本书主题有关系,因此,我们将
只论述其中的几个方面。模块设计和子程序设计的好坏在很大程度上取决于系统的结构设计好
不好,因此,要确保结构设计先决条件(如3.4 节所述)已经被满足了。也有许多设计工作是
在单个子程序和模块层次上进行的,这已在四、五、六章中论述过了。
如果已经对结构化设计和面向用户设计非常熟悉了,你可能想阅读下一部分的介绍,再跳
到7.4 节关于两种技术的比较,最后阅读7.5 节。

7.1  软件设计引论

“软件设计”一词的意思是指,把一个计算机程序的定义转变成可以运行程序的设计方法。
设计是联系要求定义和编码与调试的活动的桥梁。 它是一个启发的过程而不是一个确定的过
程,需要创造性和深刻的理解力。设计活动的绝大部分活动都是针对当前某特定项目进行的。
7.1. 1  大型和小型项目设计
在大型的、正规的项目中,设计通常是与需求分析、编码等活动分立的,它们甚至可能是
由不同人分别完成的。一个大型项目可能有几个级别的设计工作——软件结构设计、高层次模
块设计和实现细节设计。结构设计具有指导的意义,在小规模编码阶段往往也是很有帮助的。如
TinKingTZW - 2006-8-25 16:28:00
果进行了通用或高层次设计,那么其指导方针则在大规模编码阶段是非常有意义的。不管是出
于什么原因,即使名义上设计工作已经结束了,但事实上它远没有停止。
在小型的、非正式的项目中,大量的设计工作都是由程序员们坐在键盘前面完成的。“设计”
可能只是用程序语言编程,用PDL来编写子程序。也可能是在编写子程序之前画一下流程图。
不管是怎样进行的,小型项目与大型项目一样,都会从良好的设计工作中受到大量好处。而使
得设计活动明显化,则会极大地扩展这一好处。
7.1. 2  设计的层次
软件系统中,设计是在几个不同层次的细节上进行的。有些技术适用于所有的层次,而另
外一些,则往往只会适合其中的一或两个层次,下面是这些层次:

层次1:划分成子系统:
   
在这一层次上,设计的主要成果是划分系统的主要子系统。这些子系统可能是很大的——
数据库接口、用户接口、命令解释程序、报告格式程序等等。这一层次的主要设计活动是如何
将系统划分成几个主要要素,并且定义这些要素间的接口。在这一层次上的划分工作主要是针
对那些耗时在几天以上的项目。在图7-1中,设计是由三个主要元素和它们的交互作用组成的。

     
其中程序划分为系统(l),子系统更进一步划分为模块(2),一些模块划分为程序(3),
(4)为每个子程序内部设计。
“子程序”和“模块”的特定意义已经在前几章中引入了。“Subprogram”一词将在本章中
使用,我们可以称它为“亚程序”,它是指小于整个程序的任何程序,其中也包括子程序和模块。

层次2:划分成模块:

    这一层次的设计包括识别系统中的所有模块。在大型系统中,在程序分区层次上划分出来

附件: 7341152006825162017.bmp
TinKingTZW - 2006-8-25 16:28:00
的子系统往往太大,难以直接翻译成代码。例如,一个数据库接口子系统可能非常复杂,需要
有十几个子程序来实现。如果出现这种情况,那么还需要将这个子系统再划分为模块:如数据
存储、数据恢复、问题解释等模块。如果分解出来的模块还是太复杂,那么将对它再次划分。
在许多程序的设计中,在层次1 中分解出来的子系统将直接转变成层次2 中的模块,而不再区
分这两个阶段。
在定义模块时,同时也要定义程序中每个模块之间的相互作用方式。例如,将定义属于某
一模块的数据存取函数。总之,这一层次的主要设计活动是确保所有的子系统都被分解成为可
以用单个模块来实现它的每一部分。
与把一个系统分解成子系统一样,把子系统分解成模块也是主要针对耗时在几天以上的项
目的。如果项目很大,这一层次的分解活动是与上一层次严格区分的。如果项目较小,层次1
与层次2 可能是同时进行的。在图7-1 中,分解为模块的活动已经包含于每个元素中了。正如
图中所示的那样,对于系统不同部分的设计方法是也不同的。对某些模块之间关系的设计可能
是以网络思想为基础的,也可能是以面向对象思想为基础的。如同图中左面两个子系统那样。
而其它模块的设计则可能是分级的。如图中右侧的子系统那样。
层次3:划分成子程序:
    这个层次的设计包括把每个模块划分成各种功能,一旦一个子程序被识别出来,那么就同
时规定它的功能。由于模块与系统其它部分是相互作用的,而这一作用又是通过功能子程序进
行的,所以,模块与系统其余部分的作用细节是在这一部分规定的。例如,将严格定义如何调
用解释程序。
    这一层次的分解和设计工作对于任何耗时超过几个小时的项目都是需要的,它并不一定需
要被正式地进行,但起码是要在心中进行。在图7-1 中,在左上角的一组中的一个模块中,给
出了划分成子程序的工作活动。当你揭开黑盒子的盖子时,如同图7-l 中标有3 的模块,你可
以发现由模块提供的功能是由层次组织的子程序组成的。这并不是意味着每个黑盒子中都含有
层次结构,事实上只有某些黑盒子中才有。其余组织形式的子程序可能没有或很少有层次结构。
层次4:子程序内部的设计:
在子程序层次上的设计,包括设计单个子程序中的详细功能等。子程序内部设计往往是由
程序员进行的。这一设计包括编写PDL,在参考书中寻找算法,在子程序中组织代码段落,编
写编程语言代码等活动。这种层次的工作在任何一个项目中都是要进行的,无论是有意识的还
是无意识的,是作得好还是作得坏。如果缺少了这一层次的工作,任何程序都不可能产生。在
图7-1 中,在标有(4)的一组中,表现了这个层次的工作。
7.1.3  创建中的设计工作
    对于设计层次的讨论,是论述本章其余部分内容的前提,当人们在提到“设计”一词时,
他们事实指的可能是许多不同的活动。这是一个非常重大的主题,也是非常重要的。以下是关
于这一活动,但这次是按照从细节到总体的顺序进行的。
TinKingTZW - 2006-8-25 16:28:00

内部子程序设计
在第四章中间明确讨论过内部子程序设计问题,在第五章“高质量子程序的特点”中,对
这一问题又作了进一步的讨论。在第五章关于数据和数据控制部分中,从个别程序语句和子程
序中模块的层次,对这一问题进行了讨论。在本章中,这一问题的讨论主要分布在各个部分中。
划分成子程序
在结构化设计方法中,“设计”往往是指设计程序的结构,而不指单个子程序内部的设计。
关于程序结构问题,完全可以写一本专著,本章中论述的只是构造简单程序集合的技术总结,
这些子程序集合将是你在设计中经常要实现的。
划分成模块
    在面向对象设计中,“设计”指的是设计一个系统中的不同模块。模块定义也是一个很大的
足以写成一部书的主题,在第六章中已经论述过了。
划分成子系统
对于中小型程序来说(一般是10000条语句左右),定义模块和子程序的技术往往隐含在整
个程序的设计中,在更大一些程序中,往往需要特殊的设计方法,这对于本书来说(本书重点
是实现)是难以详尽论述的。在许多情况下,特别是在一些小项目中,设计工作是键盘上完成
的,因此事实上是一种实现活动,虽然它应该在早期就完成了。本书之所以涉及了模块和子程
序设计,是因为它们处在实现的边缘上。而关于程序划分的其它讨论,则不在本书之列。
7.2  结构化设计
结构化设计这一概念是1974年在《IBM系统日报》(IBM System Journal)一篇论文中出现
的。在后来由Ed Yourdon和Larry Constantine写进《Structured Design:Fundamentals of a Discipline
of Computer Program and Systems Design》书中(1971),对其作了全面补充与扩展。Constantine
是最初那篇论文的作者之一,而“自顶向下设计”一词则是指一种非正式的结构化设计,类似
的词还有“逐步求精”和“分解”等,指的基本都是同一意思。结构化设计是与其它结构化设
计方法一道使用的。
结构化设计是由以下部分组成的:
•  系统组织,系统将被设计成几个黑盒子,明确定义的子程序和模块、接口的实现细节对
  其它子程序来说都是隐含的。
•  开发设计的策略。
•  评估设计准则。
•  关于问题的明确说明,这是解决问题的指导原则。
•  表达设计的图形和语言工具,包括PDL 和结构图。
在下面的内容中,将对这些内容作比较详细的论述。
TinKingTZW - 2006-8-25 16:28:00
7.2.1    选择需进行模块化的要素
    在前面几章论述了程序和模块相关好坏的标准,并提供了确定子程序和模块质量的检查表,
但并没有给出识别子程序和模块的方法,在本节中,将论述这一问题的指导原则。
自顶向下分解
    把程序分解为子程序的一种流行方法是自顶向下分解,也称为自顶向下设计或逐步求精。
其特点是从关于程序功能的粗略说明出发,逐步推进到程序要做的每一项特定的工作。从粗略
的层次出发往往是指从程序中的“主要”子程序出发,通常,把这个子程序画在结构图的顶部。
    以下是几项在进行自顶向下分解时要牢记的原则:
•  设计高层次。
•  避免特定语言细节。从设计中,不应该看出打算在程序中使用什么语言,或者说当在设
      计中更换要用的语言时,不会产生任何麻烦。
•  暂时不指出下一层次的设计细节(与信息隐含类似)。
•  正规化每个层次。
•  检验每个层次。
•  转移到下一个层次,进行新的求精工作。
自顶向下设计指导原则的依据是:人脑一次只能考虑有限数量的细节。如果你从一个较简
略的子程序开始,逐步把它分解成更加详细的子程序,就不必每次考虑过多的细节。这种方法
也常称之为“分而治之”战术。它对于分层结构往往是最有效的。如图7-2 所示。
从两个方面来看,这种分而治之战术是一个逐次迭代逼近的过程,首先,这是由于往往在
一次分解之后你并不会马上停止,还要进行下几个层次的分解。其次,是由于分解并不是一蹴
而就的,采用某种方法分解一个程序,再看一下效果,然后,又用另外一种方法来分解这个程
序,看效果是否会好些,在几次尝试之后,就有了一个很好的办法,同时也知道为什么这样做。
需要把一个程序分解到什么程度呢?要持续不断地分解,直到看起来下一步进行编码要比
再分解要容易为止,或者到你认为设计已经非常明了详细,对再分解已经感到不耐烦为止,到
这时,可以认为分解已经完成了。由于你比任何人都熟悉这个问题,而且也比任何人都清楚,
因此,你要确保其解决方案是很容易理解的,如果连你都对解决方案有些困惑的话,那么,试
想一下,又有谁会理解它呢?
自底向上合成
    有时候,自顶向下方法过于抽象以至于让人不知从何下手。如果想要进行一些具体的工作,
那么可以试一下自底向上的设计方法,如图7-2 所示。你可以问自己,“这个系统需要做什么?”
毫无疑问,你能够回答这个问题。你可以识别出系统需要具备的较低层次的功能,例如,你可
能知道这个系统需要进行报告格式化、计算报告总数、用不同颜色在屏幕上显示字母等等。当
你识别出某些低层次功能后,再从事较高层设计可能会有把握些。
    以下是一些在进行自底向上合成时要牢记的原则:
•  问自己,关于系统要做什么你都知道哪些?
•  利用这一问题识别出某些低层次功能。
TinKingTZW - 2006-8-25 16:30:00

•  识别出这些低层次功能共同的方面,将其组合到一起。
•  向上一个层次,进行同样的工作,或回到顶端开始自顶向下。



其中自顶向下是从一般到特珠,自底向上是从特殊到一般。
自顶向下与自底向上
自底向上与自顶向下策略的首要区别是前者是合成,而后者则是分解。一个是从可控制的
零散问题出发,把它们合成到一起从而获得一个总体解决方案,而另一个则从总体问题出发,
将其分解成可控制的零散问题。两种方法各有优缺点。在实际使用时要详加比较。
自顶向下设计方法的特点是比较简单,人们往往擅长把大问题分解成小问题,而程序员们
则更是擅长这点。当一个问题是用层次结构模型化的时候,自上而下的分解方法恰好与其相符。
自顶向下设计的另一个优点是你可以避开实现细节。由于系统常常受到实现细节变动的干
扰(比如文件结构或报告格式的变化),因此把这些细节隐含在层级结构的底部,而不是让它在
顶部起支配作用,是非常有益的。
这种设计方法也有它的缺点。其中之一是系统的总体功能可能是很难识别的。关于系统所
作的最重要决定之一就是如何进行第一步分解工作,而在自上向下设计中,刚开始接触系统,
对其了解还很少时,便不得不做出这一决定,这是很危险的。它的另一个缺点是:由于许多系
统本身并不是层级结构的,因此是很难清晰地分解。或许这种设计方法的最大缺点就是它要求
系统在顶层要有一个单一而又清楚的功能,而对于现代事件驱动系统来说,这是很难想象的。
自底向上设计方法的优点是它在早期就可以识别出有用的功能子程序,结果是坚实可靠的
设计。如果已经开发了相似的系统,那么可以参阅一下旧系统,看看有什么可以借用的。
这种文件的弱点是很难单独地使用它。因为大多数人都不善于从小的概念出发形成综合         
的设想。这就像一个自己组装的玩具,我想我已经组装完了,怎么盒子中还有零件呢?好在,
你不必单独使用它。
它的另一个弱点是,有时从你识别出的细节出发,无法建造出整个程序,就像你无法用砖头
造出一架飞机一样。而且当你知道在底部需要什么功能时,你可能已经不得不进行顶层设计了。

附件: 7341152006825162204.bmp
TinKingTZW - 2006-8-25 16:30:00
不过,这两种方法并不是互相矛盾的。设计是一个启发的过程,就是说没有一种百试不爽
的设计方法,它总是一个尝试的过程。因此,在找到好方法之前,尽可以大胆尝试,可以用自
顶向下工作一会儿,再用自底向上工作一会儿。
设计也是一个逐次迭代逼近的过程。因此,你在第n 次用自底向上方法学到的东西,将在
第n+l 次用自顶向下方法设计时起到很大帮助作用。
7.3    面向对象
面向对象设计方法的特点是通过对实际问题的分析,从中抽象出目标,然后再用程序语言
来表现它,其过程主要是:识别目标中的分目标并识别出对于分目标的操作,然后再根据分目
标的操作开发出一个系统。面向对象设计是在程序中设计目标或模块的一种方法。在较低的程
度上说,它也是设计单个子程序的一种方法。
    虽然有些鼓吹者把计算机历史划分为面向对象设计出现前阶段和面向对象设计出现后阶
段,但事实上面向对象设计与其它设计方法并不冲突。特别地,面向对象设计与结构化编程所
提供的低层次结构化并不是不兼容的,但它与高层次结构化的确不兼容。在更高的层次上,面
向对象设计方法在简单的功能性层次结构上,添加了类、群和非层次结构等新的概念。对这些
高层次的组合思想进行研究和标准化工作,将会使编程技术再向前产生一次飞跃。
在本书,对于面向对象设计的讨论是非常浅显的。与结构化设计方法相比,面向对象设计
的抽象化程度更高。本节着重论述的只是在较低层次上起作用的抽象方法,其中主要是在个别
语句、子程序和有限数量的子程序这个层次上的。这种设计方法相对来说也是一种新的设计理
论。它还没有完全成熟,关于这方面积累的设计经验也还不够丰富,但是,它是很有前途的。
7.3.1    关键思想
    面向对象设计是建立在如下主张之上的,即:一个程序模型越是真实地反映了实际问题,
那么,由此产生出的程序质量越好,在多数情况下,关于项目的数据定义要比功能稳定得多,
因此应象面向对象设计一样,根据数据来进行设计,这可以使设计更稳定。对于现代编程来说,
面向对象设计中有许多思想是很重要的。
抽象
    抽象所带来的主要好处是可以忽略掉无关紧要的细枝末节问题,而专注于重要的特性。绝
大多数现实世界中的目标都是抽象的,房屋是木材、钉子、玻璃、砖和水泥等的抽象,是把它
们组织起来的一种特殊形式。而木材本身,则又是纤维、细胞及某些矿物质的抽象,而细胞呢,
则又是各种各样的物质分子的抽象。
    在建造房屋时,如果你从分子层次上与木材、钉子等打交道,是永远不可能建成房屋的。
同样,在建造一个软件系统时,如果总是在相当于分子的层次上工作,那是不可能建成软件系
统的。在面向对象设计中,你尽力创造出与解决真实问题某一部分抽象程度相同的编程抽象,
以便解决编程问题中的类似部分,而不是用编程语言实体来解决问题。
面向对象设计擅长使用抽象,但因为它所使用的“智力砖块”,要比结构化设计中功能方
法所使用的“智力砖块”大得多。在结构化设计中,抽象的单位是函数;而在面向对象设计中,
TinKingTZW - 2006-8-25 16:30:00
抽象的单位是目标。由于目标包括函数及受函数影响的数据,从而使得在比函数更高层次上对
问题进行处理成为可能。这种抽象能力使你可以在更高层次上对问题进行考虑,而且,不必把
神经绷得太紧,就可以一次考虑很多问题。
封装
封装是对抽象不存在地方的补充。如果抽象对你说“你应该在较高层次上看一个目标”,而
封装则会说“你只能在这个层次上看一个目标”。这事实上就是6.2节所述的信息隐蔽的重复。
你对于一个模块所知道的只是它让你知道的那些,别的什么也没有。
我们继续用房屋比拟来说明问题:封装是一个使你可以看到房屋的外表但不能走进去的办
法,当然,或许你可以透过窗户看到一小部分内部情况。在较为过时的语言中,信息隐蔽完全
是自愿的行为,因为大门上没有“禁止入内”的标志,房门没有上锁,窗户也是敞开的,而对
于 Ada 语言来说,信息隐蔽则是强制性的:门被牢牢地锁上了,窗户紧闭,而且警报系统也在
工作,你所看到的就是你所得到的,而且是你得到的一切。
模块化
面向对象设计中的模块与结构化设计中模块的含义是一致的。相联系的数据和功能被放       
入模块,在理想情况下,模块是高度内聚而又松散耦合的。同信息隐蔽一样,当模块内部发生
变化时,其接口保持不变。
层次结构和继承性( inheritance)
在设计软件系统时,你经常可以发现两个之间非常相似,其差别非常小的目标。例如,在
一个会计系统中,你可能既要考虑正式雇员,也要考虑兼职雇员,与两种雇员相联系的绝大多
数数据都是相同的,但也有一小部分是不同的。在面向目标编程中,你可以定义一种广义雇员,
把正式雇员当作一种广义雇员,但二者之间稍有差别。把兼职雇员也看作一种与种类无关的广
义雇员,那么这种操作就按广义雇员模式进行。如果某种操作是与雇员种类有关的,那么就按
雇员种类不同,分别进行操作。
定义这种目标间的共同和不同点称为“继承性”,因为兼职和正式雇员都从广义雇员那里继
承了许多特点。
    继承策略的好处是它与抽象的概念是一致的,抽象在不同层次的细节上与目标打交道。在
某一层次上调用某种分子;在下一个层次上调用纤维与植物细胞的组合,在最高层次上调用一
片木头。木头有某种特性(如你可以用锯子锯它或用斧头劈它),不管是松木还是加州红杉木,
它们有许多共同的特性,如同它们有不同特性一样。
    在面向对象编程中,继承性简化了编程,因为你只要写一个通用子程序来处理目标间的共
同特性,再编写几个专用子程序去处理它们间的不同特性就可以了。有些操作,例如Getsize(),
可能在任何抽象层次上都适用。程序语言支持Getsize()这种直到运行时才需要知道操作对象的
子程序特性称为“多形性”。像C++等面向对象的语言,自动支持继承性和多形性。而以目标为
基础的语言,如Ada和过程性语言如C 和Pascal则不支持这种特性。
TinKingTZW - 2006-8-25 16:30:00
目标与类
在面向对象设计中,最后一个关键概念是目标与类。目标是程序在运行时其中的任何一个
实体,而类则是当你看程序清单时存在的一个静态实体。目标是在程序运行时具有特定值和属
性的动态实体。例如,你可以定义一个具有名字、年龄、性别等属性的人,而在运行时则会遇
到Nancy,Tom 等人,也就是说,目标是类的特例,如果你对数据库术语很熟悉的话,它们的
区别与其中“模式”与“事例”的区别是类似的,在本节其余部分将不严格区分这些词,常会
把两种实体都称为“目标”。
7.3.2  面向对象设计的步骤
面向对象设计的步骤是:
•  识别目标及其属性,它往往是数据。
•  确定对每个目标可以做些什么。
•  确定每一个目标可以对其它目标做些什么。
•  确定每个目标对其它目标来说是可见的部分——哪一部分是开放的,哪一部分是专用
的。
•  确定每个目标的公共接口。
这些步骤下一定非要按某一特定顺序来进行,但是却需要重复。逐次迭代逼近对面向对象
设计是与其它设计方法同样重要的,下面将分别论述这些步骤。
识别目标及其属性。计算机程序通常是以客观世界实体为基础的,例如,你可以用客观世
界中的雇员,时间卡及帐单为基础来建造一个时间——记帐系统。图7-3 中表示了从面向对象
观点来看,这一系统的表现形式。
在图形系统中的目标将包括:窗口、对话框、按钮、字体、绘图工具等。对问题域进行进
一步研究,可能会发现比这种一对一方式更好的软件目标识别方法,但是,从客观世界中的真
实目标出发总是一个比较好的方法。
识别目标属性并不比识别目标困难。每一个目标都有与计算机程序相关联的特性。例如,
在时间——记账系统中,一雇员目标将具有姓名、标题和记账率;顾客目标将有姓名、支票地
址、收支状况及存款余额等;账单目标具有欠账数量、顾客姓名、日期等等。
图中目标的图形符号与第六章讲述的模块符号类似。
确定可以对一个目标做些什么。对每一个目标都可以进行一系列操作,在时间——记账系
统中,雇员的记账率等可以变动,可以要求提供雇员的奖励情况等,顾客目标可以更改其存款
额或地址等。
确定目标之间可以互相做些什么。这一步骤与其字面意义是相似的。在时间——记账系统
中,雇员可以对其它目标做些什么呢?做不了什么。雇员所能做的一切便是输入时间卡信息。
而账单则可以接受时间卡信息,在更复杂的系统中,其它相互作用更为明显。
确定每一个目标中对其它目标来说是可见的部分。在设计中的一个关键问题就是决定目标
的哪些部分应该是可见的,哪些部分应该是隐蔽的。对于数据和功能来说,都要做出这种确定。
在表示时间——记账系统的图7-2 中只表示出了可见的部分,隐蔽的部分则被藏在黑
TinKingTZW - 2006-8-25 16:31:00
盒子中。顾客与雇员目标看起来是非常复杂的,因为它们每一个都具有七八个可见的特性。这
种复杂的表现形式是图示法的一个弱点,这种情况会随着可见特性的增加而恶化。而一个精心
设计好的目标往往有许多附加的可见特性。
定义每一个目标的接口。在设计目标中的最后一个步骤是为每个目标定义一个正式的、语
法的、在程序层次上的接口。这包括由目标提供的功能和目标与类之间的继承关系。特别地,
这一步将包括函数和子程序说明。例如:时间卡的接口(用C++编写)可能是这样的:
class TimeCard
{
public:
int Enter
(
EMPLOYEE_ID Employee,
DATE Date,
CLIENT_ID Client,
PROJECT ProjectCode,
int Hours
);

附件: 7341152006825162343.bmp
TinKingTZW - 2006-8-25 16:32:00
int Retrieve
(
int& Hours,
DATE& Date,
CLIENT_ID& Client,
PROJECT ProjectCode,
EMPLOYEE_ID Employee
);
protected:

};
当你进行完这一步的工作,到达高层次的面向对象系统组织时,可以用两种方法进行迭代,
以便得到更好的目标——类组织。你也可以对定义的每一个目标进行迭代,把设计推向更详细
的层次。
7.3.3  典型面向对象设计的评论
一个面向对象系统通常有至少四类目标。如果你不知道这其中每类目标的一些情况,你很
可能会漏掉某类目标。
•  问题域要素。这个要素直接指向问题域,在前述的记账系统中,问题域包括客观世界中
的目标,如:雇员、顾客,时间卡和账单等。
•  用户接口要素。这个要素指的是系统中负责人机交互的部分。它包括数据入口类型、窗
口、对话框、按扭等等。正如6.2 节中提到的那样,最好把系统的用户接口部分隐蔽
起来以适应修改。
•  任务管理要素。这类要素指的是计算机本身的目标。包括实时任务管理程序、硬件接口、
通讯协议等。
•  数据管理要素。这部分要素包括保持一致的数据。它包括数据库以及其相联系的所有存
储、维护和检索等功能。
7.4  对目前流行设计方法的评论
如果你仔细观察日前流行的设计方法——包括结构化设计和面向对象设计——你会发现每
种方法都包括两个主要部分:
•  把一个系统分解成子系统的准则
•  解释分解的图形式语言符号
•  有些方法还包括第三个要素
•  防止你使用其它方法的规定
把“设计”的意义限制在前两个要素上说明设计的核心是把系统分解成亚程序
( Subprogram ) ,同时也说明亚程序的设计并不具备足够的挑战性,不值得讨论。
TinKingTZW - 2006-8-25 16:32:00
一个好的系统分解的确是很有价值的,但并不是说一旦确立了好的结构,设计就可以停止
了。在确认出子程序的模块之后,还有许多设计工作要做。
伴随着某些设计方法的第三个要素,即强调应该只使用一种方法的思想,是非常有害的。
没有一种方法囊括了设计系统所需的全部创造力和洞察力。强调使用一种方法将破坏设计中的
思维过程。
    但是,设计方法的选择往往会成为一种宗教似的问题——你去参加了一个宗教复兴会议,
听一些结构化设计的先知讲道,然后你回到自己的圣地在石碑上写下一些神圣的程序,从此以
后,你不再允许自己进入非基督教的领域。你应该知道,软件工程不是宗教,不应该引人宗教
的狂想性。
如果你是个建筑工人,你不会用同样的方法建造每一幢建筑物。在周一你在浇注水泥,而
到了周末你则在修建一幢木屋。你不会用水泥来修木屋,也不会在一幢新建好的摩天大楼门口
挂上“成人禁止入内”的牌子。你会根据不同的建筑物而采取不同的施工方法,从建造房子中,
你应该得到关于编程方法选择的启示,应该选择与问题相适应的方法,这种世俗方法的合理性
已经被许多例子和研究所证明。每种方法都有其优点,但同时也有其弱点,应分析使用(Peter
和Tripp 1977,Mannino 1987,Kelly 1987,Loy 1990)。
但是,有些方法的障碍是由它们自己的复杂的术语产生的。比如,你想学习结构化设计,
你必须懂得如下词汇:输入流与模块、输出流与模块、正规、控制、公用、全局和内容耦合、
功能的、顺序的、通讯的、过程的、临时的、逻辑的和偶然性内聚。输入、输出、事务处理中
心、事物处理分析和事物处理模块,甚至Benuzerfreundlichkeit(多可怕!)一词也出现了。字
典也无法给出这些词的解释。
    结构化设计,以隐蔽信息为目标的设计和面向对象设计等方法提供了看问题的不同角度。
图7-4 给出了使用它们的典型方法。
    从事结构化设计的人与从事面向对象设计的人会发现他们进行交流非常困难,原因是他们
没有意识到是在不同层次上讨论设计的,因此主题也是不同的。从事结构化设计的Tom说:“我
想这个系统应该分解成50个子程序。”面向对象设计的Joh则说:“我认为系统应划分成7个目
标”。如果你仔细观察,可能会发现这7 个目标中可能共含有50 个子程序,而Tom的子程序或
许可以分成7 组。
7.4.1  何时使用结构化设计
结构化设计主要是一种把程序分解成子程序的方法。它强调功能但不强调数据。一个面向
功能的问题域的例子是一个以批处理方式读入数据,按照可以预计的顺序对数据进行可以预计
的处理并且写数据。
结构化设计并没有把子程序组成一个协同工作子程序组的概念,也没有子程序内部设计的
概念,除非这个子程序的内部会影响到整个系统。因此,结构化设计非常适用于具有许多互不
作用的独立功能的系统。同时,它也适于那些只有几百行代码的小型程序,因为这些程序过于
简单,没有建立类、目标和属性的必要。
结构化设计的最先提出者Larry Constantine,曾经发表过一篇“目标、函数和程序可扩展性”
的文章,论述了把结构化设计和面向对象设计组合到一起的设计方法。如果数据变动可能性很
大,那么采用面向对象设计比较合适,因为它将变动可能性较大的数据独立在目标(模块)
TinKingTZW - 2006-8-25 16:33:00
中。如果功能变动的可能性较大,采用面向对象设计就不太适合了。因为功能散布在许多目标
(模块)中。如果功能变化的可能性比数据要大,那最好采用分解功能的结构化设计。
7.4.2  何时采用信息隐蔽
无论什么问题领域,都应该尽量采用信息隐蔽。使用它没有任何危险。到目前为止,联邦
卫生委员会还没有发现它会发生危险,不论是设计子程序、模块,还是目标程序,它都是很有
效的,因此你尽可以放心使用它。
7.4.3  何时采用面向对象设计
面向对象设计与结构化设计的主要区别是:面向对象设计在较高抽象层次上要比结构化设

附件: 7341152006825162528.bmp
TinKingTZW - 2006-8-25 16:41:00
计有效。这是具有重大历史意义的。因为结构化最初开发起来时程序员们正在建立定义大而复
杂的系统,到现在已经远不及如今的大规模系统那样复杂了。
面向对象设计主要是设计模块数据和对数据操作的集合。它非常适于从最顶层分解系统。
而当你识别出目标的接口并开始编码时,你往往会转向结构化设计。如果你用面向对象的语言
编程,那么很难认为你没有在面向对象设计,因为你用面向对象方法、信息或其它进行结构设
计工作。如果你是在用比较传统的过程性语言进行设计,则很容易认为你是在用较旧的结构化
设计方法,在这时,使用这一方法是很合适的。
面向对象设计适合于任何客观世界中的目标。这类系统的例子包括高度交互化的窗口、对
话框、按钮等程序;面向对象的数据库;需要对随机事件做出反应的事件驱动系统等等。
关于面向对象设计技术的研究工作主要是针对代码在105~1005 行以上的系统的。对于这
种规模的系统,结构化设计基本上是无能为力的,而面向对象设计则往往比较有效。然而,除
了这些超大型项目之外,稍显陈旧的结构化设计还是比较有效的,而面向对象设计对于较小型
项目的有效性,却还有等待证明。
7.5  往返设计
通过组合使用主要设计方法来扬长避短是完全可能的。每种设计方法都只是程序员工具箱
中的一件工具,不同的工具适合不同的工作,你将从研究所有方法的启发中获益无穷。
下面一小节结论述了软件设计为什么困难的某些原因,并指出了如何组合使用结构化设计,
面向对象设计和其它设计方法。
7.5.1  什么是往返
你可能会有这样的体验:当你编写程序快结束时,你非常希望能有机会再重新编写一次,
因为在编写过程中你对问题又有了更深的理解。这对设计也是同样适用的,只不过在设计中这
个循环的周期更短,带来的好处也更大,因此,你完全可以在设计过程中进行几次往返。
“往返设计”一词抓住了设计是个迭代过程这一特点:通常你不会只从A点走到B 点,往
往还需要再返回A 点。
在用不同的设计方法对各种设计方案进行尝试的进程中,将从高层次的总体上和低层次的
细节上对问题进行观察。在从事高层次问题时获得的总体印象将会对你在低层次细节中的工作
有很大帮助;同时,在从事低层次问题时所获得的细节将为你对高层次的总体理解和作出总体
设计决定奠定下良好的基础。这种在高层次和低层次之间往返思维过程是非常有益的,由此而
产生的结构,将比单纯自顶向下或自底向上产生的结构要稳定得多。
许多程序员,都会在这一往返过程中遇到麻烦。从对系统的一个观察点转到另一个观察点
上,的确是很困难的,但这是进行有效设计所必需的,你可以阅读一下Adams 于1980 年写的
一本叫“Conceptual Blockbusting”的书,来提高自己思维的灵活性。
7.5.2    设计是一个复杂的过程
  J.P Morgon曾经说过人们在做事情时常常有两个原因:表面上冠冕堂皇的原因和真正的
原因。在设计中,最终结果往往看起来是井井有条的,似乎设计者从未犯过任何设计错误,事
TinKingTZW - 2006-8-25 16:41:00
实上,设计过程很少有像最终结果那样井井有条。
设计是一个复杂的过程。因为你很难把正确答案与错误答案区分开来。如果你让三个人分
别设计同一个程序,他们带回来的往往是三个大相径庭的方案,而且其中每一个看起来都非常
适用。它是一个复杂的过程还因为你在设计过程中曾钻过许多死胡同、犯过许多错误。说它是
一个复杂的过程也是因为你不知道什么时候设计方案已经足够完善了。什么时候算完成呢?对
这个问题的通常答案是“当你没有时间时”。
7.5.3  设计是一个“险恶”的过程
Horst Rittel和 Melvin Webber 把“烦人”的问题,定义成只有通过解决它或者部分解决它,
才能给出明确定义的问题。这个似是而非的定义事实上暗示着你不得不首先“解决”这个问题,
对其有一个清楚的定义和理解,然后再重新解决一遍,以获得正确的解决办法。这一过程对软
件开发就像母爱和面包对你我一样必不可少。
在现实中,关于险恶问题的一个富于戏剧性的例子便是托卡马大桥的设计。在修建大桥时,
主要考虑的便是它应该能承受设计载荷并能抗12级大风。然而,没人想到风在吹过桥时会产生
“卡门旋涡”——一种特殊的空气动力学现象,从而使桥产生横向简谐振动。结果,在1940 年
的一天,只在7 级风的作用下,桥便因振动而坍塌了。
说它是险恶问题的典型例子是因为直到桥坍塌时,也没有一个设计师想到应该在这个设计
中考虑空气动力学问题。只有在建成大桥(解决问题)之后,才使得他们意识到了要考虑的这
一“额外”问题,经过重新设计,新桥至今依然屹立在河上。
你在学校中设计的程序和在实际工作中设计的程序最重要的不同是:在学校中遇到的程序
设计问题,几乎没有哪个是险恶的,教师留给你的程序作业都是预先想好让你一次即可完成的。
如果哪位教师留给你们一个程序作业,当你们完成后他又突然改变了作业题目,接着,当你即
将完成那个程序时,他又改变了主意,会怎么样呢?我想,如果有谁胆敢这样的话,你们肯定
会把它绞死。但在实际工作中,几乎总是这样。
7.5.4  设计是一个启发的过程
进行有效设计的关键是要认识到它是个启发的过程。设计中,总是吃一堑,长一智的。往
返设计的概念事实上解释了设计是个启发过程这一事实,因为你要把任何设计方法都只当成一
种工具。一种工具只对一种工作或者一种工作的某一部分才有效,其余的工具适合其它的工作,
没有一种工具是万能的。因此,你往往要同时使用几种工具。
一种很有效的启发工具就是硬算。不要低估它。一个有效的硬算解决方案总比优雅却不能
解决问题的方案要好。以搜索算法的开发为例,虽然在1946年就产生了对分算法的概念,但直
到16 年后,才有人找到了可以正确搜索各种规模的表的算法。
图示法是另一种有力的启发工具。一幅图抵得上一千个单词。你往往不愿用那一千个单词
而宁愿用一幅图,因为图形提供了比文字更高的抽象水平,有时或许你想在细节上处理某一问
题,但是,更常见的是在总体上处理问题。
往返设计的一个附加的启发能力是你在设计的头几次循环中,可以暂时对没有解决的细节
问题弃之不管,你不必一次就对一切都做出决定,应记住还有一个问题有待做出决定,但同时
要意识到,你目前还没有充分的信息来解决这个问题。为什么在设计工作的最后10%的部分苦
TinKingTZW - 2006-8-25 16:42:00
苦挣扎呢?往往在下一循环中它们会自然获得解决。为什么非要在经验和信息都不足的情况下
草率决定呢?你完全可以在以后等经验和信息丰富时做出正确决定。有些人对一次设计没能彻
底解决问题会感到很不舒服,但与其很不成熟地勉强解决问题,不如把问题暂放一个,待到信
息足够丰富时,再解决它。
最重要的设计原则之一是不要死抱着一种方法不放。如果编写PDL无效的话,那么就作图,
或用自然语言写出来,要么就写一小段验证程序,或者使用一种完全不同的方法,比如硬算解
决法,坚持用铅笔不停地写和画,大脑或许会跟上。如果这一切都无效,暂时放开这个问题。
出去自由自在地散散步,或者想一下别的,然后再回到这个问题上。如果你已经尽了全力但还
是一无所获,那么暂时不考虑这个问题往往会比坚持冥思苦想更快得到答案。最后,可以借鉴
其它领域中的方法来解决软件设计中的问题。关于问题解决中的启发方法的最初的一本专著是
G. Polya 的《How To solve in》一书(1957),Polya的书推广了数学中解决问题的方法,表 7-1
就是对其所用方法的总结,本表摘自Polya 的书中的类似的总结表:
表7-1  怎样解决问题

l.理解问题,你必须理解要解决的问题
问题是什么?条件是什么?数据是什么?有可能满足条件吗?已知条件足以确定未知
吗?已知条件是否不够充分?是否矛盾7 是否冗余?
画一个图,引入恰当的符号,把条件的不同部分分解开。
2.设计一个方案。找到已知数据和未知之间的联系。如果不能找出直接联系的话,你可能不得
不考虑一些辅助问题,但最后,你应该找到一个解决方案。
以前你是否遇到过这个问题?或者是见过与它稍有不同的问题?是否知道与其相关的问
题?是否知道在这个问题中有用的定理?
看着未知!努力回忆起一个有着相同或类似未知的问题。这里有一个与此相关的你以前
解决过的问题,你能利用它吗?是能利用它的结论还是能用它的方法?是否该引入辅助
要素以使这个问题可以再用?
能否重新表述一下问题?能用另外一种方式表述它吗?返回到定义。
如果你无法解决这个问题,可以先试着解决一些别的问题,是否能想象出一个容易解决的
相关问题;一个广义些的问题或是一个更特殊的问题?一个相似的问题呢?能否解决问
题的一部分呢?仅保留一部分条件,忽略其余条件;未知可以被决定到什么程度?会发生
什么变化?能否从数据中推导出一些有用的东西?能否找出适于确定未知的其余数据?
能否改变数据或未知?同时改变两者呢?这样做能否使新的未知和新的数据更接近些?
是否使用了全部的数据?使用全部条件了吗?是否考虑了这个问题的全部必要条件?
3.执行你的计划。
执行你解决问题的计划,同时检查每一步工作。你是否可以认定每一步都是正确的?你
能证明这点吗?
4.回顾,检查一下答案。
你能检查一下答案吗?能检查一个论证吗?能否用另外一种方法推导出答案?能否一眼
就看出答案?
能否在其它问题中再利用本题的答案或者结论?
7.5.5  受迎的设计特点
高质量的设计往往有一些共同的特点。如果你能达到这些目标,那么可以认为你的设计也
是非常成功的。有些目标是互相矛盾的。但是这是设计的挑战所在,在相互矛盾的目标之间做
TinKingTZW - 2006-8-25 16:42:00
出合理的折衷。某些高质量设计的特点同时也是高质量程序的特点——可靠性。其余的则是设
计所独有的。
以下是设计所独有的一些特点:
智力上的可管理性。对于任何系统来说,智力上的可管理性都是其重要目标之一。它对于
整个系统的完整性是非常重要的,并且会影响程序员们开发和维护系统的难易程度。
低复杂性。低复杂性实际上是智力上的可管理性一部分,由于上述同样的原因,这点也很
重要。
维护的方便性。维护的方便性意味着设计时要为负责维护的程序员着想。在设计中,要不
停地想象维护程序中将会对你的设计提出的问题。应该把维护程序员当作你的听众,同时把系
统设计成明白易懂的。
最小的联系性。最小的联系性指的是按照保持子程序之间的联系最少的原则来设计,应该
利用强内聚,松散耦合和信息隐蔽等作为指导原则来设计系统,使其内部的联系性尽可能少。
最小的联系性可以极大地减小综合、测试和维护阶段的工作量。
可扩充性。可扩充性指的是不必影响系统的内部结构,就可以对系统的功能进行强化,你
可以改变系统的某一部分而不影响其余部分,使得最大可能性变动对系统带来的影响最小。
可重复使用性。 可重复使用性指的是把系统设计成其中许多部分是可以被其它系统借用
的。
高扇入。高扇入指的是对于一个给定的子程序来说,应该有尽可能多的子程序调用它。高
扇入表明一个系统在低层次上充分利用了功能子程序。
低或中等程度扇出。低或中等扇出指的是对一个确定的子程序来说,它所调用的子程序应
该尽可能地少。高扇出(大约7 个以上)说明一个子程序控制了许多其它子程序,因此可能是
很难理解的。而中等扇出则表明一个子程序只把任务交给了数量较少的其它子程序,因此是比
较容易理解的。低扇出(少于4 个)看起来像是一个子程序没有把足够的任务交给其余的子程
序去做,但经验表明并不是这样。一项研究表明有42%只调用一个子程序的子程序是没有错误
的,有32%的调用 2~7 个子程序是没有错误的,而在调用7 个以上子程序的情况中,只有12%
是没有错误的(Card, Church 和Agresi,1986)。由此,Card认为0~2 个扇出是最优的。
可移植性。可移植性指的是把系统设计成很容易转到另外的环境下运行。
简练性。简练性指的是把系统设计得没有任何多余部分。Voltaire曾说过,当一本书不能删
掉,而不是不能添补任何内容时,才可以认为它已完成了。在软件中,这也是非常正确的,因
为当你对系统进行改进时,你不得不对冗余的代码进行开发、评审、测试和维护等等工作,而
且在开发软件的新版本时,新版本也不得不与这些冗余的代码兼容。最有害的观点是“多加入
些又不会有害,怕什么呢?”
成层设计。成层设计指的是尽量分解的层次是成层的,这样你可以在每一个单独的层次上
观察系统,同时也可以使观察的层次是连续的。也就是说当你在某一层次上观察系统时,不会
看到在其它层次上看到的东西。你会经常遇到某些子程序和软件在几个层次上起作用。这样会
使系统很混乱,应尽力避免。
如果在编写一个先进系统时,不得不借用许多旧的、设计得不好的代码,那么你可以在新
系统中建立一个层(layer),与那些旧代码相联接。精心设计这个层使它把旧代码的缺点隐含起
来,从而使新层表现了一整套连续的功能。然后,让程序的其余部分调用些子程序而不是直接
TinKingTZW - 2006-8-25 16:43:00
调用旧代码。成层设计的好处是:(l)它可以使你避免与拙劣的旧代码直接打交道;(2)一旦
你想废弃那些旧代码中的子程序的话,只要修改一下接口层就可以了。
标准化技求。标准化技术是深受欢迎的。一个系统使用的奇特的、非标准的技术越多,当
别人第一次读它时就会越感到可怕,也越难理解。应该通过采用常用的、标准化的技术使得人
们在阅读它时是一种熟悉的感觉。
7.5.6    检查表
高层次设计
本表给出了在评估设计质量时,通常要考虑一些问题。本表是3.4 节中结构设计检查表的
补充,这个表所考虑的主要是设计质量。3.4 节中的检查表则侧重于结构设计和设计内容。这
个表中的某些内容是相互重合的。
•  是否使用了往返设计方法,应从几个方案中选择最好的,而不是首次尝试就确定方案。
•  每个子程序的设计是否都和与其相关的子程序设计一致?
•  设计中是否对在结构设计层次上发现但并未解决的问题作了充分说明?
•  是否对把程序分解成目标或模块的方法感到满意?
•  是否对把模块分解成子程序的方法感到满意?
•  是否明确定义了子程序的边界?
•  是否是按照相互作用最小的原则定义子程序的?
•  设计是否充分利用了自顶向下和自底向上法?
•  设计是否对问题域要素、用户接口要素、任务管理要素和数据管理要素进行了区分?
•  设计是智力上可管理的吗?
•  设计是低复杂性吗? 
•  程序是很容易维护的吗?
•  设计是否将子程序之间的联系保持在最低限度?
•  设计是否为将来程序可能扩展作了准备?
•  子程序是否是设计成可以在其它系统中再使用的?
•  低层次子程序是高扇入的吗?
•  是否绝大多数子程序都是低或中等程度扇出的?
•  设计易于移植到其它环境吗?
•  设计是简练的吗?是不是所有部分都是必要的?
•  设计是成层的吗?
•  设计中是否尽量采用了标准化技术以避免奇特的、难以理解的要素?
7.6  小结
•  设计是一个启发的过程。固执地坚持某一种方法只会抑制创造力,从而产生低质量的程。
    坚持设计方法上有一些不屈不挠的精神是有益的,因为这可以迫使你对这种方法进行充
    分理解。但是,一定要确信你是在不屈不挠而不是顽固不化。
TinKingTZW - 2006-8-25 16:43:00
•  好的设计是通过迭代逼近得到的:你尝试过的设计方案越多,你最终所确定的设计方案
  也越好。
•  结构化设计比较适合于小规模的子程序组合,同时,它对于功能变化可能性比数据大的
      问题也是较适用的。
•  面向对象设计更适于子程序与数据的组合,通常在比结构化设计抽象程度更高些的层次
  上适用。它尤其适合于数据变动可能性大于功能变动可能性的问题。
•  设计方法仅是一种工具,你对工具运用得好坏决定了你所设计的程序的质量。利用不好
  的设计方法,也可能设计出高质量的程序。而即使是好的方法,如果运用不当的话, 也
  只能设计出拙劣的程序。但不管怎样,选择正确的工具更容易设计出高质量的软件。
•  许多关于设计的丰富而有用的信息都是在本书之外的。在这里所论述的,不过是冰山的
  一角而已。
TinKingTZW - 2006-8-25 16:44:00
第八章  生成数据
目录
8.l  数据识别
8.2  自建数据类型的原因
8.3  自建数据类型的准则
8.4  使变量说明更容易
8.5  初始化数据的准则
8.6  小结

相关章节
变量命名:见第9 章
使用变量时的一些考虑:见第10 章
使用基本数据类型:见第11 章
使用复杂数据类型:见第12 章
在结构设计阶段定义数据:见3.4 节
说明数据:见19.5 节
数据结构布置:见18.5 节

本章的内容既包括高层次的子程序、模块和程序设计中要考虑的问题,也包括对数据实现
问题基本要素的讨论。
数据结构在创建阶段能带来的收益大小,在某种程度上是由它对创建前的高层次工作影     
响大小决定的。好的数据结构所带来的收益往往是在需求分析和结构设计阶段体现出来的。为
了尽可能地利用好的数据结构带来的收益,应在需求分析和结构设计阶段就定义主要数据结构。
数据结构的影响同时也是由创建活动所决定的。由创建活动来填平需求分析与结构设计     
之间的壕沟是很正常也是很受欢迎的。在这种微观问题上,仅靠画几张蓝图来消除一致性的缺
陷是远远不够的。本章的其余部分将论述填平这一壕沟的第一步——生成进行此项工作的数据。
如果你是一位专家级的程序员,本章的某些内容对你来说可能已是司空见惯了。你可以浏
览一下标题和例子,寻找你不熟悉的内容看就可以了。

8.1  数据识别
有效生成数据的第一步是应该知道该生成什么样的数据结构。一张良好的数据结构清单是
程序员工具箱中的一件重要工具。对数据结构基本知识的介绍不在本书范围之内。但你可以利
用下面的“数据结构识别测试题”来看一下自己对其知道多少。
TinKingTZW - 2006-8-25 16:44:00
8.1.1  数据结构识别测验题
每遇到一个你所熟悉的词可以计1 分。如果你认为你知道某个词但并不知道其确切内容,
计0.5分。当作完题后,把你的得分加到一起,然后再根据测验题后面的说明来解释你的得分。

        ____抽象数据类型          ____文字型
        ____数组                  ____局部变量
        ____B 树            ____查找表
        ____位图            ____指针
        ____逻辑变量(布尔变量)____队
        ____字符变量          ____记录
        ____命名常量          ____回溯
        ____双精度              ____集合
        ____枚举流            ____堆栈
        ____浮点            ____字符串
        ____散列表            ____结构化变量
        ____堆            ____树
        ____索引          ____联合
        ____整型            ____数值链
        ____链表              ____变体记录
                                ____最后得分

    以下是得分解释办法(可随便些):
0~14 分  你是个初级程序员,可能是计算机专业一年级的学生,或者是一个正在自学
第一种语言的自学者。如果读一下后面列出的书的话,你将会学到很多。本
章的许多论述都是针对高级程序员的,如果你读完那些书再阅读本书将更
有益。
15~19 分  你是个中级程序员或是个健忘的富有经验的程序员虽然你对表中许多概念
都已经很熟了,但最好还是先阅读一下后面列出的书。
20~24 分  你是个专家级的程序员,你的书架上很可能已经插上了后面所列出的书。
25~29 分  关于数据结构,你知道的比我还多!可以考虑写一本你自己的专著(请别忘
了送我一本)。
30~32 分  你是个自大的骗子。“枚举流”、“回溯”和“数值链”并不是指数据结构
的,是我故意把它们加进去以增加难度的,在阅读本章其余部分之前,请先
阅读引言中关于智力诚实性的内容。
    以下是关于数据结构的一些不错的书:
Aho, Alfred V., John E. Hopcroft 《Data Structure and Algorithms, Reading,Mass》
Addison-Wesley,1983。
Reingold, Edward M 和Wilfred J.Hansen《Data Structures》, Boston: Little, Brown, 1983。
Wirth, Niklaus,《Algorithms and Data Structures》, Englewood Cliffs,N.J.;Prentice Hall, 1986。
TinKingTZW - 2006-8-25 16:44:00
8.2  自建数据类型的原因
程序语言所赋予你的阐明自己对程序理解的最强有力的工具之一便是程序员定义的变量
类型。它们可以使程序更容易阅读。如果使用C、Pascal 或其它允许用户定义类型的语言,那
么一定要利用这一点。如果使用的是Fortran,Generic Basic 等不允许用户定义变量类型的语言,
那接着看下去,或许读完本节后你就想换一种语言了。
为了说明自定义类型的效力,让我们来看一个例子。假设你正编写一个把x、y、z坐标转
换为高度、经度、纬度坐标的程序,你认为可能会用到双精度浮点数,但是在十分确定之前,
你只想用单精度浮点数。这时,可用C 中的typedef 语句或Pascal 中的type 说明来为坐标专门
定义一种变量(当然也可用其它相当的语言)。以下是在 C 中是如何建立这种变量的:
typedef float Coordinate_t; /* for coordinate variables */
这个类型定义说明了一种新的类型,Coordinate_t,在功能上它与float 是一样的,为了使
用这种新类型,应用它来说明变量,就像用float来预先定义一种类型一样。以下是一个用C写
成的例子:
Routine1(...)
{
Coordinate_t latitude; /* latitude in degrees */
Coordinate_t longitude; /* longitude in degrees */
Coordinate_t elevation; /* elevation in meters from earth center */
...
}
...
Routine2(...)
{
Coordinate_t x; /* x coordinate in meters */
Coordinate_t y; /* y coordinate in meters */
Coordinate_t z; /* z coordinate in meters */
...
}
在这段代码中,变量x,y,z和变量latitude,longitude,elevation都是Coordinate_t类型的。
现在,假设程序发生了变化,发现最终还是得用双精度变量。由于你为坐标专门定义了一
种类型,因此所要做的就是改变一下类型定义而已。而且只需在一个地方进行改动;在typedef
语句中。下面是改变后的类型定义:
typedef double Coordinate_t; /* for coordinate variables */  ——原浮点已改为双精度类型

下面来看第二个例子,它是用Pascal写成的。假设你正在生成一个工资发放系统,其中雇
员的名字至多有30 个字母。你的用户告诉你从来没有任何人的名字长度超过30 个字母。你是
否该在程序中把数字30作为文字型变量来使用呢?如果你这样作的话,那你就过于轻信你的用
TinKingTZW - 2006-8-25 16:44:00
户了。更好的办法是为雇员名字定义一种类型:
Type
EmployeeName_t = arrayy[1..3] of char;
当引入数组或字符串时,最好定义一个命名常量来表示数组或字符串的长度,然后
在类型定义中使用这个命名常量,在程序中,你会许多次用到了这个常量——以下
是你将使用它的第一个地方,它看起来是这样的:
Const
Namelenght_c = 30;——这里是命名常量的说明

Type
EmployeeName_t = array[1..NameLength_c] of Char;——这里是命名常量使用的地方

一个更有说服力的例子是将自定义类型与信息隐蔽的思想结合在一起。在某些情况
下,你想隐蔽的信息是关于数据类型的。
在坐标例子中用C 写成的程序已经在走向信息隐蔽的途中了。如果你总是用Coordinate_t
而不是用float或double,事实上已经隐蔽了数据的类型。在C 或Pascal中,这些便是语言本身
能为信息隐蔽所做的一切,其余部分便是你或后来的使用者必须遵守这个规则,不查看
Coordinate_t的定义。C 和Pascal所赋予的都是广义的而不是狭义的信息隐蔽能力。
其它像Ada和C++等语言则更进一步支持狭义的信息隐蔽。下面是在Ada 语言中,用包来
定义Coordinate_t的代码:
package Transformation is
type Coordinate_t is private; ——这个语句说明coordinate_t作为包的专用说明

以下是在另一个包中使用Coordinate_t的代码:
with Transformation:

procedure Routine1(…)…
latitude: Coordinate_t;
longitude: Coordinate_t;
begin
-- statements using latitude and longitude

end Routine1;
注意Coordinate_t在包定义中是说明为专用的,这意味着只有Transformation包中的专用部
分才知道Coordinate_t 的定义,而程序其余部分则都不知道。在有一群程序员的开发环境中,
只有包的定义部分才是开放的。对于从事另一个包的程序员来说,他是不可能查寻Coordinate_t
的类型的。在这里,信息是狭义隐蔽的。
这些例子已经阐明了建立自己的类型的几条理由:
· 使得改动更加容易。建立一种新类型工作量极小,但这却可以带来极大的使用灵活性。
TinKingTZW - 2006-8-25 16:44:00
· 避免过度分散的信息分布。硬性类型会使程序中充斥着数据类型细节,而不是使其集
中在一个地方。这正是 6.2 节中所讨论的集中化原则。
· 为了增加可靠性。在Ada和Pascal中,可以定义类似Age_t = 1~99的类型。然后,编
译程序会产生运行检查信息,以保证Age_t类型总是在1~99 的范围内。
· 为了补偿语言的弱点。如果语言中不具备某种定义好的类型,可以自己定义它。例如,
C 中不含逻辑类型,通过建立你自己的类型,很容易弥补这个缺点:
typedef int Boolean_t;
8.3  自建数据类型的准则
以下是几条生成自己的类型时应牢记的准则:
建立具有面向功能名称的类型。应避免用暗指计算机本身数据类型的名称。要使用代表实
际问题某一部分的名称。在上例中,为坐标建立的新类型命名就很恰当,因为它代表了客观世
界中的实体。同样,你也可以为现金、工资发放代号、年龄等客观世界中的实体建立新变量。
要避免使用含有已定义变量类型的名称。比如像 BigInteger和 LongString等指的是计算机
数据而不是客观世界中实体的名称就应避免使用。建立自己的类型其最大优点是,可以在程序
及其实现语言之间建立一个绝缘层,而指向程序语言类型的名称则会破坏这个绝缘层,使用已
定义的类型不会带来任何优点。面向问题的名称可以增加易改进性,并且使数据说明成为自注
释的。
避免使用已定义类型。如果类型存在变动的可能性,那么除了在typedef和type 定义之外,
不要再使用已定义的类型。建立面向功能的类型是非常容易的,而改变程序中该类型的数据是
非常困难的。而且,当使用自建的面向功能类型说明变量时,也同时对变量进行了说明。
Coordinate_x所告诉你的关于x 的信息要比float x 多得多。因此应尽可能使用自建类型。
不要对已定义类型重新定义。改变标准类型的定义往往令人困惑。例如,语言中已经有了
Integer 类型,而你又自建了叫作Integer 的类型。这样,程序的阅读者往往会记住你所定义的
Integer的含义,而仍把它当作语言中的标准 Integer类型。
定义替换类型以增强移植性。与避免重新定义标准类型的建议相反,你可能想为标准类型
定义一种替换类型,从而使得在不同的硬件环境下变量所代表的都是同一实体。例如,你可以
定义INT 类型来代替标准的int 类型,它们之间的唯一区别便是字母的大小写。但当你把程序
移到新的硬件环境下时,你只要重新定义一个INT类型,就可以在新的硬件环境下使得数据类
型与旧环境下相容。
如果你所用的语言是不区分大小写的,你将不得不使用其它办法来对标准类型和替换类型
加以区别。
使用其它类型来建立新类型。你可以在已经建立的简单类型的基础上建立复杂类型。这种
变量类型可以进一步推广你用原来类型所达到的灵活性。
8.4  使变量说明更容易
本节论述的是怎样才能更顺利地进行变量说明。显然,这是件很容易的事情,你甚至会认
123456
查看完整版本: 【转载】软件开发人员必备工具书 【代码大全】