0

ビジターパターンは oop メカニズム、特にポリモーフィズムを利用します。このパターンは、多数のトークンを独自に処理する必要があるパーサーの実装に役立ちます。しかし、C などの非 oop 言語の場合、解決策は何ですか? switch-case制御ステートメントの長い文字列は目的のためだと思います。

4

1 に答える 1

3

OOP 言語は、優れた型チェックとシンタックス シュガーを除けば、命令型言語ほど特別なものではありません。それ以外には、基本的な Java や C++ から C を切り離すものは何もありません。多くの OOP に似た機能は、非 OOP 言語で実装できます (実装されています)。たとえば、C では、 とstruct、これらの を操作する関数を定義するのが一般的ですstruct(通常、それらを最初の引数として使用します)。仮想メソッドまたは抽象メソッドは、コールバックにすぎません。

ここではウィキペディアからを盗みます。ビジター パターンを使用して保存する 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;

の各コールバック メソッドがどのように最初の引数としてVisitoraを受け取るかに注目してください。この引数は、呼び出し規約によると、OOP 言語の(または)struct _Visitorと意味的に同等です。これにより、ファイル形式固有の Visitor の最初のフィールドを作成することで、追加のデータを に保存できます。thisselfVisitorVisitor

これで、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これらの構造体の 1 つに固有の を返す、この構造体の 2 つのコンストラクターを定義します。ジェネリック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

これで、シェイプ コンストラクターで、適切なアクセプター コールバックを設定できます。

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 で実装しました。これらのアイデアを拡張して、オブジェクト システム全体を作成できます。そこから、ビジター パターンを作成するのは簡単です。唯一の問題は、明確に定義されたコーディングと命名規則に固執しない限り、コールバックやメモリ リークなどで簡単に混乱してしまうことです。

楽しく (そして慎重に) コーディングしてください!

于 2013-06-25T21:25:55.307 に答える