开源项目JHotDraw分析报告
(图片资源请参考附件)
一、JHotDraw项目的简要介绍
JHotDraw是一个基于LGPL协议的二维的GUI框架开源项目,是一个设计良好的(Well-Designed)框架,主要用于支持用Java开发的图形编辑器,已用于各种研究。JHotDraw实际上是HotDraw的Java版,HotDraw也是一个图形框架,最初是由Kent Beck和Ward Cunningham用Smalltalk开发为教学的目的而设计的。
目前它的最新版本是JHotDraw 7.0.9,从JHotDraw5.2 版本起其框架结构已经成熟稳定。本文以5.2版本为讨论对象,在这个版本里,原来的AWT组件被换成了相应的JFC Swing 组件,同时也支持一些新的JFC Swing 特性。所以,JHotDraw作为一个专业应用的GUI框架,它基于的是Swing框架提供的通用的GUI设施,在这些设施之上,JhotDraw 又提供的一些特性和功能包括支持多文档应用、拆分编辑器窗口、支持滚动条、弹出式菜单、支持撤消/重做、工具栏可以根据选择的不同的工具与图形而显示不同的工具、通过StorageFormat 接口可以自己实现所存贮的图形文件格式(比如xm1)、JHotDraw甚至支持简单的动画等。
关于JHotDraw的组织方式及结构,其所有的类及接口是按照它们的功能来组织的。可概括如下:CH. ifa. Draw. framework 包括核心组件的大多数接口定义;CH. Ifa. draw. Standard 包含这些接口的标准实现;CH. Ifa. Draw. figures 包含图形以及它们的相关类;CH. Ifa. Draw. contrib 包含~些附加的功能;CH. Ifa. Draw. samples 包含一些利用JHotDraw的应用的简单例子;CH. Ifa. Draw. application 包含应用(Application)实现的框架;CH. Ifa. Draw. applet 包含Applet实现的框架,等等。JHotDraw主框架如图1所示。
图 1 JHotDraw主框架
经过这么多年的发展,JHotDraw主框架的各主要组件已相对固定,DrawApplication 定义了绘图的界面,并作为各个组件交互的中介者;DrawingView 是一个显示绘图的区域,它也可用来接受客户的输入;Drawing 容纳各种图元(Figure),是图元的容器。Drawing 的改变被传递到DrawingView,并负责更新图形。每个图元都有句柄(Handle),句柄用来定义访问点以及如何与图元交互。在DrawingView 中,可以选择几个图元并对其操作。工具栏里有各种工具(Too1)用来生成图元或对图元进行操作。
实际上,框架从某种意义上讲是一个不完整的系统,通常会对此系统进行剪裁、扩充等用以创建完整的应用。框架实现了应用程序之间的共同点,从而减少了构建这些应用的代价。
在研究运用JHotDraw的开发过程中,一般会考虑下面几个步骤。
1. 为开发的应用程序创建自己的图元(figure)。针对特定的应用程序创建对应的图元,不过在JHotDraw中一些预定义的图元已经存在,像AbstactFigure、compositeFigure、AttributeFigure等,可以通过继承并重载一些方法(比如draw())来重新定义它们的行为和表现形式,从而使具有创建特定图元的功能。
2. 按照应用的需求开发图形工具。JHotDraw本身已提供了一些工具,像创建工具CreationTool、联接工具ConnectionTool、选择工具SelectionTool以及文本工具TextTool,等等。通过继承这些类并重定义其中的某些方法,比如像mouseUP()、mouseDown(),就可以定义程序的交互过程,执行应用程序所需要的任务,比如操作自定义的对象。
3. 生成GUI并整合进应用程序中。JHotDraw已经有了一个基本的应用程序框架;如,DrawApplication、MDI_DrawApplication、DrawApplet。开发者可以重定义createMenus()、createFileMenu()和其他的办法来开发出自己的菜单,通过重定义createTools()来插入新的工具。
任何利用JHotDraw的应用都是用一个窗口用来绘图,这个窗口就是GUI编辑器窗口,它是JFrame的子类,包含一个或多个内部框架(Internal Frames),每个都与一个绘画视图(Drawing View)相关。DrawingView 是Jpanel 的子类,是一个显示绘图的区域。
二、JHotDraw项目的设计模式与框架应用
1.JHotDraw里设计模式的应用
设计模式是一种可复用性的方案,用来解决软件开发中重复出现的问题。设计模式可以使人们更加简单方便地复用成功的设计和结构体系。JHotDraw的设计中采用了许多著名的设计模式,是最早的为复用而设计并被称为“框架”的工程之一,它也是很早就被称为与设计模式相关的工程之一。
1.1 MVC模式1
所谓“MVC模式”是三个单词的首字母缩写,它们是Model(数据模型)、View(视图)和Controller(控制器)。MVC模式最早是由Smalltallk-80语言引入的。其包括互相通信的三种对象:
1)Model:用于存放构造界面所需的数据,这些数据通常与应用领域有关,一般从业务层获得;
2)View:是在屏幕上可见的组件,它们通过一定的形式表示数据模型中的数据,数据模型中数据的改变可及时地反应到组件的屏幕外观显示;
3)Controller:负责处理用户操作组件所激发的事件,根据用户的输入更新数据模型对象中的数据,或者(以及)通知视图对象更新屏幕上的外观表示。
MVC三种对象之间的通信可使用图2表示1。
图2 [摘自文献1]
如图所示,MVC模式分离了数据状态及其在界面上的表示,使得同一个数据模型可以使用不同的视图表示,对数据模型的改变会通知所有依赖于该模型的视图。而数据模型是与视图无关的,因此对视图的改变不会对数据模型产生影响,数据模型和视图的关联甚至可以在运行时动态绑定。视图使用控制器制定它的响应机制,同一数据模型上的试图可以采用不同的控制器,从而有不同的用户事件响应和处理方式。
JHotDraw是基于MVC模型的,通常,应用JHotDraw必须提供自己的对象模型(JHotDraw本身的对象模型是由JModellerClass定义的),并要添加一些图元以表示模型潜在的对象。正如MVC模式所定义的,JHotDraw是View,也是Controller。作为Controller,它为管理用户的交互及操作图形对象提供工具组件。作为View,对图形对象的改变会反应到对象模型中。也可以开发包含应用逻辑以及能直接处理对象模型的工具
1.2 Composite设计模式
Composite设计模式结构图:
图3
需要用到复合模式一般是因为客户代码过多的依赖于对象容器复杂的内部实现,对象容器内部实现结果的变化将引起客户代码的频繁变化,而带来了代码的维护型、扩展性等弊端。Composite模式是为了将对象组合成树形结构以表示“部分-整体”的层次结构,将“一对多”的关系转化为“一对一”的关系,使得用户对单个对象和组合对象的使用具有一致性。所以,解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口--而非对象容器的复杂内部实现结构--发生依赖关系,从而是程序更具有拓展性。
Component 为组合中的对象声明接口,在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子组件。可以在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
Leaf 在组合中表示节点对象,或单个个体。
Composite 定义子部件的那些部件的行为,存储子部件。在Component接口中实现与子部件有关的操作。
Client 通过Component接口操纵组合部件的对象。
用户使用Component类接口与组合结构中的对象进行交互.如果接受者是单个对象的叶节点,则直接处理请求。如果接受者是Composite,它通常将请求发送给它的子部件,在转发请求之前或之后可能执行一些辅助操作。不过这对用户都是透明的,用户不知道处理的是叶子组件还是复合组件。
Composite设计模式在JHotDraw中的结构图:
图4
如上图,ClassFigure类的图形,用一个TextFigure存类名,而用两个别的图形来存贮每个方法及属性,ClassFigure必须包含这些图形。JHotDraw 中提供了CompositeFigure,它用来将几个图形整合成一个图形。简单地说,Composite的思想就是将一个包含几个同种基本类型的组件象一个组件一样处理。所有行为的调用都是通过容器传到组件,具体的讲,就是说客户端组件不知道它是在调用一个合成组件还是一个单个组件。这种封装技术使建立合成对象成为可能,象CompositeFigure,具有一个合成组件的层次结构,但它的行为却象一个组件一样。比如,StandardDrawing是CompositeFigLIFe的子类,也就是说,StandardDrawing包含别的图形,但它自己也一个图形。可以通过CompositeFigure中的draw()方法看看Composite怎样分派行为到它的组件中:
public void draw(Graphics g) {
FigureEnumeration k = figures();
while (k.hasMoreElements())
k.nextFigure().draw(g);
}
1.3 Strategy设计模式
Strategy设计模式结构图:
图5
Strategy策略模式是一种对象行为模式。主要是应对在软件构建过程中,某些对象使用的算法可能多种多样,经常发生变化。如果在对象内部实现这些算法,将会使对象变得异常复杂,甚至会造成性能上的负担。故把算法和使用算法的客户端分开(把行为和环境分割开),从而方便的选择其中一个算法。正如在GoF《设计模式》中说道:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可独立于它们的客户变化。
从图中可知Strategy模式实际上就是将算法一一封装起来,ConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyC,但是它们都继承于一个接口,这样在Context调用时还可以以多态的方式来实现对于不用算法的调用。
Strategy模式使用的场合很多,像系统有许多类而他们的区别仅仅在于它们的行为,或者动态选择几种算法中的一种,或者一个对象有很多行为等等时候。
图6 JHotDraw中的策略设计模式结构
关于JhotDraw 中的策略设计模式,在上图6 中ClassFigure 只对画图负责,它并不知道ClassFure 怎样布局。事实上,图形表现是独立于布局算法的。因此,布局算法与ClassFigure 是分离的,并封装在一个外部组件里,这个组件对主组件有广泛的访问权和控制权。如果我们要放置一个ClassFigure 时,它便委派任务给Layouter,它遍历所有的子元素并安排这些元素的布局。这种算法与内容的分离可以使你在运行时动态地改变行为并增加算法的可重用性。
1.4 Factory Method 设计模式
工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类。工厂模式有以下几种形态:简单工厂(Simple Factory)模式、工厂方法(Factory Method)模式、抽象工厂(Abstract Factory)模式。针对JHotDraw,这里说的是工厂方法(Factory Method)模式。
Factory Method设模式结构图7:
图7 Factory Method设模式结构
可以说,Factory Method是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类。当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模式了。简单地说,Factory Method可以根据不同的条件产生不同的实例,当然这些不同的实例通常是属于相同的类型,具有共同的父类。Factory Method把创建这些实例的具体过程封装起来了,简化了客户端的应用,也改善了程序的扩展性,使得将来可以做最小的改动就可以加入新的待创建的类。
Product: 需要创建的产品的抽象类。
ConcreteProduct: Product的子类,一系列具体的产品。
Creator: 抽象创建器接口,声明返回Product类型对象的Factory Method。
ConcreteCreator: 具体的创建器,重写Creator中的Factory Method,返回ConcreteProduct类型的实例。
可以清楚的看出这样的平行对应关系:Product对应Creator;ConreteProduct对应ConreteCreator。抽象产品对应抽象创建器,具体产品对应具体创建器。即客户(client)只需引用抽象的Product和Creater,对具体的ConcreteProduct和ConcreteCreator可以毫不关心,这样一来,首先客户端可以统一从抽象创建器获取产生的实例,Creator的作用将client和产品创建过程分离开来,客户不用操心返回的是那一个具体的产品,也不用关心这些产品是如何创建的。同时,ConcreteProduct也被隐藏在Product后面,ConreteProduct继承了Product的所有属性,并实现了Product中定义的抽象方法,按照Java中的对象造型(cast)原则,通过ConcreteCreator产生的ConcreteProduct可以自动的上溯造型成Product。这样一来,实质内容不同的ConcreteProduct就可以在形式上统一为Product,通过Creator提供给client来访问。其次,当我们添加一个新的ConcreteCreator时,由于Creator所提供的接口不变,客户端程序不会有丝毫的改动,不会带来动一发而牵全身的灾难,这就是良好封装性的体现。但如果直接用ConcreteProduct和ConcreteCreator两个类是无论如何也做不到这点的。
优良的面向对象设计鼓励使用封装(encapsulation)和委托(delegation),而Factory Method模式就是使用了封装和委托的典型例子,这里封装是通过抽象创建器Creator来体现的,而委托则是通过抽象创建器把创建对象的责任完全交给具体创建器ConcreteCreator来体现的。
Factory Method在JHotDraw中被广泛的使用,特别是在创建用户界面组件(比如,菜单以及Tools)时,在CH.ifa.draw.application.DrawApplication中可以发现许多Factory Method,比如createTools()、createMenus()、CreateFileMenu()等等。还可以在相应的方法中做一些变化,比如,为建立自己的类以及在类之间建立关联关系,可以在主应用类中重载createTools0方法。
public class JModellerApplication extends MDI_DrawApplication {
…
protected void createTools(JToolBar palette) {
super.createTools(palette);
Tool tool = new ConnectedTextTool(view(), new TextFigure());
palette.add(createToolButton(IMAGES+"ATEXT", "Label", tool));
tool = new CreationTool(view(), new ClassFigure());
palette.add(createToolButton(DIAGRAM_IMAGES+"CLASS", "New Class", tool));
tool = new ConnectionTool(view(), new AssociationLineConnection());
palette.add(createToolButton(IMAGES+"LINE", "Association Tool", tool));
tool = new ConnectionTool(view(), new DependencyLineConnection());