当前位置导航:炫浪网>>网络学院>>编程开发>>JAVA教程>>Java进阶

架构模式-界面组装器模式

  本文提出了一种界面设计中的架构模式-界面组装器模式,它致力于分解界面,将界面和组装行为解耦,将界面逻辑处理与领域逻辑处理解耦,这样我们在开发GUI胖客户端界面应用时可以从众多的界面控制管理中解脱出来,而专注于我们的后台业务逻辑的开发。通过该模式,我们可以动态地组装我们的界面,我们甚至还可以在我们的界面中轻松地插入 transaction 事务或 session 会话管理。

  本文将通过分析设计一个架构的过程来讲解该模式,从一个简单的设计模型开始,一步步走向一个完整的架构。借此也向大家展示一个架构设计的思维历程。另外,本文给出了 Eclipse SWT(Standard Widget Toolkit) 的示例。

  问题引出

  界面设计常常是模式产生的根源,无论是架构模式,还是设计模式,比如 MVC 模式,Observer,Facade 等,也是整个软件行业向前发展的动力。遗憾的是,即使在软件技术发达的今天,界面设计仍是软件设计中的难以突破的瓶颈之一。我们用过 Java swing 或 Eclipse SWT 作过项目的都知道,要将界面进行分解是很困难的,它不像我们的业务逻辑,可以方便地按职责分解到不同的类中去实现,因为各个业务逻辑之间耦合度很低。但界面逻辑不一样,你不可能将一个文本框的读取操作委任到另一个类中去,而且各个界面元素之间相互依赖,无法去除耦合,一般的做法只能是在界面元素的事件触发(比如按钮点击事件)时,将输入数据封装成一个数据对象传给后台的逻辑处理类来处理。

  Eclipse 的 Wizard 框架在界面分解上提供了一种很好的实践,它可以将按钮区和其他界面区分离出来,用类似 MVC 的方式实现了 Wizard 框架。但这个实现并非没有瑕疵,一个缺点是 wizard 是一个 plug-in,这样的话就减少了可重用性,不能移植到 eclipse 以外的环境。另一个缺点就是它引入了很大的复杂性,而且在一些对界面元素的控制上丧失了一些精细控制的能力,这可能是它过度地强调了自动化和用户扩展的方便性的缘故。比如,用户不能将自己的逻辑插入按钮区的按钮事件控制中,而只能在自定义区的界面元素 Listener 中设定按钮区状态,如果用户自定义的界面元素很多,就需要很多个 Listener 来组合判断一个按钮状态(如是否进行“下一步”),这样的话就很影响性能,而且无端地多了一堆复杂的逻辑判断,也就是说本来只需在按钮 Listener 事件中处理的逻辑现在要分散在各个界面元素的 Listener 中去处理。这也正是设计上一个值得反复强调的普遍问题:当你要保持架构或设计的完美性时必然会以丧失其他特性为代价。世上永远没有完美的东西,我们只关注适合我们的。

  我下面要提出的这个架构模式的灵感来自于我的一个真实项目,一个用 RSA(Rational Software Architect)/Eclipse 建模的项目,在 RSA 环境中,读写模型都必须在一个特有的 context 下才能操作,这就意味着我在界面的启动之前必须封装好输入数据,关闭之后返回输出数据,而不是直接处理数据,必须对输入/输出数据对象进行封装。正如前面提到的,这种情况界面设计中很普遍。所以,在模式命名时我用了组装器-assembler 这个词,有一层意思是输入/输出数据对象的组装,另一层意思就是界面部件(界面元素的集合)的组装,这里的组装还有更深层次的涵义就是指界面部件的可装配性,可以在运行时动态组装。而且这个模式可以用任何语言(Java,C++ 等)来实现。在这里我会从一个简单的设计模型开始,一步步走向一个完整的架构。借此也向大家展示一个架构设计的思维历程。本文中给出了 Eclipse SWT(Standard Widget Toolkit) 的示例。

  界面分解

  在 Eclipse SWT 中,有几个重要的界面部件,一个是Shell-界面的最外层容器,类似 Java Swing 中的 Frame,另一个就是 Composite-界面元素的集合的容器,类似 Java Swing 中的 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-Close Principle)。经过重构后的架构如下图:

  图2. 重构后的架构

  

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

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

  如果进一步考虑到我们的组装逻辑会比较复杂,或会比较容易改变,甚至在运行时动态改变,我们就可以将众多的 EditorComposite 和复杂的逻辑存储在一个元数据文件中,如 XML 或配置文件。这样,有新的 EditorComposite 需要支持,或修改装配逻辑时,不用修改 EditorAssembler 类,只要修改元数据文件即可。这样就可以很动态的配置我们的界面。这里会有一个架构权衡的问题,元数据由它的优点,也有它的缺点,其一,必须编写解析它的类,复杂性增加了,其二,不需要编译是它的优点也是它的缺点,对 XML 或配置文件我们可以随意修改,只有在运行时发现异常才知道改错了,而且也可能被人蓄意破坏掉。所以我们只在真的需要很频繁地修改 EditorComposite 的配置或经常需要增加 EditorComposite 时才采用元数据方案。在这里我倾向于采用资源类方案。

共6页 首页 上一页 1 2 3 4 5 6 下一页 尾页 跳转到
相关内容
赞助商链接