如何在非oop语言中实现“访问者”模式?

时间:2013-06-25 20:47:43

标签: design-patterns

访问者模式使用oop机制,尤其是多态。此模式在实现解析器时非常有用,解析器必须自行处理许多令牌。但对于非oop语言,例如C,解决方案是什么?我想这是一长串 switch-case 控制语句。

1 个答案:

答案 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。请注意我们如何在通用VisitorOBJVisitor_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中实现了接口,继承,虚拟(或抽象)方法和构造函数。您可以扩展这些想法以创建整个对象系统,从那里创建访问者模式是微不足道的。唯一的打嗝是,除非你坚持一个明确定义的编码和命名约定,否则你很容易陷入混乱,回调,内存泄漏等等。

快乐(小心)编码!