访问者模式使用oop机制,尤其是多态。此模式在实现解析器时非常有用,解析器必须自行处理许多令牌。但对于非oop语言,例如C,解决方案是什么?我想这是一长串 switch-case 控制语句。
答案 0 :(得分:3)
OOP语言并不比命令式语言更特殊,除了一些不错的类型检查和语法糖。除此之外,没有什么可以将C与基本的Java或C ++分开。许多类似OOP的功能可以(并且)以非OOP语言实现。例如,在C中通常定义struct
和对这些struct
进行操作的函数(通常将它们作为第一个参数)。虚拟或抽象方法只不过是回调。
我要在这里从维基百科偷一个example。假设您具有要使用访客模式保存的CAD程序的3d对象层次结构。我们的对象层次结构包含直线,圆,圆弧和三角形。您希望有一个访问者用于OBJ文件,另一个访问者用于3DS文件。
首先让我们定义我们的访客结构:
typedef struct _Visitor {
void (*visitLine)(struct _Visitor *, Line *);
void (*visitCircle(struct _Visitor *, Circle *);
void (*visitArc)(struct _Visitor *, Arc *);
void (*visitTriangle)(struct _Visitor *, Triangle *);
} Visitor;
注意Visitor
中的每个回调方法如何将struct _Visitor
作为其第一个参数。根据我们的调用约定,该参数在语义上等同于OOP语言中的this
(或self
)。这允许我们使用Visitor
存储额外数据,方法是将Visitor
作为特定文件格式访问者中的第一个字段。
现在我们可以为OBJ和3DS格式定义访问者:
typedef struct _OBJVisitor {
struct _Visitor visitor;
int objSpecific1; // This is just an example, these fields can be whatever you want,
... // the key is that the struct _Visitor is the first element,
... // because this lets us freely cast between Visitor and OBJVisitor
} OBJVisitor;
typedef struct __3DSVisitor {
struct _Visitor visitor;
int _3dsSpecific1;
...
} _3DSVisitor; // C names can't start with a number...
现在,我们将为此结构定义两个构造函数,这些构造函数返回特定于其中一个结构的Visitor
。请注意我们如何在通用Visitor
和OBJVisitor
或_3DSVisitor
之间自由转换,这种跨平台转换的自由由C标准保证。
Visitor *mkOBJVisitor(int objSpecific1, ...) { // The constructor can take any other arguments as necessary
OBJVisitor* objVisitor = (OBJVisitor *) malloc(sizeof(OBJVisitor));
objVisitor->visitor.visitLine = OBJVisitor_visitLine;
objVisitor->visitor.visitCircle = OBJVisitor_visitCircle;
objVisitor->visitor.visitArc = OBJVisitor_visitArc;
objVisitor->visitor.visitTriangle = OBJVisitor_visitTriangle;
objVisitor->objSpecific1 = objSpecific1;
return (Visitor *) objVisitor;
}
void OBJVisitor_visitLine(struct _Visitor *_this, Line *line)
{
OBJVisitor *this = (OBJVisitor *) this; // This convertibility is guaranteed.
... // implementation of line serialization
}
我省略了3DS和其他OBJVisitor_*
函数的功能,因为它们都很相似。
现在,我们可以使用相同的技巧并为每个对象创建一个Acceptor
结构:
typedef struct _Acceptor {
void (*accept)(struct _Acceptor *, Visitor *);
} Acceptor;
typedef struct Circle {
Acceptor acceptor;
double radius;
double x, y;
... // Any other information you want
}
... // Define similar structures for the other shapes
现在,在我们的shape构造函数中,我们可以设置适当的acceptor回调。
Circle *mkCircle(double radius, double x, double y)
{
Circle *circle = (Circle *) malloc(sizeof(Circle));
circle->acceptor.accept = Circle_accept;
circle->radius = radius;
circle->x = x;
circle->y = y;
return circle;
}
Circle_accept
方法将在visit*
上调用相应的Visitor
函数。
void Circle_accept(struct _Acceptor *_this, Visitor *visitor)
{
visitor->visitCircle(visitor, (Circle *) _this);
}
请注意我们如何将visitor
作为第一个参数传递给visitor->visitCircle
。因为C没有内置的OOP支持,所以它不会自动传入我们调用visitCircle
方法的“对象”。但这不是一个问题,因为我们总能自己做。
现在,假设有一个组对象包含这些形状作为子对象,并且本身就是一个接受者:
typedef struct _Group {
Acceptor acceptor;
int childCount;
Acceptor **children;
} Group;
Group *mkGroup(int childCount, Acceptor** children)
{
Group *group = (Group *) malloc(sizeof(Group));
group->acceptor.accept = Group_accept;
... // Omitting code for clarity
}
现在,我们可以将我们的形状排列成一个层次结构,并访问每一个形状,只需在最高级别Group
上调用accept即可。
void Group_accept(struct _Acceptor *_this, Visitor *visitor)
{
Group *this = (Group *) this;
int i;
for(i = 0; i < this->childCount; ++i) {
this->children[i]->accept(this->children[i], visitor); // Invoke the visitor on each of our children, including any group nodes.
}
}
正如您所看到的,OOP只是命令式编程的扩展。上面,我们已经在普通的C中实现了接口,继承,虚拟(或抽象)方法和构造函数。您可以扩展这些想法以创建整个对象系统,从那里创建访问者模式是微不足道的。唯一的打嗝是,除非你坚持一个明确定义的编码和命名约定,否则你很容易陷入混乱,回调,内存泄漏等等。
快乐(小心)编码!