By 高焕堂 2011/09/04
[ IT史上最完整、最经典的软件框架开发技术宝典 (上百篇经典文章&eBooks) ]
[Go Back]
敏捷架构设计与代码造形
前言
在敏捷开发里,流行的敏捷设计观点是:“敏捷是把原来架构设计里的详细设计放到了编码的过程中,开发人员要有架构师的思维,架构师的预先架构设计要<正好足够>。”在敏捷中,将传统的架构设计分成:架构 + 设计。
- 敏捷开发的架构师,将架构部分做到”足够好”即可。
- 然后,将(详细)设计转移到代码撰写阶段、重构阶段、以及单元测试阶段等。
简而言之,在敏捷中,架构设计仅止于”足够好”的架构,不进行详细设计。但详细设计仍然存在,只是转移到了开发阶段而已。这些详细设计需要花费大量的时间,包含详细的流程、数据结构等设计;而且在编程阶段,也蕴含了很多详细设计的内容。如果直接将设计表现于代码中(Code is design),就能避免开发人员同时既要维护一套设计,又要维护一套代码;可大幅减轻开发者的负担,提升效率。
1. 如何表述(Represent)”足够好的架构”呢?
架构设计被切分为”足够好的架构”和”详细设计”两个部分,那么这两部分又如何相互衔接呢?
其实大家都知道,架构师最关键的任务就是:接口(Interface)设计。所以,”足够好架构”的核心部分就是:接口的定义(Definition)和表述(Representation)。那么,有什么有效的方法能清晰而明确地定义和表述接口设计呢?
答案是:代码造形(Code Form)。
2. 什么是代码造形?
顾名思义,代码造形就是代码层级的设计造形(Form)。代码造形就是开发者常用的词汇(Vocabulary),其能直接对映(Map)到程序语言的基本结构,此结构大多定义成为关键词(Key word)。例如,指令(Instruction)、函数(Function)和类(Class)。[關於EIT代碼造形的詳細說明]
3. 代码造形在软件开发上的用处
- 因为代码造形能直接对映到程序语言的结构,具有高度的精确性(Precision),架构师能准精确地传达设计的涵义。
- 因为代码造形是开发者的词汇,架构师以<代码造形>表述架构,基于共同词汇,提升了共识(Shared Understanding),开发者很容易理解其架构的设计涵意。
- 所以,代码造形能大幅提升开发者的效率;而且迅速配合需求变更、架构创新(或重构)设计,大幅提升了整体团队的敏捷性。
- 架构师如同妈妈,使用kid language来与小孩交谈,非常有助于小孩语言天份的开发。同样地,架构师以<代码造形>来表述架构,来与开发者交谈;非常有助于开发者设计能力的提升。
- 架构师自由创意去思考架构设计(加法设计),但是都以一致的<代码造形>来表述架构设计(减法设计)。就如同唐诗的<七言绝句>造形,李白、杜甫、白居易人人都能发挥创意、尽情思考,但都以一致的造形来表述(Representation)。不但没有伤害创意,而且还基于<诗同形>而相互激发创作的氛围。
- 即使架构尚未达到”足够好”,只要能清晰而明确地表述,就能随着敏捷迭代而互相切磋着磨而达到”足够好”的境界了。
4. 大家熟悉的2种代码造形:函数和类
在软件开发中,大家最熟悉的代码层级的基本造形有二:函数(Function)和类(Class)。
1970年代的主要造形:函数(Function)
像C语言的代码基本结构就是函数,例如:
/* C语言程序代码 */
int function add( int x, int y)
{
int sum;
sum = x + y;
return sum;
}
int function mul( int x, int y)
{
int sum;
sum = x * y;
return sum;
}
int function exec( int a, int b)
{
int k = mul( add(a, b), 100);
}
void main()
{
printf(“%d”, exec(3, 5));
}
函数造形简单,其内部的组成要素是:指令(Instruction),或称叙述(Statement)。也有简单的造形组合规律:线性排列,并相互调用(Function call)。
1980年代的主要造形:类(Class)
自从1980年代到今天,软件开发的主要造形是:类(Class)。类造形并不难理解,它只是对函数造形加以扩大;也就是以函数为基础(保留了函数的各项功能),扩大结合了属性(Attribute);让开发者拥有更大的视野,具有更好的整体观。就如同太极图,引导人们掌握更宏大的整体观。
像C++语言的代码基本结构就是类,例如:
// C++程序代码
class Calculator {
int x, y, value;
public:
void set(m, n){
x = m;
y = n;
}
void add() {
value = x + y;
}
void mul() {
value = x * y;
}
int get() {
return value;
}
}
//-----------------------------------------------
class Adder extends Calculator {
public:
int exec(int m, int n){
set(m, n);
add();
set(get(), 100);
mul();
return get();
}
}
//------------------------------------------------
void main(){
Adder adderObj = new Adder();
printf(“%d”, adderObj.exec(3, 5);
}
自从1986年C++语言问世以来,类(Class)都是主要的软件代码造形(Form),例如C++、Objective-C、Java和C#等语言的主要代码造形就是类。 类造形内含2个要素(更小的组成单位):属性(Attribute)和函数(Function)。也有清晰的造形组合规律:定义了类与类之间的组合关系,例如上述范例里的”扩充( Extends)”关系等;并透过内含的函数来相互调用。
5. 介绍新的EIT代码造形
EIT造形的涵意
自从1996年Java问世之后,接口(Interface)成为Java语言的关键词(Key Word)。于是,<接口>的位阶已经提升了,其与<类>是同位阶了,而不再隐藏于类造形里。这意味着,我们可以设计一个更大的代码造形来包容类和接口两种元素。为了凸显接口角色,就得考虑两项特性:
- 为了清楚地定义一个接口(主角),需要两个类来当配角。
- 此外,接口以能实现为类(造形)。
于是,高焕堂 老师将3个<类造形>组合起来,成为一个更大的造形;就像生物DNA的螺旋结构,组合如下图:
在上图里,为于中间的类就是接口实现类。高老师将其命名为EIT造形:
EIT造形也不难理解,它只是对类造形加以扩大;也就是以类为基础(保留了类的各项功能),将3个类结合在一起,各扮演不同角色;让开发者拥有更大的视野,具有更好的整体观。例如,Java语言的程序:
class Tasks implements Runnable{
public void run() {
int sum = 0;
for (int i = 0; i <= 100; i++)
sum += i;
System.out.println("Result: " + sum);
}
}
// ----------------------------------------------------------
public class JMain {
public static void main(String[] args) {
Thread t = new Thread( new Tasks());
t.start();
System.out.println("Waiting...");
}
}
其主要意图是:凸显出<接口>元素与类同位阶的角色。为了清楚地定义一个接口(主角),需要两个类来当配角。例如,为了凸显Runnable接口,而且要精确地表述它,就需要Thread和Tasks两个类来陪衬,如下图:
由于接口定义是架构师的主要职责,所以EIT可以协助架构师清晰地定义接口,非常有助于清晰表达架构。
EIT造形的重复组合
EIT造形也内部结构简单,也能透过内含的类的组合关系,将EIT造形组合起来,轻易地组合出大型而复杂的系统。例如,EIT造形能像DNA螺旋结构一样,组合起来:
EIT造形提供更宏大的整体观,更易于重构,迅速从简单组合出复杂系统。
这是由两个EIT造形(即Thread造形和View造形)所组成的。
在游戏软件应用上,这个Thread造形里的小线程(由UI线程所诞生的)扮演一个特殊的角色:成为游戏的主控循环(Game Loop),而UI线程则专注于响应UI 的事件,创造出两个线程完美分工。由于这个线程专注于游戏主控循环,所以又称为游戏线程(Game Thread)。游戏线程调用postInvalidate()函数,间接触发UI线程去调用invalidate()函数了,也触发View重新调用App子类的onDraw()去重新绘图了。现在就将上图落实为Android程序码,如下:
// GameLoop.java
package com.misoo.pk001;
public class GameLoop implements Runnable {
myView mView;
GameLoop(myView v){
mView = v;
}
public void run() {
mView.doUpdate();
mView.postInvalidateDelayed(1000);
}
}
// myView.java
package com.misoo.pk001;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class myView extends View {
private Paint paint= new Paint();
private int x, y;
private int line_x = 100;
private int line_y = 100;
private float count = 0;
myView(Context ctx)
{ super(ctx); }
public void doUpdate(){
if( count > 12) count = 0;
x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
count++;
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
paint.setColor(Color.BLUE); paint.setStrokeWidth(3);
canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
paint.setStrokeWidth(2); paint.setColor(Color.RED);
canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);
paint.setColor(Color.CYAN);
canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);
Thread gt = new Thread(new GameThread(this));
gt.start();
}
}
// myActivity.java
package com.misoo.pk001;
import com.misoo.pk001.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
public class myActivity extends Activity implements OnClickListener {
private myView mv = null;
private Button ibtn;
@Override protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
show_layout_01();
}
public void show_layout_01(){
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
mv = new myView(this);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(200, 200);
param.topMargin = 10;
param.leftMargin = 10;
layout.addView(mv, param);
//----------------------------------------------
ibtn = new Button(this);
ibtn.setOnClickListener(this);
ibtn.setText("Exit");
ibtn.setBackgroundResource(R.drawable.gray);
LinearLayout.LayoutParams param1 =
new LinearLayout.LayoutParams(200, 65);
param1.topMargin = 10; param1.leftMargin = 10;
layout.addView(ibtn, param1);
//-----------------------------------------------
setContentView(layout);
}
public void onClick(View v)
{ finish(); }
}
由于EIT造形只是对类造形加以扩大;也就是以类为基础(保留了类的各项功能),将3个类结合在一起,各扮演不同角色。所以,只要利用类造形既有的组合机制,就能将EIT造形组合起来,成为复杂的系统了。例如,也能组合如下图:
这通称为:双层EIT造形的架构设计。◆
[Go Back]