ee ee
歡迎訪問 ==>高老師的博客網頁
高煥堂:MISOO(大數據.大思考)聯盟.臺北中心和東京(日本)分社.總教練
EE EE
您好,登錄后才能下訂單哦!
前言:客端呼叫抽象類別的TemplateMethod()函數,而此TemplateMethod()函數轉而呼叫抽象類別的PrimitiveOperation()函數。由于此PrimitiveOperation()函數是抽象函數,也就是卡榫函數,于是轉而呼叫具體子類別的PrimitiveOperation()函數。其中,AbstractClass抽象類別是屬于框架,而ConcreteClass具象子類別則屬于應用程序。
ee ee
歡迎訪問 ==>高老師的博客網頁
高煥堂:MISOO(大數據.大思考)聯盟.臺北中心和東京(日本)分社.總教練
EE EE
◆ 實踐接口:擅用抽象類別(Abstract Class)
◆ 實踐框架接口:擅用Template Method設計樣式
◆ 誰來誕生應用子類別的對象呢? 答案是:框架
◆ 規劃壁虎的尾巴
◆ 設計Callback機制
◆ 由基類封裝線程(Thread)的復雜性
1. 雕龍之技#1:實踐接口:擅用抽象類別(Abstract Class)
不要期待一部汽車能在街道上,也能在沙灘上跑。
不要期待一支AP能在WinMobile上跑,也能在Android上跑。
但是,如果能隨時抽換輪胎的話,汽車就有可能。
但是,如果能隨時抽換應用子類別的話,AP就有可能。
接口的來源
為了隨時能更換輪胎,可以將一部完整的「汽車」看成一顆優質的「大理石」,然后學習偉大雕刻師羅丹的雕刻之道:“把不必要的部分去掉”。首先仔細審視一部優質的「汽車」(如同大理石),如下圖:
圖1 羅丹的雕刻之道:把「不」必要的部分去掉
由于要等客戶出現才能決定輪胎,所以客戶到來之前,先不做輪胎。于是,把輪胎(含輪轂)部分去掉,先不做輪胎,而得出「汽車×××」,如下圖所示:
圖2 挖掉輪胎,出現接口(即×××)
軟件接口的實踐:抽象類別
為了隨時能更換子類,可以將一支完整的「軟件類別」看成一顆優質的「大理石」,然后學習偉大雕刻師羅丹的雕刻之道:“把不必要的部分去掉”。首先仔細審視一個「類別」(如同大理石),如下圖:
圖3 一顆軟件大理石
由于要等客戶出現才能決定輪胎,所以客戶到來之前,先不做輪胎。于是,把輪胎(含輪轂)部分去掉,先不做輪胎,而得出「軟件接口」,如下圖所示:
圖4 挖掉小鳥之形,出現軟件接口
抽象類別之內涵:抽象函數
抽象類別含有抽象函數(Abstract Function),成為抽象類別與它的具象子類別(Concrete Class 或Subclass)之卡榫函數(Hook Function)。[歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
圖5 挖掉小鳥之形,出現軟件接口
為何叫做卡榫函數呢? 因為它是讓具體子類別來相銜接的接口處。例如,
圖6 挖掉小鳥之形,出現軟件接口
卡榫函數是抽象類別與具象子類別之間的接口。
2. 雕龍之技#2:實踐框架接口:擅用Template Method設計樣式
卡榫函數是抽象類別與具象子類別之間的接口。如果再加上抽象類別與客端(Client)之間的接口,就成為大家所熟悉的Template Method設計樣式(Pattern)了。這個樣式就是來自GoF的<<Design Patterns>>一書,此樣式如下圖所示:
圖7 GoF的Template Method樣式圖
客端呼叫抽象類別的TemplateMethod()函數,而此TemplateMethod()函數轉而呼叫抽象類別的PrimitiveOperation()函數。由于此PrimitiveOperation()函數是抽象函數,也就是卡榫函數,于是轉而呼叫具體子類別的PrimitiveOperation()函數。其中,AbstractClass抽象類別是屬于框架,而ConcreteClass具象子類別則屬于應用程序。將之對應到上述的「畫鳥」范例,得到下圖:
圖8 「畫鳥」范例的Template Method樣式
3. 雕龍之技#3:誰來誕生應用子類別的對象呢? 答案是:框架
框架開發在先,應用子類別開發在后,框架開發者事先無法預知應用子類別的名稱,那么他又如何去new一個應用子類別的對象(Object)呢?
圖9 框架誕生Bird子類別的對象
如果是Java框架,就可使用Java的CreateInstance()函數來實際誕生Bird應用子類別的對象。
4. 雕龍之技#4:規劃壁虎的尾巴
小框架終究還是要離開母框架而自主運行或移植到其它平臺上,就像一位姑娘終究要離開母親,而自主生活或嫁入婆家的。為了讓小框架擁有獨立自主的求生能力,我們應該幫她規劃好對外的結合接口。于是,母框架扮演一只惡貓,而小框架扮演一只壁虎,就可以找出壁虎的尾巴了。規劃壁虎的尾巴一直是框架設計的重要之技之一。例如,Blackjack撲克牌游戲的Android應用程序,其一般架構如下圖:
圖10 壁虎在惡貓的嘴巴里
其中,Blackjack游戲玩法的主要函數(如bj_f1()、bj_f2()等)都分散于Activity或View的子類別里。茲舉個比喻:
Activity、View等基類,如同一只貓。
bjActivity、bjView等子類別,如同貓的嘴巴。
bjActivity、bjView等子類別里的onCreate()、onDraw()等函數,如同貓的牙齒。
Blackjack游戲主要函數(如bj_f1()、bj_f2()等),如同一只壁虎的身體。
從這個比喻,可以看出來這只壁虎是很可憐的。
圖11 棄尾求生術
這樣的新架構,讓壁虎隨時可以棄尾求生,移植到別的平臺里,繼續生存下去。甚至可以成為框架的一部分,如下圖:
圖12 移到框架里(一)
圖13 移到框架里(二)
5. 雕龍之技#5:設計Callback機制
前面介紹的Template Method樣式都是將「會變」的部份委托給子類別,當有所變化時,就抽換掉子類別,換上新的子類別就行了。由于該子類別與其抽象類別之間具有「繼承」關系,所以就通稱為:繼承方式的反向控制(IoC, 或稱Callback)。如下圖:
圖14 Callback(繼承)
現在介紹另一種反向控制,則是某一個類別將「會變」的部份委托另一個類別,這兩個類別不必具有繼承關系,而只須具結合(Association)關系。我們稱此為:委托方式的反向控制(IoC, 或稱Callback)。
圖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類別時,該如何做呢?
圖16 由應用子類別誕生小(子)線程
由于BiAdder的執行時間可能超過5秒鐘,所以最好使用子線程去執行之。但是這件事情可能會困擾著myActivity類別的開發者,因為BiAdder開發者可能不同于myActivity的開發者。所以myActivity類別的開發者通常并不知道BiAdder切確的執行時間長度。在此種情況下,最佳的解決辦法是:
BiAdder開發者也提供一個基類(Super-class),例如下圖2里的Adder基類,由它來包容線程的考慮,于是myActivity類別開發者就會開心了,他不必替BiAdder執行時間長短而傷腦筋了。
圖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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。