假设我们是一家芯片供应商,现在新开发了一款CPU。该CPU上集成了各种总线控制器,比如:i2c、spi、usb等等。为了让客户能够顺利使用我们的CPU,我们需要给客户提供各种总线控制器配套的驱动软件。
下面以i2c为例来看看,作为芯片供应商的我们提供给客户的驱动软件长什么样子。
(PS:我假设读者对驱动软件有一定的了解,所以本文中不会再解释i2c和驱动软件的基本概念)
我们假设“i2c读操作”驱动的伪码如下:
I2cRead()
{
if (I2cAdaptorOperation()) { // 第一步
I2cClientOperation() // 第二步
}
}
I2cAdaptorOperation()
{
具体实现略
}
I2cClientOperation()
{
具体实现略
}
上面是整个“i2c读操作”驱动的伪码。上述伪码分为三部分:
第一部分是I2cAdaptorOperation()。它是对CPU中集成的i2c控制器的操作。这部分代码的实现由我们(即芯片供应商)负责。因为i2c控制器是我们实现的,所以我们才清楚i2c控制器的驱动具体怎么实现。
第二部分是I2cClientOperation()。它是对i2c外设的操作了。这部分代码的实现由客户自己负责。因为客户可能用我们的CPU对接各种i2c外设(比如i2c的rtc,i2c的led),我们根本不知道客户的外设到底是什么,所以没有办法实现这部分代码。
第三部分是I2cRead()。它是i2c读操作主流程代码。那这部分代码到底是我们负责实现,还是客户自己实现呢?这两种方式都很常见。
对于客户自己负责实现I2cRead()的方式:一般来说我们(即芯片供应商)在设计i2c控制器时就已经明确I2cRead()应该怎么实现了,而且客户常常不清楚应该怎么实现I2cRead()。所以我们一般会给客户提供I2cRead()的DEMO代码给客户参考,客户会照着DEMO代码来实现I2cRead()。
对于我们(即芯片供应商)负责实现I2cRead()的方式:由于I2cRead()要调用I2cClientOperation(),而I2cClientOperation()是由客户实现的。所以我们在实现I2cRead()时,I2cClientOperation()还没有实现。这时候我们只能定义好I2cClientOperation()的抽象接口,并在I2cRead()调用I2cClientOperation()的抽象接口,然后由客户对I2cClientOperation()的抽象接口进行重写。
这里“我们(即芯片供应商)负责实现I2cRead()的方式”使用的就是模板方法。下面我们以此为例整理下模板方法的思想和实现。
模板方法定义一个操作的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
对应到上面的例子:
I2cRead()就是算法的骨架
I2cClientOperation()就是延迟到子类中的步骤
参与者
i2c读操作主流程,其中包含流程中调用的方法的实现。但I2cClientOperation()方法需要具体类重写。
这部分由集成i2c控制器的CPU芯片供应商负责实现。
继承I2cRead,根据外设(rtc、led)的具体情况重写I2cClientOperation()。
这部分由购买CPU芯片的客户复杂实现。
UML
I2cRead示例代码
i2c_read.h
#ifndef I2C_READ_H
#define I2C_READ_H
struct I2cRead {
void (*Read)(struct I2cRead *this, char *operationCode, char *ret);
int (*AdapterOperation)(struct I2cRead *this);
void (*ClientOperation)(struct I2cRead *this, char *operationCode, char *ret);
};
// 构造函数
void I2cRead(struct I2cRead *this);
// 析构函数
void _I2cRead(struct I2cRead *this);
#endif
i2c_read.c
#include "i2c_read.h"
#include
static void Read(struct I2cRead *this, char *operationCode, char *ret)
{
if (this->AdapterOperation(this) == 0) {
this->ClientOperation(this, operationCode, ret);
}
}
static int AdapterOperation(struct I2cRead *this)
{
printf(" 执行i2c适配器操作\n");
return 0;
}
static void ClientOperation(struct I2cRead *this, char *operationCode, char *ret)
{
printf(" 请重写i2c外设操作\n");
ret[0] = '\0';
}
// 构造函数
void I2cRead(struct I2cRead *this)
{
this->Read = Read;
this->AdapterOperation = AdapterOperation;
this->ClientOperation = ClientOperation;
}
// 析构函数
void _I2cRead(struct I2cRead *this)
{
this->Read = NULL;
this->AdapterOperation = NULL;
this->ClientOperation = NULL;
}
RtcI2cRead示例代码
rtc_i2c_read.h
#include "i2c_read.h"
// 构造函数
void RtcI2cRead(struct I2cRead *this);
// 析构函数
void _RtcI2cRead(struct I2cRead *this);
rtc_i2c_read.c
#include "rtc_i2c_read.h"
#include
#include
static void ClientOperation(struct I2cRead *this, char *operationCode, char *ret)
{
printf(" 根据operatinCode执行Rtc读操作:\n");
printf(" ......\n");
printf(" 返回读Rtc的结果\n");
strcpy(ret, "时间为8:15:00\n");
}
// 构造函数
void RtcI2cRead(struct I2cRead *this)
{
I2cRead(this);
this->ClientOperation = ClientOperation;
}
// 析构函数
void _RtcI2cRead(struct I2cRead *this)
{
_I2cRead(this);
}
LedI2cRead示例代码
led_i2c_read.h
#include "i2c_read.h"
// 构造函数
void LedI2cRead(struct I2cRead *this);
// 析构函数
void _LedI2cRead(struct I2cRead *this);
led_i2c_read.c
#include "led_i2c_read.h"
#include
#include
static void ClientOperation(struct I2cRead *this, char *operationCode, char *ret)
{
printf(" 根据operatinCode执行Led读操作:\n");
printf(" ......\n");
printf(" 返回读Led的结果\n");
strcpy(ret, "Led为关\n");
}
// 构造函数
void LedI2cRead(struct I2cRead *this)
{
I2cRead(this);
this->ClientOperation = ClientOperation;
}
// 析构函数
void _LedI2cRead(struct I2cRead *this)
{
_I2cRead(this);
}
客户端代码示例
#include "led_i2c_read.h"
#include "rtc_i2c_read.h"
#include
void main()
{
char ret[100];
printf("读i2c Rtc:\n");
struct I2cRead rtcRead;
RtcI2cRead(&rtcRead);
rtcRead.Read(&rtcRead, "读时间", ret);
printf(" %s", ret);
printf("读i2c Led:\n");
struct I2cRead ledRead;
LedI2cRead(&ledRead);
rtcRead.Read(&ledRead, "读状态", ret);
printf(" %s", ret);
}
客户端显示示例
-bash-4.2# ./test
读i2c Rtc:
执行i2c适配器操作
根据operatinCode执行Rtc读操作:
......
返回读Rtc的结果
时间为8:15:00
读i2c Led:
执行i2c适配器操作
根据operatinCode执行Led读操作:
......
返回读Led的结果
Led为关