Visitor

パターンの目的

p.353,line.3-5: あるオブジェクト構造上の要素で実行されるオペレーションを表現する。 Visitorパターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに新しいオペレーションを定義することができるようになる。

GoF パターンのクラス図

structure-08-05:visitor

MixJuice 版 Visitor (改善1)

解決される GoF パターンの問題点

情報隠蔽の問題点

p.359,line.5-8: 6.カプセル化を破る。visitorによるアプローチでは、ConcreteElementクラスのインターフェイスが、visitorが仕事を行うのに十分、強力であることを仮定している。その結果、このパターンでは要素の内部状態にアクセスする公開オペレーションを提供するように強いられることがしばしばある。したがって、カプセル化に対して妥協を与えることになるかもしれない。

対策

ConcreteElement クラスの公開オペレーションと内部状態へのアクセスオペレーションをそれぞれ仕様モジュールと実装モジュールに分離する。この対策は GoF パターンの場合でも別解の場合でも適用できる。

結果

ConcreteElement の内部状態へのアクセス方法を、公開オペレーションとは分離できる。しかし、 ConcreteElement の追加、アルゴリズムの追加のいずれの場合も補完モジュールが必要になる。

構造

structure-08-07:visitor

module element {
  define abstract class Element {
    define abstract void accept(Visitor visitor);
  }
  define class ConcreteElementA extends Element {
    void accept(Visitor v){ v.visitConcreteElementA(this); }
  }
  define class ConcreteElementB extends Element {
    void accept(Visitor v){ v.visitConcreteElementB(this); }
  }
  define abstract class Visitor {
    define abstract void visitConcreteElementA(ConcreteElementA a);
    define abstract void visitConcreteElementB(ConcreteElementB a);
  }
}
module element.implementation extends element {
  class ConcreteElementA {
    int state;
  }
  class ConcreteElementB {
    int state;
  }
}
module visitor extends element {
  define class ConcreteVisitor extends Visitor {}
}
module visitor.implementation
  extends visitor, element.implementation {
  class ConcreteVisitor {
    void visitConcreteElementA(ConcreteElementA a){
      ... int x = a.state; ...
    }
    void visitConcreteElementB(ConcreteElementB b){
      ... int x = b.state; ...
    }
  }
}

MixJuice 版 Visitor (改善2)

解決される GoF パターンの問題点

導入可能性の問題点
既存のデータ構造があっても、 accept メソッドが実装されていなければ Visitor パターンを使うことはできない。
拡張性の問題点

p.358 「3. 新しい ConcreteElement クラスを加えることは難しい」

p.336, line.4, 3.Adding new ConcreteElement classes is hard. The Visitor pattern makes it hard to add new subclasses of Element. Each new Concrete Element gives rise to a new abstract operation on Visitor and a corresponding implementation in every ConcreteVisitor Class.

対策

MixJuice では、メソッド追加により Visitor パターンを後から導入できる。

また、MixJuice ではアブストラクトメソッド追加により、ConcreteElement クラスを加えることができる。

結果

構造

structure-08-09:visitor2

module element {
  define abstract class Element {...}
  define class ConcreteElementA extends Element {...}
  define class ConcreteElementB extends Element {...}
}

module visitor extends element {
  class Element { define abstract void accept(Visitor v); }
  class ConcreteElementA { void accept(Visitor v) { v.visit(this); } }
  class ConcreteElementB { void accept(Visitor v) { v.visit(this); } }

  define class Visitor {
    define abstract void visit(ConcreteElementA elt);
    define abstract void visit(ConcreteElementB elt);
  }

  define class ConcreteVisitor1 extends Visitor {
    void visit(ConcreteElementA elt) {...}
    void visit(ConcreteElementB elt) {...}
  }

  define class ConcreteVisitor1a extends ConcreteVisitor1 {
    void visit(ConcreteElementA elt) {...}
    void visit(ConcreteElementB elt) {...}
  }
}

module new_concrete_element extends visitor {
  define class ConcreteElementC extends Element {
    ...
    void accept(Visitor v) { v.visit(this); }
  }

  class Visitor {
    define abstract void visit(ConcreteElementC elt);
  }

  class ConcreteVisitor1 {
    void visit(ConcreteElementC elt) {...}
  }

  class ConcreteVisitor1a {
    void visit(ConcreteElementC elt) {...}
  }
}

MixJuice 版 Visitor (別解)

解決される GoF パターンの問題点

クラス数増加の問題点
Visitor クラスおよびそのサブクラスはオペレーションを表現するものであり、これらをクラスとするのは適切ではない。したがって、これらのクラスの数だけ必要以上にクラス数が増加しているといえる。

対策

MixJuice では、Visitor パターンを使わなくても既存のモジュールを変更せずに新しいオペレーションを定義することができる。このように表現することにより、 Visitor パターンを使う場合と比較して Visitor クラスや accept メソッドが必要なくなり、クラス数・メソッド数が減少し、システムを単純化できる。

結果

Visitor パターンを使う必要がなく、Visitor クラスが必要なくなり、記述を単純にできる

ただし、ConcreteVisitor クラスの定義において継承を利用する場合は、この方法は使えない。

構造

The Visitor pattern without Visitor class

module element {
  define abstract class Element {...}
  define class ConcreteElementA extends Element {...}
  define class ConcreteElementB extends Element {...}
}

module traverser extends element {
  class Element { define abstract void traverse(); }
  class ConcreteElementA { void traverse() {...} }
  class ConcreteElementB { void traverse() {...} }
}

サンプルコード

関連するパターン


MixJuice によるデザインパターン改善カタログ


田中 哲 <akr@m17n.org>, 一杉 裕志 <y-ichisugi@aist.go.jp>