By 高焕堂 2012/05/25
多层EIT&框架设计范例—以涂鸦(Scribble)为例
内容:
** 简介「涂鸦」(Scribble)
**「涂鸦」范例的功能需求
**「涂鸦」范例的架构设计
** 结语
1. 简介「涂鸦」(Scribble)
涂鸦是一种有趣的玩意儿,也有许多用途,包括:幼儿园学生的作业本、军队传达命命、生动活泼的教学等等。因此,涂鸦程序有许多花样,有的可以配上图片,例如BugMe! NotePad上的涂鸦:
有些可以在底图上做涂鸦。再如儿童艺术的KinderArt公司的涂鸦,如下图所示。它不仅可以画线条也可以着色。
还有一些涂鸦可以配上录音。
2. 「涂鸦」范例的功能需求
2.1 功能说明
在本节里,将要设计一个涂鸦程序来说明多层EIT造形的架构设计。为了让代码精简易懂,对此示例做了一些简化:
- 只画线条,提供白、黄、红、绿四种颜色给涂鸦者挑选。
- 不配底图,也不配声音。
- 一边涂鸦,一边录制(Record)涂鸦的过程。
- 可以重复播放(Play)所录制的涂鸦情境。
在画线条的过程中,会把线条的端点和时间存录在内存里,你随时能重新播放(按<Play>按钮)。因为轨迹及时间都被存录起来,回放的时候会精准地依据原来绘图的速度和顺序而重新画一次。即使是幼儿,也能轻易彩绘各种图案,进行创作演练。[歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
2.2 操作情境
此程序启动后,就可以开始绘图(Draw)和录制(Record)了,例如绘出如下:
画完了,按下<Play>按钮,就可以重复播放所录制的涂鸦情境。按下<New>会清除画面,并可重新画图和录制了。由于此涂鸦程序,也会纪录画图的时间、顺序,所以会依据你原来所画的速度和顺序,重新画一遍给你看。按下<Exit>按钮,程序就结束了。
3. 「涂鸦」』范例的架构设计
3.1 阶段一:从传统类(Class)造形设计出发
从传统类(Class)造形设计出发,针对『涂鸦』进行传统的OOAD分析与设计(Object-Oriented Analysis & Design)。其分析步骤为:
Step-1: 找到”Model”,就是:Scribbler和dwPoint类。
首先,依据”MVC(Model-View-Control)”思维,寻找『涂鸦』的核心概念,成为类造形的内涵。例如:涂鸦的”Model”部分是:Scribbler和dwPoint类,来记录一张涂鸦的绘图过程。其中Scribbler代表录制器,而dwPoint代表轨迹(由一连串的小线段所构成)上小线段的端点;如下的类图:
dwPoint类内含有m_x、m_y、m_type、m_color和m_timeSpan共5个属性(Attribute)。其中,m_X和m_Y记载轨迹上小线段的端点坐标。m_type代表提笔中(即不画出移动轨迹)或下笔中(即会画出移动轨迹)。m_color代表目前所挑选的颜色。m_timeSpan代表从第一点到此点的时间差距。Scribbler类内含一个poList串行来储存一序列的端点。
Step-2: 从MVC而联想到”View”,将图像绘在画布(Canvas)上。但是使用拿一个类,尚未决定。如下图:
Step-3: 从画布(Canvas)联想到绘图器和颜色,也就是:Painter和Color类。
Scribbler自己不做画图的动作,而委托Painter(或Paint类)来做。Scribbler将poList串行参考传给Painter(或Paint类)并且请Painter将串行里的每一个dwPoint绘出于屏幕画面上。此外,用户可以选择画笔的颜色。于是,设计好了架构图,并以类造形的组合来表述所设计的架构如下。
3.2 阶段二:继续运用EIT造形设计
Step-4: 定义”Model”与”View”之间的接口。
从上图里,可以看出来,传统基于类造形的分析与设计,只凸显了类(Class)和关系(Relationship),而将接口(Interface)隐藏于类或关系里,此时EIT造形就派上用场了。例如,依据MVC(Model-View-Control)架构设计模式,Model和View是可以分离的。也就是在<绘图画面>与<Scribbler>之间,可以放置一个EIT造形来凸显其接口<I>,如下图:
为了清晰而明确地定义和表述这个<I>,也必须将<E>和<T>表述出来。例如可将EIT造形落实到Android平台的SurfaceView类和Callback接口,如下图:
由于EIT造形有两种变形,所以上图也相当于下图:
由于EIT造形是代码层级的造形,所以开发者很容易对映到代码。由于SurafaceView、Painter和Color都是Android框架已经定义好了的类,于是开发这只需要撰写Scribbler、dwPoint、mySurfaceView和MainActivity类即可了。如下述的Android程序开发项目(Project)。
Step-5: 建立一个曹操类(即Stub类)。
在上图里,可以看出来,mySurfaceView类里,实现了Callback接口的函数,以及SurfaceView里的抽象函数(或Overridable函数)。如果将SurfaceView视为天子,而mySurfaceView视为子民。这是单层EIT造形的架构。
从三国演义的故事里,可以知道还有曹操的角色:介于天子与子民之间,而产生<挟天子以令诸侯(或子民)>的效果。例如,于上图里,在<T>与<E&I>之间插入一个SvStub类(及曹操类)。这个SvStub类既扮演上层EIT造形的<T>角色;又扮演下层EIT造形的<E&I>角色。于是得到一个两层EIT造形的架构,如下图:
这个SvStub类既是上层EIT造形的<T>,同时又是下层EIT造形的<E&I>。为什么称它是曹操类呢? 因为它实现了天子的Callback接口,而提供了自己的接口。例如,它实现了Callback接口里的surfaceCreated()函数,并提供了自己的新接口,例如定义了自己的begin_record()抽象函数。
此时,mySV类(子民)就看不到天子的Callback接口了,只看到SvStub类(及曹操类)自己定义的新接口。所以,才称之为<挟天子以令诸侯(或子民)>。在架构设计上,这是很重要的技巧,其主要效果是:
- SvStub类改变了接口。例如,将Callback的surfaceCreated()转换为begin_record()函数。让mySV开发者只用到自己定义的新接口,而不受Callback接口的束缚。将通用性的Callback接口转换成为较特殊化的接口,常常可以减轻mySV类的复杂性,降低mySV类开发者的负担。
- 将SurfaceView与Scribbler两者切分得更清晰。例如,SvStub类负责与SurfaceView类沟通;而mySV则负责与Scribbler沟通。这种清晰的分离,对未来系统的重构是很有帮助的。
建立Android开发项目
现在,就来将上图里的双层EIT造形落实为代码。例如下述的Android程序开发项目(Project)。
在上图里,虽然建立了两层EIT造形,但是仍然只是两层框架而已。所以上述所写的各类都属于应用程序(App)的内容。这涂鸦的UI画面上,有个颜色盘:
这颜色盘的内容是写在Scribbler类里:
public Scribbler(SurfaceHolder holder){
// ………
colorList = new ArrayList<Integer>();
colorList.add(new Integer(Color.WHITE));
colorList.add(new Integer(Color.RED));
colorList.add(new Integer(Color.GREEN));
colorList.add(new Integer(Color.YELLOW));
}
由于,这Scribbler类是App里的内容,所以是由App开发者来决定颜色盘的内容的。
Step-6: 建立两层框架(Framework)。
在上图里,虽然建立了两层EIT造形,但是仍然只是两层框架而已。然而,这种两层EIT造形架构,可做为两层框架的基础。例如,曹操类(即SvStub类)和Scribbler(含dwPoint)类,可以归属于一个单独的框架,如下图:
于是,形成了两层框架了。上层的SurfaceView属于Android大框架;而SvStub和Scribbler则属于曹操小框架。然后,mySV则属于App里的一个类。[歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
由App类开发者决定颜色盘的内容
由于Scribbler类已经不属于App的内容了,但是仍然必须让App开发者来决定颜色盘的内容(包括几种颜色、那些颜色)。于是,可看出了一个新EIT造形:Scribbler扮演<E>,而mySV扮演<T>;如下图:
这提醒我们:必须设计一个<I>来衔接<E>和<T>,才成为一个完整的EIT造形。于是,小框架定义了一个IScribbleColor接口,这让mySV开发者能实现这接口里的getScribbleColors()函数。
也就是,由App开发者来决定颜色盘的内容(包括几种颜色、那些颜色)。如下述的Android程序开发项目(Project)。
Step-7: 维护Scribbler核心模块的变动自由度。
在上图里,Scribbler类的函数名称,直接被写在 SvStub类或其它类里。如果Scribbler类有所改变(例如更改函数名称)时,必须要更改SvStub类或其它类里的代码;这大大减损了Scribbler类(或称模块)的变动自由度(因为牵一发而动全身)。于是,EIT造形就派上用场了,如下图:
这个EIT造形的<E>提供通用性接口给SvStub等类来调用;而<T>则调用Scribbler类的函数。有时候,EIT造形的<E>被省略(或称退化)了,直接将<I>提供出来,成为通用性的接口。如下图:
Scribbler类的函数名称只被写在<T>里。在未来,如果Scribbler类有所改变时,只需要更改<T>的代码即可了;这大大提升了Scribbler类(或称模块)的变动自由度。
建立Android开发项目
现在,就来将上图里的双层框架落实为代码。例如下述的Android程序开发项目(Project)。
由mySV类来提供颜色盘的内容,如下代码:
@Override
public ArrayList<Integer> getScribbleColors() {
colorList = new ArrayList<Integer>();
//-----------------------------------
colorList.add(new Integer(Color.WHITE));
colorList.add(new Integer(Color.RED));
colorList.add(new Integer(Color.GREEN));
colorList.add(new Integer(Color.YELLOW));
return colorList;
}
Scribbler类会透过IScribbleColor接口来调用getScribbleColors()函数而取得colorList的内容。同样地,MainActivity也调用mySV的getColorList()函数而取得colorList内容,然后显示在涂鸦的画面上。在本范例里,只限制4种颜色(因为采取Button的静态呈现);必要时课改采ListView方式来动态呈现颜色盘。
Step-8: Scribbler的<没钱就改版,改版就有钱>。
只要IScribble通用性接口不改变,随时都能弹性抽换Scribbler类,或者随时都能轻易让Scribbler更换新版本。如下图:
4. 结语
从本章的<涂鸦>范例里,可领悟到:
”以简单的形,设计其形形色色的组合;来包容复杂(多变)的内容,是架构设计的真谛。”
例如,在<涂鸦>范例里,可看到如何组合出两(多)层 EIT造形的体系。它又能支撑两(多)层框架的体系。框架与App是由不同团队开发的,上下层框架也可以由不同团队来开发。所以,多层框架体系,也意味着一组团队之间的分工体系。EIT造形的主角<I>正好能清晰定义了系统模块之接口,也明确定义了团队分工合作的界线。
[Go Back]