访问者模式
1 模拟场景
假设我们要开发网口驱动程序。现有两种类型的网口:低速网口和高速网口。每种类型的网口都支持配置速率、配置模式的操作。那么我们的驱动程序如下所示:
假设以上程序已经实现了,现在因业务需要,我们要添加查询速率的操作。
那么,我们就需要修改已实现的Mac类及它的子类,给Mac类及它的子类添加GetSpeed()。修改后的驱动程序如下所示:
这种修改显然是违反了“开闭原则”。
我们有没有办法在不修改Mac类及它的子类的前提下给它们添加GetSpeed()呢?
访问者模式就提供了这样一种方法。
当然,使用访问者模式必须有一个前提,就是我们需要在设计Mac类的时候就预料到未来可能要对Mac类添加操作,并预留运行时动态添加操作的通用接口。
下面我们来从头理一下访问者模式。
2 访问者模式简介
GoF给出的访问者模式意图是:表示一个作用于某对象结构中的各元素的操作。它是你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
具体到上述场景来说:各元素就是Mac类及它的子类。新操作就是GetSpeed()。
我们把新操作定义成Visitor,每增加一个新操作就增加一个Visitor的子类。
我们在定义Mac类的时候需要预留运行时动态添加操作的通用接口Accept(Visitor)。Accept可以接收新操作。这样无论后续增加几个新操作,都可以通过Accept让新操作作用于Mac上。
下面我们来看访问者模式的具体实现。
3 使用访问者模式实现网口驱动程序
参与者
- Visitor
声明访问者可以访问哪些元素,并为可以访问的每一个元素声明一个Visitor操作。
上述场景中,可以访问的元素有两个:HighSpeedMac和LowSpeedMac。所以需要为HighSpeedMac和LowSpeedMac分别声明Visitor操作VisitLowSpeedMac()和VisitHignSpeedMac()。
- ConcreteVisitor: GetSpeedVisitor
实现由Visitor声明的操作。每添加一种操作就需要新增一个ConcreteVisitor。
我们以添加GetSpeed()为例。如果后续我们还要添加GetMode(),只需要再定义GetModeVisitor即可。
- Element: Mac
需要预留运行时动态添加操作的通用接口Accept
- ConcreteElement: LowSpeedMac、HighSpeedMac
实现ConcreteElement自身的方法及Accept。
- ObjectStruct
提供一个接口,用来枚举Element。本例中我们只会定义两个Element作为示例,所以我们就直接在main函数将Element列出来,就不定义专门的ObjectStruct了。
UML
Visitor示例代码
visitor.h
#ifndef VISITOR_H
#define VISITOR_H
#include "mac.h"
struct Visitor {
void (*VisitLowSpeedMac)(struct Visitor *this, struct Mac *lowSpeedMac);
void (*VisitHighSpeedMac)(struct Visitor *this, struct Mac *highSpeedMac);
};
#endif
GetSpeedVisitor示例代码
get_speed_visitor.h
#ifndef GET_SPEED_VISITOR_H
#define GET_SPEED_VISITOR_H
#include "visitor.h"
// 构造函数
void GetSpeedVisitor(struct Visitor *this);
// 析构函数
void _GetSpeedVisitor(struct Visitor *this);
#endif
get_speed_visitor.c
#include "get_speed_visitor.h"
#include <stddef.h>
#include <stdio.h>
static void VisitLowSpeedMac(struct Visitor *this, struct Mac *lowSpeedMac)
{
printf(" get speed for low speed mac, speed is \"%s\"\n", lowSpeedMac->speed);
}
static void VisitHighSpeedMac(struct Visitor *this, struct Mac *highSpeedMac)
{
printf(" get speed for high speed mac, speed is \"%s\"\n", highSpeedMac->speed);
}
// 构造函数
void GetSpeedVisitor(struct Visitor *this)
{
this->VisitLowSpeedMac = VisitLowSpeedMac;
this->VisitHighSpeedMac = VisitHighSpeedMac;
}
// 析构函数
void _GetSpeedVisitor(struct Visitor *this)
{
this->VisitLowSpeedMac = NULL;
this->VisitHighSpeedMac = NULL;
}
Mac示例代码
mac.h
#ifndef MAC_H
#define MAC_H
#include "visitor.h"
struct Mac {
char mode[50];
char speed[50];
// 编译时已实现的操作
void (*SetMode)(struct Mac *this, char *mode);
void (*SetSpeed)(struct Mac *this, char *speed);
// 用于在运行时扩展操作
void (*Accept)(struct Mac *this, struct Visitor *visitor);
};
#endif
LowSpeedMac示例代码
low_speed_mac.h
#ifndef LOW_SPEED_MAC_H
#define LOW_SPEED_MAC_H
#include "mac.h"
// 构造函数
void LowSpeedMac(struct Mac *this);
// 析构函数
void _LowSpeedMac(struct Mac *this);
#endif
low_speed_mac.c
#include "low_speed_mac.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
static void SetMode(struct Mac *this, char *mode)
{
strcpy(this->mode, mode);
printf(" set mode for low speed mac, mode is \"%s\"\n", mode);
}
static void SetSpeed(struct Mac *this, char *speed)
{
strcpy(this->speed, speed);
printf(" set speed for low speed mac, speed is \"%s\"\n", speed);
}
static void Accept(struct Mac *this, struct Visitor *visitor)
{
visitor->VisitLowSpeedMac(visitor, this);
}
// 构造函数
void LowSpeedMac(struct Mac *this)
{
this->mode[0] = 0;
this->speed[0] = 0;
this->SetMode = SetMode;
this->SetSpeed = SetSpeed;
this->Accept = Accept;
}
// 析构函数
void _LowSpeedMac(struct Mac *this)
{
this->mode[0] = 0;
this->speed[0] = 0;
this->SetMode = NULL;
this->SetSpeed = NULL;
this->Accept = NULL;
}
HighSpeedMac示例代码
high_speed_mac.h
#ifndef HIGH_SPEED_MAC_H
#define HIGH_SPEED_MAC_H
#include "mac.h"
// 构造函数
void HighSpeedMac(struct Mac *this);
// 析构函数
void _HighSpeedMac(struct Mac *this);
#endif
high_speed_mac.c
#include "high_speed_mac.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
static void SetMode(struct Mac *this, char *mode)
{
strcpy(this->mode, mode);
printf(" set mode for high speed mac, mode is \"%s\"\n", mode);
}
static void SetSpeed(struct Mac *this, char *speed)
{
strcpy(this->speed, speed);
printf(" set speed for high speed mac, speed is \"%s\"\n", speed);
}
static void Accept(struct Mac *this, struct Visitor *visitor)
{
visitor->VisitHighSpeedMac(visitor, this);
}
// 构造函数
void HighSpeedMac(struct Mac *this)
{
this->mode[0] = 0;
this->speed[0] = 0;
this->SetMode = SetMode;
this->SetSpeed = SetSpeed;
this->Accept = Accept;
}
// 析构函数
void _HighSpeedMac(struct Mac *this)
{
this->mode[0] = 0;
this->speed[0] = 0;
this->SetMode = NULL;
this->SetSpeed = NULL;
this->Accept = NULL;
}
客户端代码示例
#include "low_speed_mac.h"
#include "high_speed_mac.h"
#include "get_speed_visitor.h"
#include <stdio.h>
void main()
{
struct Mac lowSpeedMac;
LowSpeedMac(&lowSpeedMac);
struct Mac highSpeedMac;
HighSpeedMac(&highSpeedMac);
printf("调用低速网口编译时已实现的操作:\n");
lowSpeedMac.SetSpeed(&lowSpeedMac, "1G");
lowSpeedMac.SetMode(&lowSpeedMac, "GMII");
printf("\n");
printf("调用高速网口编译时已实现的操作:\n");
highSpeedMac.SetSpeed(&highSpeedMac, "50G");
highSpeedMac.SetMode(&highSpeedMac, "SGMII");
printf("\n");
printf("--扩展getSpeed操作--\n");
printf("\n");
struct Visitor getSpeedVisitor;
GetSpeedVisitor(&getSpeedVisitor);
printf("调用低速网口运行时扩展的操作:\n");
lowSpeedMac.Accept(&lowSpeedMac, &getSpeedVisitor);
printf("\n");
printf("调用高速网口运行时扩展的操作:\n");
highSpeedMac.Accept(&highSpeedMac, &getSpeedVisitor);
printf("\n");
}
客户端显示示例
-bash-4.2# ./test
调用低速网口编译时已实现的操作:
set speed for low speed mac, speed is "1G"
set mode for low speed mac, mode is "GMII"
调用高速网口编译时已实现的操作:
set speed for high speed mac, speed is "50G"
set mode for high speed mac, mode is "SGMII"
--扩展getSpeed操作--
调用低速网口运行时扩展的操作:
get speed for low speed mac, speed is "1G"
调用高速网口运行时扩展的操作:
get speed for high speed mac, speed is "50G"
4 使用访问者模式的限制说明
限制一:
我们在前面的描述中已经提到了其中一条限制:在设计Element的时候就预料到未来可能要对Element添加操作,并预留运行时动态添加操作的通用接口,即:Accept()。
限制二:
另外还有一条比较严苛的限制:Element有多少个子类必须要稳定。
因为在Visitor中需要给每个元素(即:Element的每个子类)声明一个Visitor操作。所以当新增一个Element的子类时,就需要修改Visitor的定义,以增加一个新的Visitor操作。这显然又违反了“开闭原则”。
所以,请大家注意:本文中的场景其实是假设Mac就只有LowSpeedMac和HighSpeedMac这两个子类的。这是一条比较严苛的限制。现实中其实Mac的子类并不稳定,因为很有可能未来需要新增超高速网口SuperHighSpeedMac。那么这种情况其实是不适用访问者模式的。