私が見つけたコードに基づいて、訪問者は訪問したオブジェクトの構造を知り、必要な子を呼び出す必要があるようです。これは、訪問したクラスが変更された場合でも、訪問者が作業を継続したい場合に少し不格好に思えます。
本当の質問は次のとおりだと思います:訪問者のコードではなく、訪問したコードによって列挙が行われるパターンですか?
私が見つけたコードに基づいて、訪問者は訪問したオブジェクトの構造を知り、必要な子を呼び出す必要があるようです。これは、訪問したクラスが変更された場合でも、訪問者が作業を継続したい場合に少し不格好に思えます。
本当の質問は次のとおりだと思います:訪問者のコードではなく、訪問したコードによって列挙が行われるパターンですか?
訪問者オブジェクトは、訪問するものの構造を知る必要があります。でも大丈夫です。訪問者が訪問方法を知っているものの種類ごとに、特別な訪問操作を作成する必要があります。これにより、訪問者は実際に訪問したい量と順序を決定できます。
木があるとします。あるビジターは事前順トラバーサルを行い、あるビジターはインオーダートラバーサルを行い、さらに別のビジターはリーフノードでのみ行動するかもしれません。ビジター クラスは、ツリー クラスに変更を加えることなく、これらすべてを行うことができます。
ビジターは構造を知っていますが、ビジターが実行する操作がすべての構造を知っているとは限りません。ビジターとコマンドを組み合わせることができます。ビジター オブジェクトにコマンド オブジェクトを与えると、ビジターは訪問したものごとにコマンドを呼び出します。
単純な操作を行い、コレクションが各アイテムを操作できるようにする場合は、コレクション自体に反復子を提供する必要があります。イテレータが与えるものごとに関数を呼び出します。
ツリーのノードをさまざまな順序で反復処理する場合、ツリーは複数の反復子を提供する必要があります。ツリーがまだサポートしていない順序でノードを処理する場合は、ツリー クラスを変更する必要があります。
はい。訪問したオブジェクトは、列挙を行うことができます (つまり、必要な子を呼び出します)。これは今でも「Visitor」パターンと呼ばれています (実際、Design Patternの Visitor の最初のサンプルはこのようにしています)。私の作った例のスニペット:
public void accept(Visitor visitor) {
for (Node n : children) {
n.accept(visitor);
}
}
注:子供たちを訪問するため、私たちは言うことができませんvisitor.visit(n);
。これは、Java が (引数の実行時クラスに基づいて) メソッドを動的に選択するのではなく、メソッドを静的に (その引数のコンパイル時の型によって) 選択するためです。
Hierarchical Visitor Patternは、レベルに出入りするためのイベントを追加する別のアプローチを説明しています。関連するディスカッション ページは、訪問者またはコンテナー内にある反復の引数を示します。通常のツリーがあり、別の方法で反復する必要がある場合、私には理にかなっている外部イテレータを使用するという提案が含まれています。
私のoofRep ビジターを振り返ってみると、訪問するさまざまなクラスの一連のレベルがあり、次のようなメソッド内で反復が行われていました。
void
oofRepVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
VisitBandList(inBands);
}
void
oofRepVisitor::VisitBandList(oofRepBandList& inBands)
{
EnterLevel();
const unsigned long numBands = inBands.count();
for (unsigned long i=0; i<numBands; i++) {
oofRepBand* theBand = inBands.value(i);
assert(theBand);
VisitTypedBand(theBand);
}
LeaveLevel();
}
オーバーライド付き
void
OOF_repXMLlayoutVisitor::VisitViewHeaders(oofRepBandList& inBands)
{
oofRepStreamEnv::out() << mIdentities.getIndentString();
if (inBands.keepTogether())
oofRepStreamEnv::out() << "<header>\n";
else // default is ON, and simplifies XML
oofRepStreamEnv::out() << "<header keepTogether='false'>\n";
VisitBandList(inBands);
oofRepStreamEnv::out()
<< mIdentities.getIndentString()
<< "</header>\n";
}
簡単に言えば、訪問者パターンは列挙の方法と直交していると思います。いずれかの方法で行うことも、列挙をまったく行わないこともできます。
訪問者は、訪問した構造がどの要素で構成されているかを知る必要があると思います。車はホイールとエンジンで構成されていることを知りたいです。それらがどのように組み合わされているかを正確に知る必要はないと思います。次の例を検討してください。インサイダーは、訪問したオブジェクトの構造を知っており、列挙自体を実行します。部外者はそれを知らず、訪問したオブジェクトに列挙を委譲します。
interface Visitable {
void accept(Visitor visitor);
}
class WorkingRoom implements Visitable {
public int number;
WorkingRoom(int number) {
this.number = number;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class BossRoom implements Visitable {
public String bossName;
BossRoom(String bossName) {
this.bossName = bossName;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor{
void visit(WorkingRoom workingRoom);
void visit(BossRoom bossRoom);
void visit(Office office);
}
class Office implements Visitable{
public Visitable[] firstFloor;
public Visitable[] secondFloor;
public Visitable ceoRoom;
public Office(){
firstFloor = new Visitable[]{ new WorkingRoom(101),
new WorkingRoom(102),
new BossRoom("Jeff Atwood"),
new WorkingRoom(103)};
secondFloor = new Visitable[]{ new WorkingRoom(201),
new WorkingRoom(202),
new BossRoom("Joel Spolsky")};
ceoRoom = new BossRoom("Bill Gates");
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void showMeTheOffice(Visitor visitor, boolean sayPlease) {
// Office manager decides the order in which rooms are visited
for(int i=secondFloor.length-1; i >= 0; i--){
secondFloor[i].accept(visitor);
}
if (sayPlease){
ceoRoom.accept(visitor);
}
for (int i = 0; i < firstFloor.length; i++) {
firstFloor[i].accept(visitor);
}
}
}
class Insider implements Visitor{
public void visit(WorkingRoom workingRoom) {
System.out.println("I> This is working room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("I> Hi, "+bossRoom.bossName);
}
public void visit(Office office) {
// I know about office structure, so I'll just go to the 1st floor
for(int i=0;i<office.firstFloor.length;i++){
office.firstFloor[i].accept(this);
}
}
}
class Outsider implements Visitor{
public void visit(Office office) {
// I do not know about office structure, but I know they have a
// nice office manager
// I'll just ask to show me the office
office.showMeTheOffice(this, true);
}
public void visit(WorkingRoom workingRoom) {
System.out.println("O> Wow, room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("O> Oh, look, this is "+bossRoom.bossName);
}
}
public class Main{
public static void main(String[] args) {
Office office = new Office(); // visited structure
// visitor who knows about office structure
Insider employee = new Insider();
office.accept(employee);
System.out.println();
// visitor who does not know about exact office structure
// but knows something else
Outsider candidate = new Outsider();
office.accept(candidate);
// no enumeration at all, but still a visitor pattern
Visitable v = new BossRoom("Linus Torvalds");
v.accept(candidate);
}
}
まったく列挙せずにビジターパターンを広く使用するプロジェクトがありました。基本インターフェース Field とそれを実装する多くのクラス (StringField、NumberField など) がありました。非常に多くの場合、フィールド タイプに基づいてさまざまなことを行う必要がありました。たとえば、別の方法でレンダリングする、DB からロードする、xml にエクスポートするなどです。 . Field インターフェースでメソッドを定義することもできますが、それはプロジェクトのすべての機能と結合することになります - 貧弱なフィールドはエクスポート、インポート、html や rtf へのレンダリングなどについて知る必要があります。 Field インターフェイスを実装するクラスは時間の経過とともに変更され、新しいフィールド タイプを追加して追加するのを忘れる可能性がありました
else if (field instanceof NewlyAddedFieldType) {...}
どこか。そこで、ビジター パターンを使用することにしました。
Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);
フィールドの実装にはメソッドが必要なので
void accept(FieldVisitor visitor)
次に、Field インターフェイスの新しい実装を追加する場合は、何らかの方法で実装する必要があります。通常はそうです
visitor.visit(this);
これは新しく追加されたクラスです。これにより、追加する必要があります
void visit(NewlyAddedClass visited);
これにより、既に持っているすべての FieldVisitor 実装に実装することができます。したがって、これを行うのを忘れると、コンパイラ エラーが発生します。この場合の列挙は、もしあれば、訪問された構造とビジターの外で行われました。しかし、私はまだそれを訪問者パターンの有効なケースと考えています。実装するのは少し難しいですが、使用するのは簡単で安全です。