前言:
接口就是一种纯抽象(pure abstract)的基类,而实作(Implement)接口就是继承纯抽象(pure abstract)的基类。一般的继承机制,让AP的类可继承框架里的基类。而样板类机制则相反,让框架的类可继承AP里的基类。于是,具有了双向的继承机制。这是杰出框架设计者,所不可或缺的两把刷子。
by 高煥堂
大家都知道AP开发者实现框架里所定的接口。意味着,框架开发者订定接口给AP开发者来使用。然而,AP开发者也能订定接口来给框架开发者来使用呀!
1. 前言
在前面的文章里,例如:
<<框架的主要元素:基类与API>>
<<框架主动型API幕后的IoC机制>>
所谈到的接口都是由框架开发者所设计或制定;例如,设计基类的抽像函数,让应用程序开发者来撰写这些抽像函数的代码。也就是说,上述文章所谈的都是:
由框架开发者制定接口;
由应用程序开发者撰写代码。
在本文里,则将其颠倒过来,改由框架开发者来撰写代码;而由应用程序开发者来制定接口;此时模板(Template)技术就变得非常重要了。希望你阅读本文之后,就能活用模板来替应用程序开发者做更多的服务。也更丰富了框架的内涵了。
2. 模版介绍
模版(Template)可用来重复使用以便创造出一序列相类似的东西。当我们使用印章时,就在使用模版了﹔过年过节时,妈妈用「粿印」来做出一整笼的红龟粿,这时阿妈就在使用模版了。上述这些印章及粿印皆是日常生活中我们所常见的模版。甚至我们走在沙滩上时,我们的脚掌就是个模版,不断地造出脚印﹗上述的印章、粿印等通常是不需调整的,只需重复使用它即可了。不过,还有一种更具弹性的模版,它是可调整或抽换模版中的某些组件,例如邮局服务员使用的「邮戳」,其每天皆得抽换其日期数字,才能不断重复使用之﹔再如我们古代四大发明之一的活字版,也是可调整和抽换的活动模版。活动模版的好处有:
● 能不断重复使用 -- 例如使用活字版来复制成千上万份的报纸﹔邮差每天盖无数个戳印。
● 能弹性调整 -- 例如活字版可随时修正、调整版面、抽换字体,以应付外在环境的随时变化﹔再如邮戳能抽换其日期数字后,所以能天天使用了。
在软件方面,也有各式各样的模版,例如您已非常熟悉的「类」就是个典型的模版,可制造出一序列结构类似但内容不同的对象(又称物件)。
由于类已定义好数据项及函数,所以每个对象的数据项及函数皆相同,只有其数据值不同。就如同每个红龟粿的形状相同,但其实际原料则不同。因之,类就是对象之样模版,或称为对像模版(Object Template) ,用来制造一序列类似的对象。以此类推,可想想:类之模版是什么呢﹖在Java里,提供了类模版(Class Template)之观念,其可用来制造一序列结构类似的类。
类模版是可调整的模版,经由调整就可制造出各式各样的相似的类,然后再由类来制造一序列相似的对象。类模版将是本章所要介绍的重点。不过在介绍类模版之前,将先介绍较基本的函数模版──能用来制造一序列相似函数的活动模版。
3. 类(Class)模版
如果类之间只是数据型态不同而已,就可导出类模版。例如:
3.1 Java范例
//--- Ex01 in Java ---
class DoubleData {
private Double value;
DoubleData(Double v) { value = v; }
public Double Value() { return value; }
}
class StringData {
private String value;
StringData(String v) { value = v; }
public String Value() { return value; }
}
public class JMain {
public static void main(String[] args) {
DoubleData price = new DoubleData(78000.25);
StringData hello = new StringData("good morning");
System.out.println( price.Value() + ", " + hello.Value());
}}
这两类的数据项及函数皆相同,只是数据型态不同而已。此时就是类模版的适用场合﹗导出类模版的步骤是:
Step 1:找出相异点,如下:
Step 2:拿个较通用之名称来取代DoubleData及 StringData类名称,做为模版名称。例如,Data是个适当的名称,其意义上包括了DoubleData及StringData。
Step 3:以T 来代替Double及String型态名称。于是,得到类模版:Data<T>,兹以Java表达如下:
//--- Ex02 in Java ---
class Data<T> {
private T value;
Data(T v) { value = v; }
public T Value() { return value; }
}
public class JMain {
public static void main(String[] args) {
Data<Double> price = new Data<Double>(87000.25);
Data<String> hello = new Data<String>("good morning");
Data<Long> distance = new Data<Long>(6553500L);
System.out.println( price.Value() + ", " + hello.Value() + ", " + distance.Value());
}}
当编译到指令:
Data<Double> price = new Data<Double>(87000.25);
时,就拿 Double 来代换模版的参数──T ,于是诞生了新类:
class Data<Double> {
private Double value;
Data(Double v) {
value = v;
}
public Double Value() {
return value;
}
}
同样地,编译到指令:
Data<String> hello = new Data<String>("good morning");
时,就拿String来代换模版参数──T,于是诞生了Data<String>新类。因之,此程序编译之后,程序中含有3个类:Data<Double>、Data<String>和Data<Long>。从上所述,可归纳如下:
◎ 从实际类中,区分出其相异点。
◎ 如果类内容大同小异,只是数据型态和类名称不同时,宜用类模版。
◎ 一旦导出类模版,可依参数型态来产生各式各样之类。
3.2 C++范例
C++类模版的最简单格式是:
template <char 型态代称>
class 类名称
{ ... }
也常写成一行如下:
template <char 型态代称> class类名称
{ ... }
如此,就定义了类模版。例如:
//--- Ex03 in C++ ---
template <class T, int dv>
class Number {
T value;
public:
Number( T v = dv ) : value(v) {}
T Value() { return value; }
};
兹写个主程序:
//--- Ex04 in C++ ---
#include <iostream.h>
#include "cx10-01.h"
int main(){
const int k=10;
Number<int, 30> a, b(10);
Number<int, 5*10> c;
Number<int, 5*k> d;
cout << a.Value() << ", " << b.Value() << endl;
return(0);
}
其中,指令:
template <class T, int dv>
﹉﹉﹉ ﹉﹉
↑ ↑
│ └─(说明:dv可代表int 常数值)
└─(说明:T 可代表任何型态)
编译时,看到Number<int, 30> 就会产生一个类:
class Number<int, 30>
﹉﹉ ﹉
{ │ ╰────╮
↓ │
int value; ↓
public: ﹍
Number( int v = 30 ) : value(v) {}
int Value() { return value; }
};
接下来,看到Number<int, 5*10> 时,会诞生个类:
class Number<int, 50>
{ ...... }
最后,看到Number<int, 5*k>时,其5*k 值为50,此时Number<int, 50> 已存在了,就不再诞生类,只使用现成的 Number<int, 50>就行了。[歡迎光臨 高煥堂 網頁: http://www.cnblogs.com/myEIT/ ]
4. Android框架里的模版范例
框架设计者通常撰写一些抽象的基类(Super class)和接口(Interface),它们就成为框架的主要内涵了。基于此框架,AP开发者就能撰写子类(Subclass)来继承框架里的基类或使用其接口。上述的框架与AP之间的最常见结构。然而,一个完整的框架,常需要其它花招或更具巧思的结构。例如,模版就是一项不可或缺的结构。其必要性在于:
大家都知道AP开发者实现框架里所定的接口。意味着,框架开发者订定接口给AP开发者来使用。
然而,AP开发者也能订定接口来给框架开发者来使用呀! 只是AP开发在后,框架开发在先,框架开发者又如何使用后来才创建的接口呢? 答案就是:擅用模版。
由于框架设计在先,于是使用T来代表未知的接口,例如Android框架里的BnInterface<T>和BpInterface<T>模版。应用程序(AP)撰写在后,于是定义了接口,例如ISQRS,然后取代T,而成为BnInterface<ISQRS>和BpInterface<ISQRS>。详细实作如下:
4.1 框架里的模版写法: 以Android为例
以Android框架为例,框架里已经设计了两个模版类,如下:
template<typename INTERFACE>
class BnInterface: public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual String16 getInterfaceDescriptor() const;
protected:
virtual IBinder* onAsBinder();
};
// ----------------------------------------------------------------------
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
BpInterface(const sp<IBinder>& remote);
protected:
virtual IBinder* onAsBinder();
};
4.2 AP里的模版用法: 以Android为例
基于上述Android框架里的两个模版,AP开发者就能使用之。其使用方法是:
在AP里定义接口,如下:
// ISQRS.h
#include <utils/RefBase.h>
#include <utils/IInterface.h>
#include <utils/Parcel.h>
#ifndef ANDROID_MISOO_ISQRS_SERVICE_H
#define ANDROID_MISOO_ISQRS_SERVICE_H
namespace android {
class ISQRS: public IInterface
public:
DECLARE_META_INTERFACE(SQRS);
virtual int square(const int& n) = 0;
};
以接口来去取代模版< >里的T,如下:
class BpSQRS: public BpInterface<ISQRS>{
public:
BpSQRS(const sp<IBinder>& impl): BpInterface<ISQRS>(impl){}
virtual int square(const int& n);
};
}; // namespace android
#endif
5. 结语
接口就是一种纯抽象(pure abstract)的基类,而实作(Implement)接口就是继承纯抽象(pure abstract)的基类。BpInterface<ISQRS>意味着,框架开发者来实现AP开发者所订定的接口。也就是框架里的模版类继承了AP里的接口类。一般的继承机制,让AP的类可继承框架里的基类。而样板类机制则相反,让框架的类可继承AP里的基类。于是,具有了双向的继承机制。这是杰出框架设计者,所不可或缺的两把刷子。◆