By 高焕堂 2011/09/04

[ IT史上最完整、最经典的软件框架开发技术宝典 (上百篇经典文章&eBooks) ]

[ 請指教:高老師的免費on-line教學視頻 ]  

                                                                                                            

[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]