JHotDraw应用程序框架
注:这是本人发表在javaeye的文章, 这里对这一系列文章做一个整理.
JHotDraw简介
JHotDraw是面向文档的应用程序框架, 它能通过桌面或者web的方式来发布. 早期的JHotDraw则是以二维图形编辑器的Java图形框架而闻名. JHotDraw起源于Erich Gamma的一个教学实例。更早期的JHotDraw可上溯到1992年由Johnson采用smalltalk开发的hotdraw, 而hotdraw则是参考1989年weinand用C++实现的ET++框架.
JHotDraw是基于LGPL协议的发布的, 即其代码是完全开发的,并可以作为第三方类库用于商业用途(修改或衍生代码则必须遵循LGPL). JHotDraw 采用了大量的设计模式来处理Java矢量图形开发中的各种问题. 由于JHotDraw具有良好的框架,很好的复用性和扩展性,很快成为一个Java二维矢量图开发的应用框架。而历经了几个版本的发展,JHotDraw已经成为一个成熟的面向文档应用程序框架。本文的目的在于研究JHotDraw的框架,探讨与分析其源码, 以学习软件架构及设计模式的具体运用等知识.
JHotDraw项目主页位于sourceforge,你可以在http://sourceforge.net/projects/jhotdraw/上下载最新的版本。当前最新的版本为7.5。JHotDraw有详尽的文档, 而针对较新的版本, 学习JHotDraw唯一可用的资源也是这些文档, 但这对于研究其架构这些文档已经足够.
JHotDraw最初采用的语言并非Java,而是SmallTalk,项目命名为HotDraw。人们一开始是因为ET++(一个C++应用程序框架)而关注JHotDraw。而Erich Gamma将JHotDraw应用于教学,主要是通过JavaDoc的方式对程序进行说明,并提供相应的教程来说明框架设计的较为重要的地方。
JHotDraw特性
作为一个教学实例, JHotDraw 闻名于它的 excellent framework(构架良好),well design(设计良好),robust and reliable(可靠与健壮). 整个程序存在大量的设计模式与OO设计原则, 除了用于教学, 它还是一个极好的可视化程序的框架, 图形编辑器工具箱, 等等.
目前, 基于JHotDraw的程序有很多, 国外的见JHotDraw Applications , 国内的有千鸟的jphotoshop .此外,本人开发的开源地图编辑器Mepper中应用程序框架部分的设计思想也来源于JHotDraw
鉴于JHotDraw的特性, 这里将开始一段JHotDraw之旅, 探究其框架与设计, 分析重要类的源码. 其最终目的在于提升面向对象程序设计与软件架构的能力. 如果你已经精通软件架构或设计模式(excellent in design pattern), 或者你讨厌设计模式, 那么这篇文章可能不适合你.
1.下载JHotDraw
最新的JHotDraw可以在http://sourceforge.net/projects/jhotdraw/files/上下载,目前最新的版本是7.4.1. 这里就以7.0.1版作为例子, 你可以在上面的网址找到这个版本。解压后的文件结构图
2.框架实例
打开Samples,里面依次是Draw,net,pert,svg和teddy等应用于JHotDraw框架的实例。
2.1 Draw
Draw是一个二维图形编辑器,可用于简单绘图和图形制作。Draw可以说是JHotDraw最原始最核心的应用,它从HotDraw版本就一直存在。直到JHotDraw7以后,JHotDraw框架趋向成熟,Teddy等其它实例才出现。
在draw中,数据采用xml格式存储在一个proprietary中,draw的用户界面支持一些基本的桌面应用,例如:载入、保存、打印、输出以及剪切复制和撤销重做等操作。
draw可以作为一个桌面应用程序独立运行,也可以作为一个applet程序来运行。独立运行的draw支持三种不同的文档界面:单文档界面(single document interface SDI),多文档界面(multi document interface MDI) 以及Mac系统文档界面(the Mac OS X document interface)。下面是JHotDraw在apple上的运行效果图。
图1:Draw在苹果操作系统(Mac OSX)上的文档界面
图2:draw多文档界面图
2.2 NET
NET是由两种图形(节点图NodeFigure和可连接图ConnectionFigure)组成的简单图形编辑器,NET同样是构建在JHotDraw框架之上的。
在NET中,每个节点图(NodeFigure)有四个可与其它图形链接的连接点,节点图(NodeFigure)构建于连接图(ConnectioinFigure)之上。
2.3 PERT
PERT是一个基于JHotDraw框架的计划评审图制作工具。PERT即 program evaluation and review technique,是一个软件工程概念,常用于项目规划中。PERT由任务(tasks)和依赖(dependencies)两部分组成。任务(tasks)是PERT图的节点,依赖(dependencies)则是PERT图的边框部分。
每个任务图三个字段:任务名称(name)、开始时间(strat)与持续时间(duration)。在PERT中,用户可以在任务图中设置任务名称和持续时间这两个属性,并由PERT计算开始时间属性。当任务的持续时间(duration)设置为0时表示该任务是一个旅程碑(软件工程概念,表示重要的时间点,比如“开始”)。PERT中,箭头图形表示依赖关系。
在PERT中,任务图(task)能够从“语义”上感知自己:连接两个任务即表示在这两个任务之间建立依赖关系。连接两个任务后,被连接(dependent task)将重新计算其持续时间(duration)字段。另外,PERT不允许建立有循环(回路)的评审图。
2.4 SVG
正如你所想, SVG是一个应用在JHotDraw框架下的svg (1.2版, svg即可伸缩向量图形 Scalable Vector Graphics)编辑器. SVG编辑器支持旋转,缩放以及放射性变换(affine distortion).
2.5 Teddy
Teddy 是一个应用在JHotDraw框架下的文本编辑器(记事本).
当Teddy在Mac上运行时, 程序使用Mac文档界面. Teddy实现了基于苹果人机交互界面指南(Apple Interface Guidelines )上的文本编辑器.
当Teddy 运行在其它平台时便使用单文档界面(SDI), 如Windows, Linux和unix等其它系统.
2.6源码分析的准备
1. 获取JHotDraw
在http://sourceforge.net/projects/jhotdraw/ 下载最新版的JHotDraw, 我下载的是7.4.1版.
2. 工具
eclipse: 下载地址: http://www.eclipse.org . eclipse用户源码跟踪与编写(个人使用习惯,你可以选择你喜欢的IDE).
netbeans: 下载地址: http://www.netbeans.org (同上).
3. uml 工具
我使用的是netbeans(你也可以使用rose,ea,trufun等其它uml工具), netbeans自带了非常好用的uml插件, 可以大部分的uml建模需求. 如果你的netbeans没有uml功能,你可以按照以下步骤来安装这个插件:
打开netbeans, 在菜单栏找到: 工具--插件. 见下图:
选择"可用插件", 如果插件太多, 可以在右边搜索框里填入:uml, 选中uml插件并安装.
如果插件安装成功, 则可在"已安装"里找到uml插件. 如图:
4. 导入
JHotDraw是一个netbeans工程, 所以可以用netbeans直接导入. 并由netbeans进行反向工程.
反向工程建立后, netbeans会建立一个uml项目,该项目以原项目的名字加上"-model"来命名. 这个uml项目主要由三部分组成: Model, diagrams,imported elements. 这时我们可以在Model里找到所有与JHotDraw相关的Java对象.
在Diagrams里新建一个类图(class diagram), 并从model ../org/JHotDraw/app/里面拖出相应对象,就可以创建一个app包的结构图.
3 JHotDraw的MVC架构
1. JHotDraw框架概述
开发应用程序时, 大多数情况下会反复处理一些相同的事情: 管理应用程序的生命周期, 事件处理, 线程管理, 本地化资源持久化处理. 为了节省时间与精力, 开发一个可复用的框架以节省开发成本显得非常有必要. 而应用程序框架正是提供多数应用程序都会用到的基础设施(infrastruture), 以节省重复开发的成本, 并提高程序的可维护性. 框架常以可复用类库形式导入到程序中.
框架常常会令人望而却步, 因为框架必须是设计良好的,而良好的设计总是以复杂和庞大为代价. 对于比较小的应用程序来说, 引入大型框架可能使原来的应用程序复杂化, 甚至比没有使用框架还费力. JHotDraw的框架并非是庞大和复杂的(不过随着版本的升级,它正在朝着正方面发展), 它致力于减轻使用框架的成本. 它提供了开发基于swing的应用程序的核心架构,包括程序生命周期控制, 资源管理, 事件处理, 线程管理和本地存储等功能.
2. 框架架构
整个JHotDraw应用框架的api都位于包: org.jhotdraw.app . app包定义了Application, ApplicationModel, view等接口. JHotDraw框架正是构建在MVC模式之上, 其中Application封装了控制应用程序生命周期的方法:
1. init() 初始化程序;
2. launch() 启动程序;
3. start() 开始运行;
4. stop() 停止运行;
5. destroy() 退出程序;
构建一个基于JHotDraw应用程序框架的基本步骤为:
1. 定义一个View
2. 创建Application,并启动它.
下面是示例代码:
HelloView.Java
import java.io.IOException; import java.net.URI; import org.jhotdraw.app.AbstractView; import org.jhotdraw.gui.URIChooser; public class HelloView extends AbstractView{ public void clear() { } public void read(URI uri, URIChooser chooser) throws IOException { } public void write(URI uri, URIChooser chooser) throws IOException { } }
HelloApplication.java
import org.jhotdraw.app.Application; import org.jhotdraw.app.DefaultApplicationModel; import org.jhotdraw.app.SDIApplication; public class HelloApplication { public static void main(String[] args) { Application app=new SDIApplication(); DefaultApplicationModel model=new DefaultApplicationModel(); model.setViewClass(HelloView.class); app.setModel(model); app.launch(args); } }
示例程序的运行效果
图1 org.jhotdraw.app包结构图
图2. JHotDraw MVC架构
图3 org.jhotdraw.app.Applicatioin 接口契约
3.2 MVC架构
这里只介绍JHotDraw框架的MVC模式. 关于MVC的基本概念,如MVC是什么, 为什么要用MVC, MVC的优点和缺点有哪些, 什么是改进的MVC等等等等, 这些问题可以Google一下, 这里只简单概述.
MVC发明于80年代,最早是运用在smalltalk上的. MVC试图将视图,控制器和模型分离,使之成为三个独立的部件以提高代码可复用率及可维护性.
视图:即肉眼看到的界面(严格的说法是:用户看到并与之交互的界面便为视图),视图只负责显示数据或其它可与用户交互的元件(按钮,菜单和链接等).
模型:模型是对现实数据和业务规则的模拟,也可以说是对数据和规则建模的结果. 模型为众多视图提供了数据来源. 对模型进行分离的最大好处就是一个模型可以被多个视图重用, 既减少了代码的重复量, 也提高代码的可维护度.
控制器:控制器接收用户的输入(操作)并调用模型和视图去完成用户的需求. 如博客网站中, 当用户编写完博客点击(页面上的)提交按钮时, 控制器接收请求并将请求交给相应的模型去处理, 而控制器本身对请求不做任何处理. 控制器发送完请求后, 根据模型返回的信息调用相应的视图显示处理的结果.
MVC的好处在于重用了大量的代码, 并且极大地提高了代码的可维护性. 比如在一个交友网站中, 如果你的数据从mySql迁移到noSql上, 你只需要修改模型的内部逻辑, 而无需修每个页面都需改.
再来看JHotDraw的MVC,
HelloApplication.java
import org.jhotdraw.app.Application; import org.jhotdraw.app.DefaultApplicationModel; import org.jhotdraw.app.SDIApplication; public class HelloApplication { public static void main(String[] args) { Application app=new SDIApplication(); DefaultApplicationModel model=new DefaultApplicationModel(); model.setViewClass(HelloView.class); app.setModel(model); app.launch(args); } }
首先是模型的建立(model)(即上面第8行代码). 这里的model不是业务逻辑的model, 而是应用程序的model. 它是在应用程序层次上对应用程序建模而来的. Model封装了程序名称, 程序版本号以及一系列的action(控制器)(model的详细构造将在下面的章节论述). model还存放了应用程序的视图(view)(第10行代码). 这样, 当程序运行时, application便调用model的getView()方法来获取视图并将其显示, 而用户通过点击控制器(按钮,菜单)激活控制器, 控制器将用户的操作传送给model处理, model将处理完之后, 显示相应的视图来告知用户的操作结果.
下面是应用JHotDraw框架的程序启动顺序图 (点击查看原图):
3.3 面向文档基本框架
通过前文, 我们大概了解了JHotDraw的MVC架构.简单的, 试想如果要实现一个日记程序(Daily), 应该有如下步骤:
1. 定义模型(model): DialyModel. 该模型封装了程序的名称(daily), 版本(1.0), 版权(你);
2. 定义视图(view):DailyView. 该视图可以是一个JPanel, 里面包括一个填写日记内容的JTextPane, 两个用于保存和打开的按钮(仅仅是按钮, 并没有按钮按下的动作);
3. 定义控制类(controller): 定义用于保存和打开日记的控制方法.
4. 将视图添加到模型: 通过model.setView/ViewClass方法将DailyView添加到DialyModel.
5. 将模型添加到应用程序:创建一个DailyApplication, 并通过setModel方法添加模型;
6. 启动程序: 调用DialyApplication.launch 方法.
下面补充JHotDraw框架(framework)主要接口的框架契约(contract of a framework).
设计模式:Framework(框架)
以下的接口和org.jhotdraw.app.action 包里所有类定义了面向文档应用程序的契约: ApplicationModel(org.jhotdraw.app), View(org.jhotdraw.app).
1. 接口 Application (org.jhotdraw.app) 应用程序
JavaDoc:
Application 用于控制View类的生命周期并提供一个窗口来显示View类.
一个application拥有一个ApplicationModel, ApplicationModel封装了关于application的信息(名称,版本,版权)以及创建View的模板方法.
application实现了基于文档界面的风格(document interface style), 一个application可以同时处理一个或者多个文档.
典型的文档界面有单文档界面(SDI, Single Document Interface), 多文档界面(MDI, Multiple Document Interface)和Mac系统界面(OSX, Mac OS X Application Interface). 针对这些文档风格, JHotDraw已经提供了每种文档的默认实现.
一些应用程序需要对所有打开的窗口和对话窗进行特别的设置, 这就需要在打开JFrame, JDialog或JWindow时调用application的addWindow/Palette 或removeWindow/Palette方法.(仅7.4之后的版本)
该类的使用方法:
public class MyMainClass {
public static void main(String[] args) {
Application app = new SDIApplication(); // or OSXApplication(), MDIApplication().
DefaultApplicationModel model = new DefaultApplicationModel();
model.setName("MyApplication");
model.setVersion("1.0");
model.setCopyright("Copyright 2006 (c) Werner Randelshofer. All Rights Reserved.");
model.setViewClassName("org.jhotdraw.myapplication.MyView");
app.setModel(model);
app.launch(args);
}
2. 接口 ApplicationModel (org.jhotdraw.app) 应用程序模型
ApplicationModel提供Application的元数据(名称,版本,版权), Application的控制器, 创建视图,工具栏及URIChooser的工厂方法.
3. 接口 View(org.jhotdraw.app) 视图
View通过JComponent来显示应用程序的文档.
文档以URI格式定位(一个文档的URI应为 file://home/readme.txt). 如果在多视图界面中打开同一个文档, 则应用程序将为每个文档设置"多重打开标志"来区分这些视图.
view(视图)的生命周期由application控制, 一个view的生命周期为:
1.创建(Creation): 应用程序通过调用view类的newInstance()方法将view实例化;
2.初始化(Initialisation): 应用程序调用view类的以下方法: setActioinMap(), setApplicatiin(), init().然后通过工作者线程(worker thread)调用clear()或者read()方法;
3. 开始(Start):应用程序将view添加到一个容器(如JFrame)中, 并调用view类的start方法.
4. 激活(Activation): 当view在应用程序中被激活时, 应用程序调用其activate()方法.
5. 钝化(Deactivation):当view不处在被激活状态(如其它view被激活), 在应用程序调用其deactivate(0方法. 钝化的view可以再次被激活;
6. 停止(Stop):应用程序调用view的stop()方法来停止view, 并将其从应用程序容器中删除. 停止后的view可以通过开始(Start)再次显示.
7. 销毁(Dispose):当一个view不再需要时, 应用程序调用其dispose()方法来销毁一个view. dispose()方法通过setApplication(null)并删除所有对它的引用而进入垃圾回收机制.
4. 包 org.jhotdraw.app.actions 控制器
提供"面向文档的应用程序"的抽象动作(abstract actions)及其默认实现的动作. 按照框架契约, 应用程序层次的所有动作都必须继承自org.jhotdraw.app.actions.AbstractApplicationAction类. 该类定义了诸如此类的属性:当应用程序处在disable状态时, 所有继承自AbstractApplicationAction的动作(控制器)的状态也都为disable. 基于swing的特性, 所有继承自AbstractApplicationAction的动作(控制器)既可以作为一个菜单项(menu item),也可以作为工具栏按钮. 当然, 这些动作也可以仅添加在指定的视图上.
org.jhotdraw.app.actions还定义了应用程序的一些基本动作(控制器)的默认实现类,如"关于","退出","复制","剪切"等.
相关链接:
-
http://www.jhotdraw.org/ JHotDraw项目首页
-
http://sourceforge.net/projects/jhotdraw/ JHotDraw下载
-
http://dirkriehle.com/computer-science/research/dissertation/chapter-8.html
-
http://article.yeeyan.org/compare/14599/4185 JHotDraw让你成为程序设计的毕加索
-
http://softarch.cis.strath.ac.uk/PLJHD/Patterns/JHDDomainOverview.html JHotDraw Pattern Language
-
http://st-www.cs.illinois.edu/users/brant/HotDraw/HotDraw.html 5.1版首页
-
http://www.c2.com/cgi/wiki?HotDraw 一些关于JHotDraw的资源汇集
- http://twiki.org/cgi-bin/view/Wikilearn/JHotDraw 5.1版wiki
- http://personal.cis.strath.ac.uk/~murray/efocswww/papers/EFoCS-38-2001.html Patterns for JHotDraw
- http://st-www.cs.illinois.edu/users/brant/HotDraw/HotDraw.html 早期版本的hotdraw主页与下载
其它
一网友问题
您好!请教一个问题:jhotdraw7.6中samples中draw实例中有个file菜单中有个“open”的功能,可以根据xml文件生成图形,请问这个功能的代码在哪儿?谢谢
你好,我现在用的是7.5的版本,我想应该是一样的。
实现机制:
draw的存储接口是DOMStorable,负责drawing的IO操作;所有继承或实现该接口的子类都必须实现了read和write方法,即实现具体类的读入和写出责任。AbstractAttributedFigure和Drawing都继承该接口。
读取文件的顺序为:
- openAction读入文件,调用DrawView的read方法读取该文件;
- DrawView中维护多个inputFormat,每个inputFormat负责读取特定的Figure;当调用read方法时,DrawView尝试让所有的inputFormat读取该文件,但只要有一个inputFormat读取成功就停止。
至于DrawView装载了多少InputFromat,可以参考org.jhotdraw.samples.draw.DrawView.createDrawing():
protected Drawing createDrawing() { Drawing drawing = new QuadTreeDrawing(); DOMStorableInputOutputFormat ioFormat = new DOMStorableInputOutputFormat(new DrawFigureFactory()); drawing.addInputFormat(ioFormat); ImageFigure prototype = new ImageFigure(); drawing.addInputFormat(new ImageInputFormat(prototype)); drawing.addInputFormat(new TextInputFormat(new TextFigure())); TextAreaFigure taf = new TextAreaFigure(); taf.setBounds(new Point2D.Double(10,10), new Point2D.Double(60,40)); drawing.addInputFormat(new TextInputFormat(taf)); drawing.addOutputFormat(ioFormat); drawing.addOutputFormat(new ImageOutputFormat()); return drawing; }
参考org.jhotdraw.draw.LineConnectionFigure对write()和read()的实现:
//org.jhotdraw.draw.LineConnectionFigure.read public void read(DOMInput in) throws IOException { readAttributes(in); readLiner(in); // Note: Points must be read after Liner, because Liner influences // the location of the points. readPoints(in); } //org.jhotdraw.draw.AbstractAttributedFigure.readAttributes protected void readAttributes(DOMInput in) throws IOException { if (in.getElementCount("a") > 0) { in.openElement("a"); for (int i=in.getElementCount() - 1; i >= 0; i-- ) { in.openElement(i); String name = in.getTagName(); Object value = in.readObject(); AttributeKey key = getAttributeKey(name); if (key != null && key.isAssignable(value)) { if (forbiddenAttributes == null || ! forbiddenAttributes.contains(key)) { set(key, value); } } in.closeElement(); } in.closeElement(); } } protected void readLiner(DOMInput in) throws IOException { if (in.getElementCount("liner") > 0) { in.openElement("liner"); liner = (Liner) in.readObject(); in.closeElement(); } else { liner = null; } } //org.jhotdraw.draw.LineConnectionFigure.readPoints protected void readPoints(DOMInput in) throws IOException { super.readPoints(in); in.openElement("startConnector"); setStartConnector((Connector) in.readObject()); in.closeElement(); in.openElement("endConnector"); setEndConnector((Connector) in.readObject()); in.closeElement(); }
读入机制参见:
org.jhotdraw.app.action.file.OpenFileAction#openViewFromURI() line 105
org.jhotdraw.samples.draw.DrawView#read()
org.jhotdraw.xml.DOMFactory
posted on 2012-07-22 11:20 brook.tran 阅读(2593) 评论(0) 编辑 收藏 举报