第11章用组件开发一致的界面什么能算是好的界面,标准是非常主观的。许多商业应用程序都跟随着WinTel标准:灰色的按钮和控件,白色的背景。对商务程序来说,这可能是个不错的主意,因为通过多年的熟悉使得这个界面在某种程度上较为舒服,但这是个好的界面吗?AlanCooper是VisualBasic之父,他建议“通过坚持使他们(Microsoft和Apple)各自独立的开发者群体遵守既定的方针,他们偷偷摸摸地阻止了来自应用者群体的革新。”[Cooper,212]Cooper认为,“我并不鼓吹忽略界面风格方面的指导,从而导致界面出现混乱。我仅仅认为应该像参议员看待说客那样来看待对界面风格的指导,而绝不能像司机服从于交警那样。立法者知道说客想要削减某项经费,但说客并非来自于持有客观态度的第三方。”[Cooper,212]在所有的条件下都是昀好的界面可能并不存在,即使在一定的条件下,界面的设计仍然是高度主观的。如果你能开发出像图11.1所示的新RealPlayer那样的界面,而且符合你的目的,那就很好了。如果你不擅长创建独一无二或非常有趣的图形用户界面,而且并没有雇佣图形设计者的预算,那么可能会开发出与WinTel风格类似的应用程序。对于商业目的而言,也许较为熟悉的风格可以避免使用方面的障碍。图11.1RealPlayer8使用了一些漂亮的图形按钮,并进行了视觉人类工程学方面的尝试。还可以选用卡通标志和斑马条纹等外表只有一个问题不是主观的,它也是本章的主题,那就是界面应该是一致、连贯、完全的。不一致、不连贯、不完全,不考虑界面的风格对用户来说是不可容忍的。第11章示范了一些技术,可用于简化开发并确保一致性,包括如何使用定制组件、组件模板和窗体继承,以提供一致、连贯而完全的应用程序。第11章用组件开发一致的界面26511.1定制组件创建定制组件很有趣,而且定制组件也很有用。首先,显而易见的理由是可以重用已有的对象,并封装新的或增强的特性;其次,它可以提供一致的效用。无须绘制组件时保证相同的尺寸、风格、字体、颜色或措辞,可以对组件进行定制以确保这些目标。11.1.1定制组件的三个C定制组件的三个C是一致性、连贯性和完备性。一致性意味着组件在你的应用程序和其他地方的行为是一致的。一致性(Consistency)组件每次都表现出相同的行为和初始状态,才能提供一致性。对组件的行为或状态进行一次编程,则所有的组件实例都具有一致的外观和行为。一致性并不追求数量,注意到这一点是很重要的。定制组件无须进行大量的修改,即可提供一致性。即使组件只是重载了缺省的大小或形状,创建一个定制组件也可确保一致性。有两个直接的方法可以做到这一点。您可以子类化所有的需要微小修改的组件然后再安装;或者快速地创建组件模板,这更容易一些(参考11.2节“创建组件模板”)。连贯性(Coherency)一致性是连贯的一个方面。如果对象不具有一致性,也会缺少连贯性。连贯性是对控制流和操作的逻辑性的度量,它要求语义上相似的操作具有一致的行为。定制控件和组件模板可用于提供更为连贯的行为流程。没有一致性和连贯性,应用程序不可能是完全的。完备性(Completeness)不一致、不完全的应用程序看起来是不合逻辑且不正确的,这样必定是不完备的。如果应用程序不被用户群体所接受,也不能说是完备的。完备性度量了应用程序是否执行了所要求的任务、结果是否正确、应用程序是否具有合理的容错级别。如果程序给出正确却不合时宜的回答,也是不完备的。而迅速的提供错误的结果,仍然是错误的。如果程序的行为毫无规律、不一致、或不合逻辑,那么该程序是失败的。即使程序有相应的用户群体,仍然可能失败,因为用户群体可以拒绝使用该程序,或恶意共谋使用该程序提供错误的或不合适宜的结果。为什么组件帮助你走向胜利组件是对象。每个对象都属于某个类。这意味着有一组代码需要测试、调试和扩展。如果一个类已经是完美的,那么每个实例都不会出错。这样如果类满足了3C标准,那么类的每个实例都会满足该标准。注意:“大而复杂的软件系统需要设计师,以便开发者能够朝着共同的目标前进。”[Jacobsen,Booch,andRumbaugh62]。设计师是这样的人,他形成解决方案的概念并向程序员说清设计意图。即便开始时的进度比通常慢,也要把事情做正确,这将会节省大量金钱和思考的时间,防止在最后才发现出轨。没有经验、缺乏技术的管理者可能认为编写组件接近于消磨时间,但这是面向对象的程序设计。以非面向对象的方法去使用面向对象工具是一个错误。使用Delphi编写结构化程序可以很快地到达beta版,这在短期内常常会使管理者高兴,但可能使得处于beta版的时间较长。您的程序可能永远都脱离不了beta版。迅速得到错误的答案,仍然是错误的。无论是否能确认管理层会花大笔金钱来确保成功,都可以采取一些防御措施。从许多功能正确的组件来创建程序,可以尽可能少写代码而又能提高程序的正确性。第11章用组件开发一致的界面26611.1.2重分解重分解是采取小的增量式改变的过程。组件可以一步就写出来,创建全新而独一无二的东西,这样做代价昂贵、风险较大而且浪费时间;或者我们可以采取小的步骤,分层实现各种能力,这样就不那么昂贵,风险较低而且快速。设计师的关键作用之一——找到和减少冒险。如果没有设计师,必须由程序员来完成该工作。管理者喜欢快速而廉价。那么很清楚,许多情况下昀好的选择就是对组件进行小的修改,将增量式的改变分层添加到已有的组件中。为示范进行这种小的修改所需代码的合理数量,设计了下面的组件:unitUDBShortNavigator;//UDBShortNavigator.pas-Togglesbetweenshortlistofbuttons//andlonglist//Copyright(c)2000.AllRightsReserved.//bySoftwareConceptions,Inc.Okemos,MIUSA(800)471-5890//WrittenbyPaulKimmelinterfaceusesWindows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,ExtCtrls,DBCtrls;typeTNavigatorButtonSet=(nbsFull,nbsPartial);TDBShortNavigator=class(TDBNavigator)private{Privatedeclarations}FButtonSet:TNavigatorButtonSet;procedureSetButtonSet(constValue:TNavigatorButtonSet);protected{Protecteddeclarations}public{Publicdeclarations}published{Publisheddeclarations}propertyButtonSet:TNavigatorButtonSetreadFButtonSetwriteSetButtonSet;end;procedureRegister;implementationprocedureRegister;beginRegisterComponents('PKTools',[TDBShortNavigator]);end;{TDBShortNavigator}procedureTDBShortNavigator.SetButtonSet(constValue:TNavigatorButtonSet);constFULL_SET=[nbFirst,nbPrior,nbNext,nbLast,nbInsert,nbDelete,nbEdit,nbPost,nbCancel,nbRefresh];PARTIAL_SET=[nbFirst,nbPrior,nbNext,nbLast];SETS:array[TNavigatorButtonSet]ofTButtonSet=(FULL_SET,第11章用组件开发一致的界面267PARTIAL_SET);beginif(FButtonSet=Value)thenexit;FButtonSet:=Value;VisibleButtons:=SETS[FButtonSet];end;end.TDBShortNavigator继承了TDBNavigator,添加了一个ButtonSet特性。将该特性在nbsFull和nbsPartial之间切换,即可显示所有的导航按钮或仅仅显示基本的四个按钮(如图11.2所示)。图11.2TDBShortNavigator可以快速地在显示部分或全部导航按钮之间切换很显然该组件并没有多少代码。而这正是我们所需要的。小的改变快速、便宜而且可靠。然而,有人可能认为这是不重要或不相关的。由混沌理论可知,即使蛾的翅膀振动一下也可能影响到很远的地方。那么我们可以考虑蛾困在MarkⅡ型计算机的中继转换开关中的情况。据说是COBOL的发明者GraceHopper杜撰了bug这个词,现在整个工业界都在使用它,连世界历史上昀重大的媒体事件之一Y2K问题也是它的标志。并不是说上述的导航器子类在历史上也能有这样幸运的角色;轶事能被记录本来就是戏剧性的。小的事件能够发挥值得记载的作用,并具有相当的影响。千里之行,始于足下,高级的系统正是由小块的优质代码所组成的。11.1.3小的改变有什么好处除了本节开始所描述的好处之外,还包括快速、廉价、可靠等等,这些好处都是由小的、增量式的改变得到的。TDBShortNavigator这样的组件有利于代码的收敛。收敛是指所有该算法的代码都聚集在一起;如果没有昀好的代码,那么一个实例的代码是次好的,代码多于一个实例是较差的。发散是指出现算法的多个副本;这是昀坏的情况。当子类化TDBNavigator这样的组件来进行小的改变时,可以促进代码的收敛。改变可见按钮数量的所有代码都包含在同一个地方。因此只有一个代码段需要测试、调试和扩展。如果要为按钮定义三个状态,可以在同一地方快速而有效地修改代码。注意:您可能听说过比其他的程序员多产一个数量级的程序员。也就是他个人的产量是其他人的十倍。这怎么可能呢?很显然一个人不可能比程序员的平均打字速度快十倍。这是技巧与策略方面的问题。即使最好的程序员也不太可能在语法和程序编码方面比平均程度强十倍;他只是使用了一些具有累积效应的策略。其中必有一种策略倾向于编写收敛的代码。这种程序员可以比一般人快上十倍或更多,而且其代码也可能好于平均的水平。我们提到过,修改并不重要。重要的是修改表示了什么。像TDBShortNavigator这样的组件就表明了编写内聚代码的倾向。这种累积效应往往分布在程序员的职业生涯或工程的生命周期中。11.1.4采取好的策略编写收敛的代码,或编写的代码只具有算法的单一副本,这是一个策略。这可能是成为高产开发者的昀佳途径之一。有两个习惯可促进采用该策略,并逐渐使之成为一种第二天性。首先:考虑多次修改你的代码。我们知道谚语“天才是1%的灵感加99%的汗水”,这意味着当一个人思考解决方案时,只有出现了非常好的机会,灵感才能发挥作用。其次:当发现重复出现的代码时,立即把涉及到的算法编写为过程。随着实践的进行,这种迭代式的修改会变得更加自然,如果随时进行修改,也更容易发挥作用。反之,第11章用组件开发一致的界面268如果等到程序完成之后才进行修改工作,可能会遇到困难。管理者和其他程序员可能不想立即投入很多时间进行修改,而这时代码已经相互纠缠在一起以至于小的修改也可能引起代码的混乱。11.1.5组件化我们就继续讨论上一节的问题,如果你发现自己正在编写处理组件内部数据的代码,那么昀好子类化组件以封装新的行为。组件化的规则是:如果代码涉及到组件内部的数据,例如组件所拥有的对象列表,那么代码实际上描述了组件的行为。对象的行为就是方法。通过将外部的、隐式的算法提升为方法,可以使代码在类的层次上趋向于收敛。类