本文实现例子主要参考了C语言面向对象编程(四):面向接口编程

考虑一个读写文件的程序,读写文件的方式和文件内容组织结构应该是解藕的。也就是说,程序不需要考虑文件内容具体是怎么组织,使用同一个方式,都可以进行读写。这个方式,就是接口

声明接口

typedef struct TB_PropagatePair_Interface_ {
  int (*writePropagatePairTo)(FILE *fl, struct TB_PropagatePair_Interface_ *pi);
  int (*readPropagatePairFrom)(FILE *fl, struct TB_PropagatePair_Interface_ *pi);
} TB_PropagatePair_Interface;

dataToBinary.h

定义了一组读写的接口,接口函数的参数只有两个:

  1. 文件指针
  2. 指向接口本身的指针

实现接口

另用一组头文件和源文件: dataToBinary_p.h/.c,真正实现接口。包括文件内容的真正组织结构,以及读写的具体实现。

#define u32 unsigned int

typedef struct TB_PropagatePair_ {
  TB_PropagatePair_Interface ppi;   // contain an complete interface
  u32 srcAddr;
  u32 srcVal;
  u32 dstAddr;
  u32 dstVal;
} TB_PropagatePair ;

TB_PropagatePair *
newPropagatePair(u32 srcAddr, u32 srcVal, u32 dstAddr, u32 dstVal);
...

dataToBinary_p.h

TB_PropagatePair是真正的文件内容组织结构:包含了一个接口,和四个u32类型的变量。注意这里包含的是完整的TB_PropagatePair_Interface接口,而非仅仅是指针。

在dataToBinary_p.c中,有真正实现读写接口的代码:

static int readPropagatePairFrom_(FILE *fl, TB_PropagatePair_Interface *pi);
static int writePropagatePairTo_(FILE *fl, TB_PropagatePair_Interface *pi);
...

static int
writePropagatePairTo_(FILE *fl, TB_PropagatePair_Interface *pi)
{
  TB_PropagatePair *pp = (TB_PropagatePair *)pi;
  size_t sz = sizeof(TB_PropagatePair) - sizeof(TB_PropagatePair_Interface);

  if(fwrite(&(pp->srcAddr), sz, 1, fl) > 0) { // exclude the TB_PropagatePair_Interface
    printf("write to binary:%u bytes\n", sz);
    return 0;
  }
  else
    return 1;
}
...

dataToBinary_p.c

注意实现接口的代码,例如writePropagatePairTo_(),参数是TB_PropagatePair_Interface接口的指针,但是在函数内部却直接把接口指针转换为实现的指针:

TB_PropagatePair *pp = (TB_PropagatePair *)pi;

这是一个关键的trick之一。

而在newPropagatePair()中,

TB_PropagatePair *
newPropagatePair(u32 srcAddr, u32 srcVal, u32 dstAddr, u32 dstVal)
{
  TB_PropagatePair *pp = calloc(1, sizeof(TB_PropagatePair));
  assert(pp != NULL);

  pp->ppi.readPropagatePairFrom = readPropagatePairFrom_;
  pp->ppi.writePropagatePairTo  = writePropagatePairTo_;

  pp->srcAddr   = srcAddr;
  pp->srcVal    = srcVal;
  pp->dstAddr   = dstAddr;
  pp->dstVal    = dstVal;

  return pp;
}

newPropagatePair()创建一个新的TB_PropagatePair。注意在分配了内存之后,首先就是要把实现接口的方法赋值给接口中的函数指针:

pp->ppi.readPropagatePairFrom = readPropagatePairFrom_;
pp->ppi.writePropagatePairTo  = writePropagatePairTo_;

这样,在调用接口的时候,才知道真正要执行的方法是什么。

调用接口

在声明和实现接口之后,如何调用接口。

#include "dataToBinary_p.h"
#include "toBinary.h"

int main(int argc, char **argv) {

  FILE *wfl = fopen("testbin","wb");

  TB_PropagatePair *pp = newPropagatePair(0xb,0xe,0xe,0xf);
  writeToBinary(wfl, (TB_PropagatePair_Interface *)pp );

  fclose(wfl);

  FILE *rfl = fopen("testbin","rb");

  readFromBinary(wfl, (TB_PropagatePair_Interface *)pp );
  printPropagatePair(pp);

  fclose(rfl);

  delPropagatePair(&pp);

  return 0;
}

注意到在使用newPropagatePair()创建一个新的TB_PropagatePair之后,在writeToBinary()和readFromBinary()中,强制把TB_PropagatePair转换为TB_PropagatePair_Interface

writeToBinary(wfl, (TB_PropagatePair_Interface *)pp );
readFromBinary(wfl, (TB_PropagatePair_Interface *)pp );

这是实现接口编程的另一个关键trick。

而writeToBinary()和readFromBinary()实现为:

int readFromBinary(FILE *fl, TB_PropagatePair_Interface *pi);
int writeToBinary(FILE *fl, TB_PropagatePair_Interface *pi);

int
readFromBinary(FILE *fl, TB_PropagatePair_Interface *pi)
{
  return pi->readPropagatePairFrom(fl, pi);
}

int
writeToBinary(FILE *fl, TB_PropagatePair_Interface *pi)
{
  return pi->writePropagatePairTo(fl, pi);
}

直接调用接口的指针函数。

运行结果:

write to binary:16 bytes
read from binary:16 bytes
propagatepair: srcAddr:11 - srcVal:14 - dstAddr:14 - dstVal:15