by 高煥堂
认识接口(Interface)设计
1.两种接口:主动型与被动型
就软件主板(MB)设计(开发)者而言,反向调用的接口(如<I>)能让主板获得主控权,所以又称为主动型接口或强势型接口。而正向调用的接口(如CI接口)则让子类或Client类获得主控权,所有(从主板视角而言)又称为被动型接口。
无论是主动型或被动型接口都是主板的基类(或称为父类)所提供的,但是这两种接口对于子类(或Client类)的制约能力并不相同,主动型接口让基类具有强大的制约能力(所以称为强势接口),可以主导子类的结构和行为;而被动型接口则不能带给(主板的)基类任何制约或主导力量。因此,分辨主动型与被动型API的区别,是极为重要的能力。两者可藉由3个攸关的动词来区别之:
1. 定义(Define)
2. 实现(Implement)
3. 调用(Invoke or Call)
根据这3个角度,可将接口区分为「主动型」与「被动型」两种,如下表:
兹举Android的IPC主板模式为例,这软件主板里就含有两种接口,如下图所示:
其中,Binder基类的具象函数transact()调用onTransact()抽象函数,就反向调用到子类myBinder的onTransact()函数了。关于onTransact()抽象函数:
l 定义于基类
l 实现于子类
l 让基类来调用子类的实现
l 所以,对基类而言,它属于主动型接口
关于的transact()具象函数:
l 定义于基类
l 实现于基类
l 让其它Client类(别)来调用
l 所以,对基类而言,它属于被动型接口
如下图所示:
1.2 说明两种API的制约力量
所谓被动型接口就是,相对而言,基类处于被主导(Dominated)的地位,也就是说,被子类或其它Client类所主导了。如下图:
上述execute()函数所构成的CI接口,其制约力量强弱程度比率为:
基类:子类 è 0.4 : 0.6
再看看主动型的接口,相对而言,基类处于主导(Dominate)的地位,也就是说,基类主导了子类。例如,上述onTransact()函数所构成的<I>接口,其制约力量强弱程度比率为: 就软件主板(MB)设计(开发)者而言,反向调用的接口(如<I>)能让主板获得主控权,所以又称为主动型接口或强势型接口。而正向调用的接口(如CI接口)则让子类或Client类获得主控权,所有(从主板视角而言)又称为被动型接口。
基类:子类 è 0.8 : 0.2
就此接口而言,基类具有高度的制约力量可主导子类。将上述的比率,配到上图里的主板模式里。兹进一步说明如下:
- Client调用主板的execute()具象函数,属于主板的被动型接口,主板的制约力量只有0.4(比率是0.4:0.6)。
- 主板调用子类的onTransact()函数,属于框架的主动型接口,框架的制约力量有0.8(比率是0.8:0.2)。
- 所以,整体上而言,主板拥有制高点(即主导性)。所以才称之为<主板>。
以上说明了主板的两种接口。其中的主动型接口是最为重要的,因为它是实践主板的主控权的关键所在。
1.3 演练:分辨主动型与被动型接口
1.3.1 演练(一)
许多App开发者是基于Android提供的基类来撰写App子类,然后与基类结合起来编译(Compile)和连结(Link)成为可执行的App。如下图:
在此图里可以看到,Android基类View定义了onDraw()函数,它是一个可覆写的(Overridable)函数。于是,你就可以撰写一个子类(如上图里的myView),并覆写onDraw()函数。在程序执行时,基类就能反向调用到子类的onDraw()函数了。
演练问题:关于上图的onDraw()函数,属于基类(View)的主动型接口? 还是被动型接口呢?
1.3.2 演练(二)
相信你已经扮演过上述的传统角色了。现在,您可以试试转换到一个全新的角色,飞上枝头变凤凰了。也就是从原来的App开发者,转变成为主板开发者。这个新鲜的角色就是:开发自己的主板基类和接口。首先设计一个View的子类别,设其名称为 GraphView。虽然它是View的子类别,也覆写了onDraw()函数;但是它又提供了一个可覆写的函数:doDraw();可让应用子类来覆写之。如下图所示:
上图里的GraphView基类定义了一个可覆写的doDraw()函数,它可画出背景图像。执行时,GraphView的onDraw()函数调用其doDraw()函数。由于子类覆写了doDraw()函数,所以onDraw()会转而调用BirdView子类的doDraw()函数。此时,子类的doDraw()可以先调用基类GraphView的doDraw()去先画出背景,然后返回BirdView的doDraw()函数绘出前景图像。在GraphView里,其doDraw()函数里含有指令来画背景,这就是所谓的「预设行为」,子类别的doDraw()函数只要调用它,就能画出背景了;这可以减轻子类别的负担。
演练问题:关于上图的doDraw()函数,属于GraphView的主动型接口? 还是被动型接口呢?
1.3.3 演练(三)
当然还可以设计出其它的结构,例如可以将上述的预设行为独立出来成为一个新的函数,如取名为drawBackgraound()函数。此时,doDraw()就变成一个抽象函数了。如下图所示:
演练问题:关于上图的drawBackground()函数,属于GraphView2的主动型接口? 还是被动型接口呢?
1.3.4 演练(四)
当然还可以设计出其它的结构,例如下图:
在这个结构里,是由基类GraphView2的onDraw()先调用drawBackground()函数来画出背景图像,然后才调用其doDraw()抽象函数,就转而返像调用了子类别BirdView的onDraw()函数来画出前景图像。
演练问题:关于上图的doDraw()函数,属于GraphView2的主动型接口? 还是被动型接口呢?
1.3.5 演练(五)
当然还可以设计出其它的结构,例如下图:
此结构是是由基类GraphView3的onDraw()先调用drawBackground()函数来画出背景图像,然后才调用IDraw接口的doDraw()抽象函数,就转而返像调用了子类别BirdDrawing的onDraw()函数来画出前景图像。
演练问题:关于上图的IDraw接口(内含doDraw()函数),属于GraphView3的主动型接口? 还是被动型接口呢?
1.4 将设计落实为程序码
在上一节里,我们说明了传统AP开发者的则职责就是设计应用子类别。于此,我们来复习一下传统的角色,实际从基类View衍生出一个应用子类:myView,并写出其程序码。接下来的本节里,我们将变换一个角色,从传统的AP开发者摇身一变而成为框架的设计者或开发者。于是,就必须自己来设计框架的基类和接口了。
l 设计架构图
这个程序的结构,就如下图所示:
这个范例共含三层,其中包括:两层框架和一层AP。上层框架是Google所开发的,而第二层框架是我们所开发的。上层框架里的View基类会反向调用到GraphView的onDraw()函数,接着onDraw()先调用自己的drawBackground()函数去绘制背景图案(本范例是绘出空白背景),然后调用IDraw接口的doDraw()函数,汇出一只蓝色的精灵飞侠。如下图所示:
l 撰写程序码
首先建立一个Android的项目,如下:
★ 撰写你的基类和接口
// GraphView.java
package Framework;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;
publicclass GraphView extends View{
private IDraw fgDrawer;
public GraphView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.drawBackground(canvas);
fgDrawer.doDraw(canvas);
}
protected void drawBackground(Canvas canvas){
canvas.drawColor(Color.WHITE);
}
public void setForegroundDrawer(IDraw fgd){
fgDrawer = fgd;
}
}
// IDraw.java
package Framework;
import android.graphics.Canvas;
publicinterface IDraw {
voiddoDraw(Canvas canvas);
}
★ 把基类和接口送人,协助别人去开发应用子类
// myDrawing.java
package com.misoo.pk001;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import Framework.IDraw;
public class myDrawing implements IDraw {
private Paint paint;
myDrawing()
{ paint= new Paint(); }
public void doDraw(Canvas canvas) {
paint.setAntiAlias(true);
RectF rectF = new RectF(80,110,180,180);
paint.setColor(Color.BLUE);
canvas.drawArc(rectF, 220, 180,true, paint);
paint.setStrokeWidth(3);
canvas.drawLine(135,120, 140, 75, paint);
canvas.drawLine(160, 140, 210, 130, paint);
paint.setColor(Color.WHITE);
canvas.drawCircle(128, 125, 6, paint);
canvas.drawCircle(162, 145, 6, paint);
}
}
// myActivity.java
package com.misoo.pk001;
import Framework.GraphView;
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 GraphView mv = null;
private Button ibtn;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
mv = new GraphView(this);
LinearLayout.LayoutParams param =
new LinearLayout.LayoutParams(250, 280);
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(100, 65);
param1.topMargin = 10;
param1.leftMargin = 10;
layout.addView(ibtn,param1);
setContentView(layout);
//-----------------------------------------------
mv.setForegroundDrawer(new myDrawing());
}
public void onClick(View v)
{ finish(); }
}
~ End ~