9JAVA设计模式第九课界面组装器模式.docx

上传人:b****1 文档编号:2058279 上传时间:2023-05-02 格式:DOCX 页数:19 大小:79.62KB
下载 相关 举报
9JAVA设计模式第九课界面组装器模式.docx_第1页
第1页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第2页
第2页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第3页
第3页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第4页
第4页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第5页
第5页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第6页
第6页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第7页
第7页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第8页
第8页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第9页
第9页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第10页
第10页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第11页
第11页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第12页
第12页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第13页
第13页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第14页
第14页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第15页
第15页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第16页
第16页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第17页
第17页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第18页
第18页 / 共19页
9JAVA设计模式第九课界面组装器模式.docx_第19页
第19页 / 共19页
亲,该文档总共19页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

9JAVA设计模式第九课界面组装器模式.docx

《9JAVA设计模式第九课界面组装器模式.docx》由会员分享,可在线阅读,更多相关《9JAVA设计模式第九课界面组装器模式.docx(19页珍藏版)》请在冰点文库上搜索。

9JAVA设计模式第九课界面组装器模式.docx

9JAVA设计模式第九课界面组装器模式

本文提出了一种界面设计中的架构模式-界面组装器模式,它致力于分解界面,将界面和组装行为解耦,将界面逻辑处理与领域逻辑处理解耦,这样我们在开发GUI胖客户端界面应用时可以从众多的界面控制管理中解脱出来,而专注于我们的后台业务逻辑的开发。

通过该模式,我们可以动态地组装我们的界面,我们甚至还可以在我们的界面中轻松地插入transaction事务或session会话管理。

本文将通过分析设计一个架构的过程来讲解该模式,从一个简单的设计模型开始,一步步走向一个完整的架构。

借此也向大家展示一个架构设计的思维历程。

另外,本文给出了EclipseSWT(StandardWidgetToolkit)的示例。

问题引出

界面设计常常是模式产生的根源,无论是架构模式,还是设计模式,比如MVC模式,Observer,Facade等,也是整个软件行业向前发展的动力。

遗憾的是,即使在软件技术发达的今天,界面设计仍是软件设计中的难以突破的瓶颈之一。

我们用过Javaswing或EclipseSWT作过项目的都知道,要将界面进行分解是很困难的,它不像我们的业务逻辑,可以方便地按职责分解到不同的类中去实现,因为各个业务逻辑之间耦合度很低。

但界面逻辑不一样,你不可能将一个文本框的读取操作委任到另一个类中去,而且各个界面元素之间相互依赖,无法去除耦合,一般的做法只能是在界面元素的事件触发(比如按钮点击事件)时,将输入数据封装成一个数据对象传给后台的逻辑处理类来处理。

Eclipse的Wizard框架在界面分解上提供了一种很好的实践,它可以将按钮区和其他界面区分离出来,用类似MVC的方式实现了Wizard框架。

但这个实现并非没有瑕疵,一个缺点是wizard是一个plug-in,这样的话就减少了可重用性,不能移植到eclipse以外的环境。

另一个缺点就是它引入了很大的复杂性,而且在一些对界面元素的控制上丧失了一些精细控制的能力,这可能是它过度地强调了自动化和用户扩展的方便性的缘故。

比如,用户不能将自己的逻辑插入按钮区的按钮事件控制中,而只能在自定义区的界面元素Listener中设定按钮区状态,如果用户自定义的界面元素很多,就需要很多个Listener来组合判断一个按钮状态(如是否进行“下一步”),这样的话就很影响性能,而且无端地多了一堆复杂的逻辑判断,也就是说本来只需在按钮Listener事件中处理的逻辑现在要分散在各个界面元素的Listener中去处理。

这也正是设计上一个值得反复强调的普遍问题:

当你要保持架构或设计的完美性时必然会以丧失其他特性为代价。

世上永远没有完美的东西,我们只关注适合我们的。

我下面要提出的这个架构模式的灵感来自于我的一个真实项目,一个用RSA(RationalSoftwareArchitect)/Eclipse建模的项目,在RSA环境中,读写模型都必须在一个特有的context下才能操作,这就意味着我在界面的启动之前必须封装好输入数据,关闭之后返回输出数据,而不是直接处理数据,必须对输入/输出数据对象进行封装。

正如前面提到的,这种情况界面设计中很普遍。

所以,在模式命名时我用了组装器-assembler这个词,有一层意思是输入/输出数据对象的组装,另一层意思就是界面部件(界面元素的集合)的组装,这里的组装还有更深层次的涵义就是指界面部件的可装配性,可以在运行时动态组装。

而且这个模式可以用任何语言(Java,C++等)来实现。

在这里我会从一个简单的设计模型开始,一步步走向一个完整的架构。

借此也向大家展示一个架构设计的思维历程。

本文中给出了EclipseSWT(StandardWidgetToolkit)的示例。

界面分解

在EclipseSWT中,有几个重要的界面部件,一个是Shell-界面的最外层容器,类似JavaSwing中的Frame,另一个就是Composite-界面元素的集合的容器,类似JavaSwing中的Panel。

我们的界面分解将从Composite开始,(Shell本身是不需要分解的)。

我们可以在Shell中装配上一个空的Composite,然后我们的具体界面元素都定义在这个Composite里。

这样就把Composite逻辑从Shell中分离出来了,因此我们现在有了2个类(目前我们用概念类来表示):

图1.把Composite逻辑从Shell中分离出来

Editor:

该类处理Shell的逻辑,如显示-show,关闭-close,它负责创建和销毁EditorComposite。

EditorComposite:

该类处理Composite的界面逻辑,如创建界面元素。

有两点值得注意,第一,Editor负责EditorComposite的创建和销毁,也就是生命周期的管理。

那么我们可以想到,如果我们的界面需要transaction-事务或session-会话的管理,那么我们完全可以让Editor来负责这项职责,而不是分散在各个EditorComposite中。

怎么扩展界面的事务功能可能会很复杂,这已经超出本文的讨论范围,我只是从架构的层面来分析可能有的可扩展性。

第二,一个Editor可以包括多个EditorComposite,比如我们的属性页,此时我们在Shell中定义的空的Composite将会是一个TabFolder.还有一种情况,就是我们可以根据某种逻辑来判断我们需要装配哪个EditorComposite。

这就要求我们有一个装配的行为。

界面部件装配

当我们的装配逻辑很简单时,我们可以定义一个assemble()方法来负责装配行为。

但是当我们的界面需要组装一系列EditorComposite时,就会牵涉到选择逻辑,选择逻辑不一定很复杂,但我们还是应该把这种行为从Editor中分离出来,这样Editor可以集中精力负责与用户交互方面的职责,而装配行为被分配到一个新的类EditorAssembler中,这样做还有一个好处,就是我们一旦有新的EditorComposite需要添加时,我们只需要改变EditorAssembler的代码,而不用修改Editor的代码,这就把变化隔离出来,对Editor的修改关闭,对装配行为的扩展开放。

这正是面向对象设计领域反复强调的基本原则-开放-封闭原则(Open-ClosePrinciple)。

经过重构后的架构如下图:

图2.重构后的架构

EditorAssembler:

该类处理EditorComposite的创建,还包括多个EditorComposite的选择逻辑。

这里的选择逻辑我们可以用if/else或switch/case来硬编码,如果逻辑不是很复杂而且今后的修改不会太频繁的话,用这种方法就足够了,当然可以考虑将多个EditorComposite的装载信息专门用一个资源/信息类来存储,这在EditorComposite比较多的情况下很有效,这样每次添加EditorComposite就只需要改变这个资源类,这是一个很有用的建模原则(为了简化我们的核心模型,我在这里不将这个资源类表示出来)。

如果进一步考虑到我们的组装逻辑会比较复杂,或会比较容易改变,甚至在运行时动态改变,我们就可以将众多的EditorComposite和复杂的逻辑存储在一个元数据文件中,如XML或配置文件。

这样,有新的EditorComposite需要支持,或修改装配逻辑时,不用修改EditorAssembler类,只要修改元数据文件即可。

这样就可以很动态的配置我们的界面。

这里会有一个架构权衡的问题,元数据由它的优点,也有它的缺点,其一,必须编写解析它的类,复杂性增加了,其二,不需要编译是它的优点也是它的缺点,对XML或配置文件我们可以随意修改,只有在运行时发现异常才知道改错了,而且也可能被人蓄意破坏掉。

所以我们只在真的需要很频繁地修改EditorComposite的配置或经常需要增加EditorComposite时才采用元数据方案。

在这里我倾向于采用资源类方案。

IO数据装配

模型设计进行到这里,我们似乎缺少了对数据流的建模,在一个标准的界面程序中,我们首先会有一组输出数据,比如按”OK”按钮之后,我们需要将界面元素上的输入信息输出到后台逻辑类来处理或直接调用好几个逻辑类分别处理不同的界面元素输入信息了。

我们一般习惯上可能直接将这个数据传递到逻辑类来处理。

这样做三个缺点:

其一,如果我们的数据读写处理要求必须在特定的context中才能进行,这样的话我们不能在界面中直接调用后台逻辑处理类了。

其实这种限制并不罕见,在一些涉及底层(比如协议层)的开发时,经常会碰到只能读不能写的情况。

其二,UI的可替代性差,假如我们今后需要一种方案可以在运行时可以替换不同的UI但输出的数据是一样的,也就是说后台逻辑处理完全一致,那么这种情况我们就需要每一个UI自己去调用后台逻辑类,重复编码,而且可能由于程序员的失误每一个UI用了一个逻辑类,从而导致一个完全相同行为的类有了好几个不一致实现版本,这样不仅严重违反了面向对象设计,而且还可能产生难以预料的bug,难以维护。

其三,UI的可重用性差,对于上面多个UI对应一种逻辑处理的例子,由于UI依赖了后台逻辑类,如果今后要修改逻辑类结构的话,我们就需要修改每一个UI。

如果我们还有一种需求是要支持一个UI在不同的环境下需要不同的后台逻辑类时,我们可能要专门在一个UI中设置一个属性来标识后台将要使用的逻辑类。

这会很复杂。

解决上面几个缺点只有一种方法,就是将后台逻辑类与UI解耦。

如果我们把要处理的输出数据打包成一个输出数据对象从界面统一输出,再由UI的调用者决定调用哪一个后台逻辑类来处理数据,而不是UI自己决定调用行为。

还有一个输入数据对象就很好理解了,我们调用UI时,可能某些界面元素需要的从环境中动态装载数据,比如一个下列列表,还有一些我们上一次配置好的数据这次需要更新,也需要将已有数据导入。

所以我们需要一个输入数据对象。

这就得到下面的模型:

图3.输入数据对象

InputDataObject:

该类封装了输入数据。

由EditorComposite负责解析这些数据。

OutputDataObject:

该类封装了输出数据。

由EditorComposite负责产生这些数据。

Editor负责传输这两个数据对象。

重构架构

从上面的模型我们可以看出Editor类其实相当于一个Facade,所有的界面与用户的交互都由它负责集中调度管理,Editor会将装配行为分配给EditorAssembler类来处理,它还负责临时存储输入输出数据,当然如果我们有类似transaction或session之类的处理会由Editor委派到别的相关类去处理。

应用Facade设计模式,我们可以给Editor改个名字叫EditorFacade,这样更能体现设计者的意图,千万不要忽视类的命名,设计是一门严肃的科学,每一个细节我们都不能苟且,对架构的设计更要严谨。

命名可以起到沟通的作用,还能起到提醒的功能,EditorFacade提醒我们以后要给它添加新的行为是记住它是一个Facade,不能将不相干的职责分配进来。

另外,我发现添加了InputDataObject类后,EditorComposite就有两个职责:

装载界面元素初始化数据(一些需要从环境中动态获得的输入数据,从InputDataObject对象中获得)和显示上一次编辑的数据(也从InputDataObject对象中获得),我们定义两个方法来分别处理:

loadDataInfo()-装载初始化数据;showPreInfo()-显示上一次编辑的数据。

当然,一般来说这两个方法是私有的-private,因为这是EditorComposite自身的内部逻辑,但我们在这个架构中让它成为公有的-public,是因为我们可以在EditorAssembler类中集中控制它的调用,而且每一个EditorComposite都会有装载初始化数据和显示已有数据的行为,那么为什么不抽象出来呢,以便让EditorComposite的开发提供者更清楚自己的职责,虽然这么做有点破坏EditorComposite的封装性和其中方法的私密性,但从架构的角度来讲这种破坏是合适的,值得的。

再看看前面的EditorAssembler类,它其实有两个职责,一个是创建EditorComposite,还有一个就是从几个EditorComposite选择出一个的判断逻辑。

如果我们把这两个不相干的职责解耦,应用Factory设计模式,就可以将创建EditorComposite的工作委任给一个EditorCompositeFactory的新类。

经过以上几项重构后得到以下概念类模型:

图4.概念类模型

实现架构

经过上面的分析建模,我们可以开始实现架构了,从上面的概念模型我们可以很容易地抽象出相应的接口来。

首先,我们看看EditorFacade类,基于我们上面的讨论,不同的界面可能有不同的需求,比如有的要支持transaction-事务,那么EditorFacade的实现就会不同,所以我们有必要提取出一个接口来表示,下面列出了这个接口IEditorFacade:

清单1:

IEditorFacade.java

publicinterfaceIEditorFacade{

publicvoidshow();

publicIInputDataObjectgetInputData();

publicvoidsetInputData(IInputDataObjectinputData);

publicIOutputDataObjectgetOutputData();

publicvoidsetOutputData(IOutputDataObjectoutputData);

publicbooleanisFinishedOK();

publicCompositegetRootComposite();

publicvoidsetAssembler(IEditorAssemblerassembler);

publicvoidclose(booleanstatus);

}

那么EditorFacade类的部分代码如下:

清单2:

EditorFacade.java

publicclassEditorFacadeimplementsIEditorFacade{

privateShellshell;

//validateifeditorisclosedwithOKorCancel

privatebooleanfinishedOK;

//inputdata

privateIInputDataObjectinputData;

//outputdata

privateIOutputDataObjectoutputData;

privateCompositecomposite;

privateIEditorAssemblerassembler;

privatevoidcreateSShell(){

shell=newShell();

shell.setLayout(newGridLayout());

createComponent();

}

privatevoidcreateComponent(){

composite=newComposite(shell,SWT.NONE);

………

assembler.create(this);

}

publicvoidshow(){

this.shell.open();

assembler.showPreInfo();

}

publicEditorFacade(IEditorAssemblerassembler,IInputDataObjectinputData){

this.assembler=assembler;

this.inputData=inputData;

this.createSShell();

}

publicCompositegetRootComposite(){

returncomposite;

}

publicvoidclose(booleanstatus){

finishedOK=status;

this.shell.close();

}

}

下一步,我们将两个IO数据类定义出来,很显然,不同的界面会有不同的输入输出数据,在这里我们只能定义出两个抽象的接口IInputDataObject和IOutputDataObject,它们继承了序列化java.io.Serializable接口,里面并无其它内容。

这里注意一点,空的接口并非无意义,它可以起到标识的作用,另外,它隐藏了具体实现,在传递数据时传递者不用知道具体数据内容,这样传递者类具有更好的重用性,而且具体数据类也不用暴露给不该知道它的类-传递者类,这正是另一个面向对象的基本原则-迪米特法则(LoD):

不要和陌生人说话。

下面给出IInputDataObject的清单:

清单3:

IInputDataObject.java

publicinterfaceIInputDataObjectextendsSerializable{

}

接下来,我们看看EditorAssembler类的实现,根据前面的讨论,它封装了界面的装配逻辑,一定会被修改的,那么我们就需要一个接口IEditorAssembler来规范它的行为,在这里我还给出了一个抽象类AbstractEditorAssembler,实现了装载单个EditorComposite的方法,另外我还给出了一个具体的EditorAssembler类,这是一个每次只装载一个EditorComposite的例子,代码清单如下:

清单4:

IEditorAssembler.java

publicinterfaceIEditorAssembler{

/**

*createeditorbodyandinit

*@parameditor

*/

publicvoidcreate(IEditorFacadeeditor);

/**

*createeditorcomposite

*@parameditor

*@paramcompositeClassID

*:

compositeclassname,e.g.test.view.TestComposite

*@return

*/

publicIEditorCompositecreateComposite(IEditorFacadeeditor,

StringcompositeClassID);

/**

*showexistinfoinUIforupdate.

*/

publicvoidshowPreInfo();

}

清单5:

AbstractEditorAssembler.java

publicabstractclassAbstractEditorAssemblerimplementsIEditorAssembler{

publicIEditorCompositecreateComposite(IEditorFacadeeditor,

StringcompositeClassID){

IEditorCompositebody;

body=EditorCompositeFactory.createComposite(compositeClassID,editor);

body.create(editor.getRootComposite());

body.setEditor(editor);

returnbody;

}

………………………………….

}

清单6:

StandaloneEditorAssembler.java

publicclassStandaloneEditorAssemblerextendsAbstractEditorAssembler{

privateStringcompositeClassID;

privateIEditorCompositebodyComposite;

/**

*

*@paramcompositeClassID

*:

compositeclassqulifiedname,e.g.com.ibm..XXComposite;

*/

publicStandaloneEditorAssembler(StringcompositeClassID){

positeClassID=compositeClassID;

}

publicvoidcreate(IEditorFacadeeditor){

bodyComposite=createComposite(editor,compositeClassID);

if(bodyComposite!

=null)

bodyComposite.loadDataInfo();

}

publicvoidshowPreInfo(){

bodyComposite.showPreInfo();

}

}

接下来,是EditorCompositeFactory的实现,这个类的实现比较简单,只是根据类名产生类:

清单7:

EditorCompositeFactory.java

publicclassEditorCompositeFactory{

/**

*createIEditorComposite

*@paramclsName

*@parameditor

*@return

*/

publicstaticIEditorCompositecreateComposite(StringclsName,

IEditorFacadeeditor){

IEditorCompositecomposite=null;

try{

Classcls=Class.forName(clsName);

if(cls!

=null)

composite=(IEditorComposite)cls.newInstance();

}catch(Exceptione){

e.printStackTrace();

}

if(composite!

=null){

composite.setEditor(editor);

}

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

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2