访问者模式利用了 oop 机制,尤其是多态性。这种模式在实现解析器时很有用,其中必须自行处理许多令牌。但是对于非 oop 语言,例如 C,解决方案是什么?我猜一长串switch-case控制语句就是为了这个目的。
1 回答
除了一些不错的类型检查和语法糖之外,OOP 语言并不比命令式语言更特别。除此之外,C 与基本的 Java 或 C++ 没有什么区别。许多类似 OOP 的特性可以(并且正在)用非 OOP 语言实现。例如,在 C 中定义struct
s 和对这些struct
s 进行操作的函数是很常见的(通常将它们作为它们的第一个参数)。虚拟或抽象方法只不过是回调。
我将在这里从 Wikipedia中窃取一个示例。假设您有一个 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
将 astruct _Visitor
作为其第一个参数。根据我们的调用约定,这个参数在语义上等同于 OOP 语言中的this
(or )。这允许我们通过在我们的文件格式特定的访问者中创建第一个字段来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
or之间自由转换_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
现在,在我们的形状构造函数中,我们可以设置适当的接受器回调。
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
}
现在,我们可以将我们的形状排列成一个层次结构,并访问每个形状,只需在我们的最高级别调用 accept 即可Group
。
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 语言实现了接口、继承、虚拟(或抽象)方法和构造函数。您可以扩展这些想法以创建整个对象系统,从那里创建访问者模式是微不足道的。唯一的问题是,除非你坚持一个明确定义的编码和命名约定,否则很容易让自己陷入回调、内存泄漏等问题。
快乐(和谨慎)编码!