平台框架(Framework)开发的雕龙之技6招

前言:客端呼叫抽象类别的TemplateMethod()函数,而此TemplateMethod()函数转而呼叫抽象类别的PrimitiveOperation()函数。由于此PrimitiveOperation()函数是抽象函数,也就是卡榫函数,于是转而呼叫具体子类别的PrimitiveOperation()函数。其中,AbstractClass抽象类别是属于框架,而ConcreteClass具象子类别则属于应用程序。

wKioL1MFd2yjSC3SAAEfgKFAUB8610.jpg

ee                                                                        ee

欢迎访问 ==>高老师的博客网页

高焕堂:MISOO(大数据.大思考)联盟.台北中心和东京(日本)分社.总教练


EE                                                                        EE

平台框架开发的雕龙之技 6

实践接口:擅用抽象类别(Abstract Class)

◆  实践框架接口:擅用Template Method设计样式

◆  谁来诞生应用子类别的对象呢? 答案是:框架

◆  规划壁虎的尾巴

◆  设计Callback机制

◆  由基类封装线程(Thread)的复杂性



1. 雕龙之技#1:实践接口:擅用抽象类别(Abstract Class)

  • 不要期待一部汽车能在街道上,也能在沙滩上跑。

  • 不要期待一支AP能在WinMobile上跑,也能在Android上跑。

  • 但是,如果能随时抽换轮胎的话,汽车就有可能。

  • 但是,如果能随时抽换应用子类别的话,AP就有可能。

接口的来源

  为了随时能更换轮胎,可以将一部完整的「汽车」看成一颗优质的「大理石」,然后学习伟大雕刻师罗丹的雕刻之道:“把不必要的部分去掉”。首先仔细审视一部优质的「汽车」(如同大理石),如下图:

02064618-574c0a1650df4226aa1df1eeb6a5ad6

图1  罗丹的雕刻之道:把「不」必要的部分去掉


由于要等客户出现才能决定轮胎,所以客户到来之前,先不做轮胎。于是,把轮胎(含轮毂)部分去掉,先不做轮胎,而得出「汽车轮盘」,如下图所示:

02064637-e8910e58814a42298479a67202fe360

图2  挖掉轮胎,出现接口(即轮盘)


软件接口的实践:抽象类别

为了随时能更换子类,可以将一支完整的「软件类别」看成一颗优质的「大理石」,然后学习伟大雕刻师罗丹的雕刻之道:“把不必要的部分去掉”。首先仔细审视一个「类别」(如同大理石),如下图:

02065008-b58d02d25f87428fb9a2d4f9aea02da

图3  一颗软件大理石


由于要等客户出现才能决定轮胎,所以客户到来之前,先不做轮胎。于是,把轮胎(含轮毂)部分去掉,先不做轮胎,而得出「软件接口」,如下图所示:

02065143-6b245f4dea7f4d3baf435cfcd33dcf3

图4  挖掉小鸟之形,出现软件接口

抽象类别之内涵:抽象函数

抽象类别含有抽象函数(Abstract Function),成为抽象类别与它的具象子类别(Concrete Class 或Subclass)之卡榫函数(Hook Function)。[�g迎光�R 高�ㄌ� �W�: http://www.cnblogs.com/myEIT/ ]

02065206-8ebe949a90984bd4be48b077916bc38

图5  挖掉小鸟之形,出现软件接口


为何叫做卡榫函数呢? 因为它是让具体子类别来相衔接的接口处。例如,

02065231-7e40e93b24014725a390017078844f7

图6  挖掉小鸟之形,出现软件接口


卡榫函数是抽象类别与具象子类别之间的接口。


2. 雕龙之技#2:实践框架接口:擅用Template Method设计样式

卡榫函数是抽象类别与具象子类别之间的接口。如果再加上抽象类别与客端(Client)之间的接口,就成为大家所熟悉的Template Method设计样式(Pattern)了。这个样式就是来自GoF的<<Design Patterns>>一书,此样式如下图所示:

02065344-7397e26f85b44257a5e3128f9d39255

 图7  GoF的Template Method样式图


   客端呼叫抽象类别的TemplateMethod()函数,而此TemplateMethod()函数转而呼叫抽象类别的PrimitiveOperation()函数。由于此PrimitiveOperation()函数是抽象函数,也就是卡榫函数,于是转而呼叫具体子类别的PrimitiveOperation()函数。其中,AbstractClass抽象类别是属于框架,而ConcreteClass具象子类别则属于应用程序。将之对应到上述的「画鸟」范例,得到下图:

02065420-7b09cf33817840419b43e61da7ed613

图8  「画鸟」范例的Template Method样式


3. 雕龙之技#3:谁来诞生应用子类别的对象呢? 答案是:框架

   框架开发在先,应用子类别开发在后,框架开发者事先无法预知应用子类别的名称,那么他又如何去new一个应用子类别的对象(Object)呢?

02065621-e897f33803a044248a82fb89f97c26b

 图9  框架诞生Bird子类别的对象

如果是Java框架,就可使用Java的CreateInstance()函数来实际诞生Bird应用子类别的对象。

4. 雕龙之技#4:规划壁虎的尾巴

   小框架终究还是要离开母框架而自主运行或移植到其它平台上,就像一位姑娘终究要离开母亲,而自主生活或嫁入婆家的。为了让小框架拥有独立自主的求生能力,我们应该帮她规划好对外的结合接口。于是,母框架扮演一只恶猫,而小框架扮演一只壁虎,就可以找出壁虎的尾巴了。规划壁虎的尾巴一直是框架设计的重要之技之一。例如,Blackjack扑克牌游戏的Android应用程序,其一般架构如下图:

02065753-0b6c9b81219c4cb6b0c1f4021032a4d

图10  壁虎在恶猫的嘴巴里

 其中,Blackjack游戏玩法的主要函数(如bj_f1()、bj_f2()等)都分散于Activity或View的子类别里。兹举个比喻:

  • Activity、View等基类,如同一只猫。

  • bjActivity、bjView等子类别,如同猫的嘴巴。

  • bjActivity、bjView等子类别里的onCreate()、onDraw()等函数,如同猫的牙齿。

  • Blackjack游戏主要函数(如bj_f1()、bj_f2()等),如同一只壁虎的身体。


从这个比喻,可以看出来这只壁虎是很可怜的。

02065819-5dec9cd79a5141708e925ca65d74145

图11  弃尾求生术


  这样的新架构,让壁虎随时可以弃尾求生,移植到别的平台里,继续生存下去。甚至可以成为框架的一部分,如下图:

02065846-931ec1a741da4a5c9f1844d90d69391

 图12  移到框架里(一)


02065904-909ae508d47c44afa54fd3df1487262

图13 移到框架里(二)


5. 雕龙之技#5:设计Callback机制

   前面介绍的Template Method样式都是将「会变」的部份委托给子类别,当有所变化时,就抽换掉子类别,换上新的子类别就行了。由于该子类别与其抽象类别之间具有「继承」关系,所以就通称为:继承方式的反向控制(IoC, 或称Callback)。如下图:

02071053-5b4c662b210d4f3fa5bd5dda7437123

图14  Callback(继承)


   现在介绍另一种反向控制,则是某一个类别将「会变」的部份委托另一个类别,这两个类别不必具有继承关系,而只须具结合(Association)关系。我们称此为:委托方式的反向控制(IoC, 或称Callback)。

02071136-8ed6f3715a544bda8b4447ed942861d

图15  Callback(接口)


Callback的实践

// ILoc.java

package com.misoo.ppxx;

publicinterface ILoc {

int getY(int y);

}

//---------------------------------------------

// myView.java

package com.misoo.ppxx;

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.view.View;

import android.view.View.OnClickListener;

publicclass myView extends View implements OnClickListener{

private Paint  paint= new Paint();

privateint line_x = 10;

privateint line_y = 30;

private ILoc callback;    

    myView(Context ctx) {

super(ctx);

             setOnClickListener(this);

         }

 @Override

protectedvoid onDraw(Canvas canvas) {

super.onDraw(canvas);

                   canvas.drawColor(Color.WHITE);

                   paint.setColor(Color.GRAY);

                   paint.setStrokeWidth(3);

                   canvas.drawLine(line_x, line_y, line_x+120, line_y, paint);

   paint.setColor(Color.BLACK);

                   paint.setStrokeWidth(2);

        canvas.drawText("click here please", line_x, line_y + 50, paint);

int pos = 70;

                   paint.setColor(Color.RED);

                   canvas.drawRect(pos-5, line_y - 5, pos+5, line_y + 5, paint);

                   paint.setColor(Color.YELLOW);

                   canvas.drawRect(pos-3, line_y - 3, pos+3, line_y + 3, paint);

this.invalidate();

        }

      @Override

publicvoid onClick(View arg0) {

               line_y = callback.getY(line_y);

       }

publicvoid setCallback(ILoc cb){

               callback = cb;

       }

}

//------------------------------------------------------------

// myActivity.java

package com.misoo.ppxx;

import android.app.Activity;

import android.os.Bundle;

import android.widget.LinearLayout;

publicclass myActivity extends Activity{

   @Override

publicvoid onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

    LinearLayout layout_01 = new LinearLayout(this);

           layout_01.setOrientation(LinearLayout.VERTICAL);

           LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(150, 200);

           param.leftMargin = 1;         param.topMargin = 3;

           myView mv = new myView(this);

           mv.setCallback(callback);

           layout_01.addView(mv,param);

           setContentView(layout_01);

   }

ILoc callback = new ILoc(){

       @Overridepublicint getY(int y) { return y+=10;      }

 };

}


6. 雕龙之技#6:由基类封装线程(Thread)的复杂性

   线程(Thread)又称为<执行绪>。因为Android主线程(Main Thread)必须迅速照顾UI画面,尽快处理UI事件,因此常常需要创件小线程(Sub Thread)去执行一个特定或较费时的模块。例如,必须使用小线程去执行如下图1里的BiAdder.java类别时,该如何做呢?  

02071537-e5dbbb737aa449a488a07a5db2040a3

 图16  由应用子类别诞生小(子)线程


   由于BiAdder的执行时间可能超过5秒钟,所以最好使用子线程去执行之。但是这件事情可能会困扰着myActivity类别的开发者,因为BiAdder开发者可能不同于myActivity的开发者。所以myActivity类别的开发者通常并不知道BiAdder切确的执行时间长度。在此种情况下,最佳的解决办法是:

  • BiAdder开发者也提供一个基类(Super-class),例如下图2里的Adder基类,由它来包容线程的考虑,于是myActivity类别开发者就会开心了,他不必替BiAdder执行时间长短而伤脑筋了。

02071759-e3e8287d39ae41c392f26c863e53b86

图17  由基类吸收线程的复杂性


  在此图里,myActivity和myAdder两个类别都是由主线程执行,所以这两类别的开发者很开心,不必顾虑BiAdder的执行时间长度。可认为BiAdder类别也是由主线程执行的(其实是子线程执行的)。于是,在Adder::exec()里以主线程执行myAdder子类别的onGetX()和onGetY()两个函数,并诞生子线程来执行BiAdder::execute()函数。等到执BiAdder::execute()行完毕,就透过MQ要求主线程执行myListener::callback()函数。在执行myListener::callback()时,子线程已经计算出carry和sum值了。这时主线程可取得正确的carry和sum的值。所以这样的写法是对的。如下述的原始程序代码:

// IListener.java

package com.misoo.adder;

publicinterface IListener {

publicvoid callback();

}

//------------------------------------------

// BiAdder.java

package com.misoo.adder;

publicclass BiAdder {

privateint a, b, carry, sum;

publicvoid set_digits(int ia, int ib){

         a = ia;  b = ib;

   }

publicvoid execute(){

try {

               Thread.sleep(6000);

           } catch (InterruptedException e) {

               e.printStackTrace();

              }

carry = a & b;

           sum = a ^ b;

 }

publicint get_carry(){

return carry;

    }

publicint get_sum(){

return sum;

   }

  }

//------------------------------------------

// Adder.java

package com.misoo.adder;

import android.os.Handler;

import android.os.Looper;

abstractpublicclass Adder {

private BiAdder ba;

privatestatic IListener plis;

public Adder()

                  {

                     ba = new BiAdder();

                  }

publicvoid exec(){

                      ba.set_digits(onGetX(), onGetY());

new Thread(){

publicvoid run() {

                                 ba.execute();

                                 Handler h = new Handler(Looper.getMainLooper());

                                 h.post(new myRun());

                         }

                 }.start();

                }

publicint get_carry(){

return ba.get_carry();

                   }

publicint get_sum(){

return ba.get_sum();

                  }

abstractpublicint onGetX();

abstractpublicint onGetY();

publicstaticvoid setListener(IListener listener){

plis = listener;

                }

class myRun implements Runnable{

publicvoid run() {

plis.callback();

                       }

                }

}

//--------------------------------------------------------

// myAdder.java

package com.misoo.pk01;

import com.misoo.adder.*;

publicclass myAdder extends Adder {

public myAdder(){

super();

    }

              @Override publicint onGetX() {

return 1;

              }

              @Override publicint onGetY() {

return 1;

              }

}

//-----------------------------------------------

// myActivity.java

package com.misoo.pk01;

import com.misoo.adder.IListener;

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;

publicclass myActivity extends Activity implements OnClickListener {

private Button btn, btn2;

private myAdder adder;

publicvoid onCreate(Bundle icicle) {

super.onCreate(icicle);

               LinearLayout layout = new LinearLayout(this);

               layout.setOrientation(LinearLayout.VERTICAL);

             btn = new Button(this);

               btn.setBackgroundResource(R.drawable.heart);

               btn.setId(101);

               btn.setText("run");

               btn.setOnClickListener(this);

               LinearLayout.LayoutParams param =

new LinearLayout.LayoutParams(120, 55);

               param.topMargin = 10;

               layout.addView(btn, param);

               btn2 = new Button(this);

               btn.setBackgroundResource(R.drawable.heart);

               btn2.setId(102);                                   btn2.setText("exit");

               btn2.setOnClickListener(this);                layout.addView(btn2, param);

               setContentView(layout);

               //-----------------------------------

               myAdder.setListener(new myListener());

              }

publicvoid onClick(View v) {

switch(v.getId()){

case 101:

                       adder = new myAdder();

                       adder.exec();

                       setTitle("executing...");

break;

case 102:      finish();break;

               }

              }

class myListener implements IListener {

publicvoid callback() {

                setTitle(Thread.currentThread().getName() + ", sum = "

               + String.valueOf(adder.get_carry())

               + String.valueOf(adder.get_sum()));

              }

  }

}

   如果BiAdder类别与myActivity类别是由不同的人负责开发的话,上述的范例就很有参考价值了。BiAdder开发者藉由基类来封装子线程的诞生,让myActivity类别的开发变得简单了

EE                                                                  EE

034349424.png


你可能感兴趣的:(架���O�,EIT造形,A段架��)