软件接口设计指南.docx

上传人:b****7 文档编号:9761274 上传时间:2023-02-06 格式:DOCX 页数:17 大小:25.05KB
下载 相关 举报
软件接口设计指南.docx_第1页
第1页 / 共17页
软件接口设计指南.docx_第2页
第2页 / 共17页
软件接口设计指南.docx_第3页
第3页 / 共17页
软件接口设计指南.docx_第4页
第4页 / 共17页
软件接口设计指南.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

软件接口设计指南.docx

《软件接口设计指南.docx》由会员分享,可在线阅读,更多相关《软件接口设计指南.docx(17页珍藏版)》请在冰豆网上搜索。

软件接口设计指南.docx

软件接口设计指南

软件接口设计指南

拟制人日期

审核人日期

批准人日期

1目的

为大家在进行软件接口设计时提供一些指导,以帮助大家更好的理解软件接口设计的方法和原则。

2适用范围

适用于公司软件开发的接口设计过程。

3参考文件

本过程文件中的过程裁剪应依据《组织标准过程裁剪指南》的规定。

4定义和缩写

本过程文件的编写依据是美国软件工程研究院(SEI)的集成成熟度模型软件分支1.2版本(CMMI-DEVV1.2)。

5规定

5.1JAVA接口设计方法

我们在设计系统接口时,经常会遇到这样的问题:

我们的接口应该提供多少方法才合适?

我们的接口应该提供"原子方法"还是"复合方法"?

我们的接口是否应该封装(或者,能否封装)所有的细节?

接口的设计需要考虑用户的使用习惯、使用的方便程度、使用的安全程度,根据我的编程经验,下面会详细讨论接口设计的2个需要权衡的方面:

接口的单一化&复合化。

接口

接口提供了不同系统之间或者系统不同组件之间的界定。

在软件中,接口提供了一个屏障,从而从实现中分离目标,从具体中分离抽象,从作者中分离用户。

站在用户的角度看,一个接口建立并命名了一个目标对象的使用方法。

一些约束(例如:

编译时的类型系统、运行时的异常机制及返回值)使得类作者的目的得以体现和加强。

供给(affordances)指事物的被感知的真实的属性,这些属性可以决定事物使用的可能方法,供给提供了对事物操作的线索。

类设计者的一个职责便是在接口中减小约束与供给之间的隔阂、匹配目标以及一定程度上的自由度,尽可能减小错误使用目标对象的可能。

封装

对于封装来说,远不止数据私有那么简单。

在设计中,封装往往会涉及到自我包含(self-containment)。

如果一个类需要你知道如何调用它方法(e.g.在一个线程的环境中,在一个方法调用后调用另一个方法,你必须明确地同步对象),那么它的封装性就不如将所有这些全部包含并隐藏的类(e.g.这个类是thread-safe的)好。

前一个设计存在着设计的漏洞,它的许多限定条件是模糊的,而且把部分责任推给了用户,而不是让类提供者做这些工作来完成类的设计。

在空间或者时间上分离方法的执行(例如,线程,远程方法调用,消息队列),能够对设计的正确性和效率产生意义深远的影响。

这种分离带来的结果是不可忽视的:

并发引入了不确定性和环境(context)选择的开销;

分布引入了回调的开销,这些开销可能不断增加,而且会导致错误。

这些是设计的问题,修改它们可不是象修改bug那样简单。

如果一个接口主要由存取方法(set和get方法)组成,每个方法都相应的直接指向某个私有域,那么它的封装性会很差。

接口中的域存取方法通常是不会提供信息的:

他们在对象的使用中不能通讯、简单化和抽象化,这通常会导致代码冗长,并且容易出错。

所以,我们首先考虑接口设计的第一个原则:

命令与查询分离(Command-QuerySeparation)

要求:

保证一个方法不是命令(Command)就是查询(Query)

定义:

  查询:

当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质;

  命令:

当一个方法要改变对象的状态的时候,它就具有命令的性质;

通常,一个方法可能是纯的Command模式或者是纯的Query模式,或者是两者的混合体。

在设计接口时,如果可能,应该尽量使接口单一化,保证方法的行为严格的是命令或者是查询,这样查询方法不会改变对象的状态,没有副作用(sideeffects),而会改变对象的状态的方法不可能有返回值。

也就是说:

如果我们要问一个问题,那么就不应该影响到它的答案。

实际应用,要视具体情况而定,语义的清晰性和使用的简单性之间需要权衡。

例如,在java.util.Iterator中,hasNext可以被看作一种查询,remove是一种命令,next合并了命令和查询:

publicinterfaceIterator{

booleanhasNext();

Objectnext();

voidremove();

}

这里,如果不将一个Iterator对象的当前值向前到下一个的话,就不能够查询一个Iterator对象。

如果没有提供一个复合方法next,我们将需要定义一系列的命令方法,例如:

初始化(initialization)、继续(continuation)、访问(access)和前进(advance),它们虽然清晰定义了每个动作,但是,客户代码过于复杂:

for(initialization;continuationcondition;advance){

...accessforuse...

}

将Command和Query功能合并入一个方法,方便了客户的使用,但是,降低了清晰性,而且,可能不便于基于断言的程序设计并且需要一个变量来保存查询结果:

Iteratoriterator=collection.iterator();

while(iterator.hasNext();){

Objectcurrent=iterator.next();

...usecurrent...

}

下面,我们考虑接口设计的第二个原则:

组合方法(CombinedMethod)

组合方法经常在线程和分布环境中使用,来保证正确性并改善效率。

一些接口提供大量的方法,起初,这些方法看来是最小化的,而且相关性强。

然而,在使用的过程中,一些接口显现得过于原始,它们过于简单化,从而迫使类用户用更多的工作来实现普通的任务,并且,方法之间的先后顺序及依赖性比较强(即,暂时耦合)。

这导致了代码重复,而且非常麻烦和容易出错。

一些需要同时执行成功的方法,在多线程、异常、和分布的情况下会遇到麻烦。

如果两个动作需要同时执行,它们由两个独立的方法进行描述,必须都完全成功的执行,否则会导致所有动作的回滚。

线程的引入使这种不确定性大大增加。

一系列方法同时调用一个易变的(mutable)对象,如果这个对象在线程之间共享,即使我们假设单独的方法是线程安全的,也无法确保结果是意料之中的。

看下面对EventSource的接口,它允许安置句柄和对事件的查询:

interfaceEventSource{

HandlergetHandler(Eventevent);

voidinstallHandler(Eventevent,HandlernewHandler);

}

线程之间的交叉调用可能会引起意想不到的结果。

假设source域引用一个线程共享的对象,对象很可能在1、2之间被另一个线程安装了一个新的句柄:

classEventSourceExample{

publicvoidexample(Eventevent,HandlernewHandler){

oldHandler=eventSource.getHandler(event);//1

//对象很可能在这里被另一个线程安装了一个新的句柄

eventSource.installHandler(event,newHandler);//2

}

privateEventSourceeventSource;

privateHandleroldHandler;

}

为了解决问题,也需要由类的使用者而不是类的设计者来完成:

classEventSourceExample{

publicvoidexample(Eventevent,HandlernewHandler){

synchronized(eventSource){

oldHandler=eventSource.getHandler(event);

eventSource.installHandler(event,newHandler);

}

}

privateEventSourceeventSource;

privateHandleroldHandler;

}

我们假设:

目标对象eventSource是远程的,执行每一个方法体的时间和通讯的延迟相比是很短的。

在这个例子中,eventSource的方法被调用了两次,并可能在其他的实例中重复多次,因而,开销也是至少两倍。

此外还有一个问题是对外部的synchronized同步块的使用需求。

对synchronized块的使用之所以会失败,主要因为我们通过代理对象来完成工作,所以,调用者的synchronized块,同步的是代理对象而不是最终的目标对象,

调用者不可能对其行为做太多的保证。

CombinedMethod必须在分布的环境,或者,线程环境中同时执行。

它反映了用户直接的应用,恢复策略和一些笨拙的方法被封装到CombinedMethod中,并简化了接口,减少了接口中不需要的累赘。

CombinedMethod的效果是支持一种更像事务处理风格的设计。

在一个组合的Command-Query中提供一个单独的Query方法通常是合理的。

提供分离的Command方法是不太常见的,因为CombinedMethod可以完成这一工作,只要调用者简单的忽略返回结果。

如果返回一个结果招致一个开销的话,才可能会提供一个单独的Command方法。

回到前一个例子中,如果installHandlermethod返回上一次安装的句柄,则设计变得更加简单和独立:

interfaceEventSource{

HandlerinstallHandler(Eventevent,HandlernewHandler);

}

客户代码如下:

classEventSourceExample{

publicvoidexample(Eventevent,HandlernewHandler){

oldHandler=eventSource.installHandler(event,newHandler);

}

privateEventSourceeventSource;

privateHandleroldHandler;

}

这样,我们给调用者提供了一个更加安全的接口,并且不再需要他们解决线程的问题。

从而降低了风险和代码量,将类设计的职责全部给了类设计者而不是推给用户,即使有代理对象的出现也不会影响到正确性。

一个CombinedMethod可以是许多Query的集合,许多Command的集合,或者两者兼有。

这样,它可能补充Command、Query方法,也可能与之相抵触。

当冲突发生的时候,优先选择CombinedMethod会产生一个不同的正确性和适用性。

在另一个例子中,我们考虑获得资源的情况。

假设,在下面的接口中,方法acquire在资源可用前阻塞:

interfaceResource{

booleanisAcquired();

voidacquire();

voidrelease();

}

类似于下面的代码会在一个线程系统中推荐使用:

classResourceExample{

publicvoidexample(){

booleanacquired=false;

synchronized(resource){

if(!

resource.isAcquired())

resource.acquire();

else

acquired=true;

}

if(!

acquired)

...

}

privateResourceresource;

}

然而,即使我们放弃可读性和易用性,这样的设计也不是一个Command-Query分离的设计。

如果引入了代理,它就会失败:

classActualResourceimplementsResource{...}

classResourceProxyimplementsResource{...}

如果用户既可以通过ActualResource来完成工作,也可以通过ResourceProxy来完成工作,而且,ActualResource和ResourceProxy都没有处理同步,则synchronized块可能会失败。

因为,既然我们可以通过代理对象ResourceProxy来完成工作,那么,调用者的synchronized块,同步的就是代理对象ResourceProxy而不是最终的目标对象ActualResource。

一个CombinedMethod解决了这个问题,它使并发和间接性更加透明。

interfaceResource{

booleantryAcquire();

}

下面的代码清晰、简单并且正确:

classResourceExample{

publicvoidexample(){

if(!

resource.tryAcquire())

...

}

privateResourceresource;

}

CombinedMethod带来的一个结果是使一些测试和基于断言的程序设计变得十分笨拙,然而,它适合解决线程和分布问题。

实际应用中,接口应该单一化还是复合化,要视具体情况而定。

5.2C++接口设计方法

在系统中,观察一个class有两个角度,从外部或者用户角度我们看到的是接口,从内部我们看到的是实现。

因为系统肯定要不断修改,因此实现免不了不停的变化,但是接口又被要求尽量保持稳定。

这两者的矛盾必须通过良好的设计尽量避免,基本原则就是将实现细节与接口隔离。

下面列出几条比较具体点的:

  ·接口的设计保持最小而完整

  精简接口函数个数,使每一个函数有代表性,函数功能恰好覆盖class的职能。

一个最小的接口可以使维护简单,增加潜在的代码重用性,减少客户的迷惑,并且也可以缩小头文件长度和编译时间。

当改进函数时,应该用类似函数名实现改进而保留原函数,代码注释里应该有相应的说明。

可以增加新函数,但不能删除旧函数。

  ·成员变量应该都为私有,显而易见,public变量破坏封装性以及接口和实现的分离;protected变量也可能使客户编写继承类而依赖于父类的实现细节。

  ·避免函数返回成员变量的指针或引用,这么做也会使客户代码依赖于实现细节。

  ·考虑是否禁用编译器缺省产生的函数,这些函数包括:

复制构造函数,赋值操作符(operator=)。

如果我们不打算定义自己的版本而不禁用默认版本的话,可能使客户代码在不注意的情况下调用这些函数。

当实现发生改动时就可能引起问题,比如class多了一个heapmemory指针。

如果我们允许对象拷贝,比较稳妥的方法是禁用它们,而定义一个专门的clone()函数。

  兼容性(compatibility)

  不用说,兼容性是非常重要的。

Intel和Microsoft之所以如此成功,其中一个重要方面就是他们的产品,不管是硬件还是软件,都做到了很好的兼容老产品。

代码的兼容也是如此。

难以想象,如果客户依赖于你的library产品,而要因为你的产品的更新而不断的重写他的代码,他还会继续用你的产品。

  代码兼容可以简单分为二进制兼容和源代码兼容。

二进制兼容也就是说,客户的已编译代码可以在不用重新编译的情况下,直接使用你的不同版本的已编译代码。

源代码兼容就是,如果你的代码更新了,客户的代码不需要修改,只需要重新编译就可正常运行。

在C++中,接口一般是由头文件和library二进制代码提供,因此,任何

可能造成library代码和旧的头文件不一致的情况都可能破坏二进制兼容,因为客户代码必须和新的头文件重新编译一次。

  因此,遵循几条准则可以使你更轻松地解决兼容性问题:

  ·不改变类的大小或者改变成员变量的顺序

  包括几个方面:

不增加或减少成员变量;不修改成员变量类型;不改变成员变量的声明顺序;不改变虚函数的有无。

显而易见,增加或减少成员变量会改变类的大小,并且需要更新头文件,从而可能造成与客户代码不兼容。

类型的变化也可能引起类的大小的变化。

成员变量的访问一般是由编译器按偏移量确定,顺序如果改变,偏

移量也就会改变,破坏了二进制兼容。

至于虚函数的有无,决定是否存在虚函数表指针,也就影响了类的大小和成员变量的顺序。

  ·不使用inline函数

  inline函数声明于头文件中,并且被编译于客户代码中,如果inline函数访问了private成员,该成员又改变了顺序,那么inline函数虚要被重新编译,破坏了二进制兼容。

  ·接口函数不使用虚函数

  虚函数的访问和成员变量类似,是通过虚函数表中的偏移。

虚函数顺序的改变会影响偏移。

因此,在条件允许时,应该避免使用public虚函数。

比如:

classPicture{

 public:

  virtualvoidDraw();

};

  应该改为

classPicture{

 public:

  voidDraw();

 private:

  virtualvoidDoDraw();

};

voidPicture:

:

Draw()

{

 DoDraw();

}

·不改变接口函数的顺序

在很多嵌入式系统中,链接库通过输出函数表(exportedfunctiontable)暴露接口以节省空间。

此时,对接口函数的访问也是通过索引值进行,因此改变顺序也会破坏兼容性。

·避免使用函数缺省参数

给函数形参设定缺省值可以方便客户,但是可能破坏兼容。

缺省值随头文件给出,缺省值的改变也就会引起兼容问题。

5.3接口设计对软件性能的影响

性能方面的问题有好多种。

最容易修正的一种是,在执行一项计算任务时使用了一个性能不好的算法,例如,在对数目很多的数据进行排序时采用了起泡算法,每次使用时对一个经常使用的数据项进行计算而不是将它保存起来,这些问题一般我们都能很容易发现,而且一旦发现后,都能很方便地进行改正。

然而,许多Java程序性能方

面的问题都是是由一些比较深奥的、不容易修改的代码━━程序组件的接口设计引起的。

大多数的程序都是由内部人员开发的或从外部购买的组件"组装"而成的。

即使软件不完全依赖于原有的组件,面向对象的设计过程也使得应用程序在开发时采用组件形式,因为这样可以简化程序的设计、开发和调试方面的工作。

尽管采用组件的好处是不可否认的,我们还应该意识到组件的接口会对使用它们的程序的性能和运行状态产生重大的影响。

也许会有读者问,接口跟性能有什么关系?

一个类的接口不但定义了类可以完成的功能,而且还定义了它的对象创建行为和使用它所需要调用的方法的顺序,一个类如何定义它的构造器和方法会影响这个对象是否可以重用,是它本身的方法创建还是要求其客户创建中间对象,客户要使用这个类需要调用多少个方法。

所有这些因素都会影响到程序的性能。

Java软件性能管理方面的基本原理之一是:

避免创建过多的对象。

这并不意味着你不能创建任何对象从而不充分利用面象对象语言带来的诸多好处,而是说在开发对性能敏感的代码时需要对对象的创建保持谨慎。

对象创建的代价相当高昂,我们应该在对性能敏感的软件中尽量避免创建临时或中间对象。

在处理字符的程序中,String类是引起对象创建的最大源。

因为String类是不可变的,每当一个String类的对象被修改或构造时,都会创建一个新的对象。

因此,一个具有性能意识的编程人员总是避免过多地使用String类对象。

然而,尽管你在编程中尽量避免使用String对象,还是会经常发现使用的组件接口必须使用String对象,因此,你不可能不使用String类对象。

  例子:

表达式的匹配

  作为一个例子,可以假设你在编写一个名字为MailBot的邮件服务器。

MailBot需要处理每个邮件顶部的MIME头部━━例如发送日期或者发送者的邮件地址,它将通过使用一个匹配表达式的组件处理MIME头部,以使这一处理过程会更简单一些。

它把输入的字符放在一个字符缓冲区中,通过对缓冲区进行索引处理标题。

由于MailBot将

调用这一表达式匹配子程序来处理每一个标题,因此这个匹配子程序的性能将十分地重要。

 我们首先来看一个性能十分低下的表达式匹配类的接口:

 publicclassAwfulRegExpMatcher{

  /**创建一个给定表达式的匹配过程,它将对给定的字符串进行处理*/

  publicAwfulRegExpMatcher(StringregExp,StringinputText);

  /**找到针对输入文本的下一个匹配模式,如果匹配,返回匹配的文本,否则返回一个空字符*/

  publicStringgetNextMatch();

 }

即使这个类采用了一个很高效的匹配算法,大量调用它的程序的性能也不会很好。

因为匹配器对象是与输入文本捆绑在一起的,每次调用它时,都需要首先生成一个新的匹配器对象。

由于我们的目标是减少不必要的对象创建工作,实现对匹配过程代码的重用应该是一个良好的开端。

下面的这个类定义了匹配器的另一种可能的接口,它允许匹配器重用,但性能仍然不够好:

 publicclassBadRegExpMatcher{

  publicBadRegExpMatcher(StringregExp);

  /**试图针对输入文本匹配指定的表达式,如果匹配则返回匹配的文本,否则返回一个空白字符串*/

  publicStringmatch(StringinputText);

  /**得到下一个匹配的字符,否则返回一个空白字符*/

  publicStringgetNextMatch();

  }

避开返回的匹配子表达式等敏感的表达式匹配问题不谈,这个类的定义有什么问题吗?

如果仅仅从其功能方面看,它没有任何问题,但如果从性能方面来考虑,则它存在许多问题。

首先,匹配器要求其调用者创建一个String来表示被匹配的文本。

MailBot应该尽量避免生成String对象,但当它发现一个需要处理的标题时,它必须创建一个String对象供BadRegExpMatcher调用:

  BadRegExpMatcherdateMatcher=newBadRegExpMatcher(...);

   while(...){

    ...

    StringheaderLine=newString(myBuffer,thisHeaderStart,

      thisHeaderEnd-thisHeaderStart);

    Stringresult=dateMatcher.match(headerLine);

    if(result==null){...}

  }

其次,即使MailBot仅仅需要得到是否匹配的返回信息,而无需得到匹配的文本,匹配器也会返回一个匹配的字符串。

这意味着为了简单地使用BadRegExpMatcher来验证一个特定格式的日期标题,你也必须创建二个String对象━━供匹配器使用的输入文本和匹配结果文本。

创建二个对象似乎不会对性能产生重大影响,但如果必须为MailBot处理的每条邮件的标题创建二个对象,就可能严重地影响程序的性能。

这一问题并不出在MailBot本身的设计上,而是出在BadRegExpMa

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工作范文 > 演讲主持

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1