前言:

接口就是一种纯抽象(pure abstract)的基类,而实作(Implement)接口就是继承纯抽象(pure abstract)的基类。一般的继承机制,让AP的类可继承框架里的基类。而样板类机制则相反,让框架的类可继承AP里的基类。于是,具有了双向的继承机制。这是杰出框架设计者,所不可或缺的两把刷子。

模版(Template)在框架API设计之妙用_高煥堂  Android  平台框架

ee                                                                        ee

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

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


EE                                                                        EE

模版(Template)在框架API设计之妙用

by 高煥堂


 大家都知道AP开发者实现框架里所定的接口。意味着,框架开发者订定接口给AP开发者来使用。然而,AP开发者也能订定接口来给框架开发者来使用呀!


1. 前言

在前面的文章里,例如:

  • <<框架的主要元素:基类与API>>

  • <<框架主动型API幕后的IoC机制>>

所谈到的接口都是由框架开发者所设计或制定;例如,设计基类的抽像函数,让应用程序开发者来撰写这些抽像函数的代码。也就是说,上述文章所谈的都是:

  • 由框架开发者制定接口;

  • 由应用程序开发者撰写代码。

在本文里,则将其颠倒过来,改由框架开发者来撰写代码;而由应用程序开发者来制定接口;此时模板(Template)技术就变得非常重要了。希望你阅读本文之后,就能活用模板来替应用程序开发者做更多的服务。也更丰富了框架的内涵了。

2. 模版介绍

     模版(Template)可用来重复使用以便创造出一序列相类似的东西。当我们使用印章时,就在使用模版了﹔过年过节时,妈妈用「粿印」来做出一整笼的红龟粿,这时阿妈就在使用模版了。上述这些印章及粿印皆是日常生活中我们所常见的模版。甚至我们走在沙滩上时,我们的脚掌就是个模版,不断地造出脚印﹗上述的印章、粿印等通常是不需调整的,只需重复使用它即可了。不过,还有一种更具弹性的模版,它是可调整或抽换模版中的某些组件,例如邮局服务员使用的「邮戳」,其每天皆得抽换其日期数字,才能不断重复使用之﹔再如我们古代四大发明之一的活字版,也是可调整和抽换的活动模版。活动模版的好处有:

● 能不断重复使用 -- 例如使用活字版来复制成千上万份的报纸﹔邮差每天盖无数个戳印。

● 能弹性调整 -- 例如活字版可随时修正、调整版面、抽换字体,以应付外在环境的随时变化﹔再如邮戳能抽换其日期数字后,所以能天天使用了。

模版(Template)在框架API设计之妙用_高煥堂  Android  平台框架_02

在软件方面,也有各式各样的模版,例如您已非常熟悉的「类」就是个典型的模版,可制造出一序列结构类似但内容不同的对象(又称物件)。

模版(Template)在框架API设计之妙用_高煥堂  Android  平台框架_03

     由于类已定义好数据项及函数,所以每个对象的数据项及函数皆相同,只有其数据值不同。就如同每个红龟粿的形状相同,但其实际原料则不同。因之,类就是对象之样模版,或称为对像模版(Object Template) ,用来制造一序列类似的对象。以此类推,可想想:类之模版是什么呢﹖在Java里,提供了类模版(Class Template)之观念,其可用来制造一序列结构类似的类。

模版(Template)在框架API设计之妙用_高煥堂  Android  平台框架_04

   类模版是可调整的模版,经由调整就可制造出各式各样的相似的类,然后再由类来制造一序列相似的对象。类模版将是本章所要介绍的重点。不过在介绍类模版之前,将先介绍较基本的函数模版──能用来制造一序列相似函数的活动模版。


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:找出相异点,如下:

模版(Template)在框架API设计之妙用_高煥堂  Android  平台框架_05


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里的基类。于是,具有了双向的继承机制。这是杰出框架设计者,所不可或缺的两把刷子。