設定ファイルを生成するGUIアプリケーションに取り組んでいます。構成モデルのクラス階層があり、その階層のオブジェクトツリーをいくつかの異なるコンテキストで使用しています。現在、ビジターパターンを使用して、コンテキスト固有のコードでモデルクラスを汚染しないようにしています。
interface IConfigurationElement {
void acceptVisitor(IConfigurationElementVisitor visitor);
}
以前のバージョンでは、ビジターの代わりにinstanceof
条件のチェーンを使用していました。 2つのアプローチを比較すると、次のトレードオフが見られます。
ビジター
IConfigurationElement
を追加する方が簡単で安全です。新しい宣言をIConfigurationElementVisitor
に追加するだけで、コンパイラはすべてのビジター実装に対してエラーを生成します。 instanceof
チェーンでは、新しい構成要素で拡張する必要があるすべての場所を覚えておく必要があります。基本的にinstanceof
は、DRYの原則に違反します。これは、いくつかの場所でロジックが重複するためです。instanceof
conditionsよりもinstanceof
instanceof
の大きな利点は、その柔軟性です。たとえば、instanceof
を使用すると、IConfigurationElement
実装のさまざまなサブセットに対して特別なソリューションを定義でき、場合によっては同様に処理する必要があります。対照的に、訪問者は私に毎回各実装クラスのメソッドを実装することを強制します。この種の問題に対する一般的な解決策はありますか?どういうわけかビジターを適応させることができるので、いくつかのケースに共通のソリューションを提供できますか?
InstanceOfでビジターを使用できます
インターフェース:
interface Visitable {
void accept(Object visitor);
}
interface AVisitor {
void visitA(A a);
}
interface BVisitor {
void visitB(B b);
}
interface CVisitor {
void visitB(C c);
}
Visitables:
class C implements Visitable {
public void accept(Object visitor) {
if (visitor instanceof CVisitor) {
((BVisitor)vistor).visitC(this);
}
}
}
class B implements Visitable {
public void accept(Object visitor) {
if (visitor instanceof BVisitor) {
((BVisitor)vistor).visitB(this);
}
}
}
class A extends B implements Visitable {
public void accept(Object visitor) {
super.accept(visitor);
if (visitor instanceof AVisitor) {
((AVisitor)vistor).visitA(this);
}
}
}
訪問者:
class PrintBs implements BVisitor {
public void visitB(B b) {
system.out.println(b);
}
}
class PrintAs implements AVisitor {
public void visitA(A a) {
system.out.println(a);
}
}
class PrintCs implements CVisitor {
public void visitC(C c) {
system.out.println(c);
}
}
class PrintAsAndCs implements CVisitor, AVisitor{
public void visitA(A a) {
system.out.println(a);
}
public void visitC(C c) {
system.out.println(c);
}
}
各クラスは関連するインターフェースのみを認識しているため、新しいビジターまたはビジタブルを追加するには、そのカテゴリ(ビジター/ビジター)のすべてを変更する必要があります(ビジターの場合は何も変更する必要はありません。ビジブルの場合は新しいビジターインターフェースを作成する必要がありますが、やはり既存のオブジェクトの変更)。
このように、instanceofテストのチェーンはなく、サブセットのビジターはこのサブセット外の型について知る必要さえありません。
質問は、AがBを拡張する(そしてBもVisitableである)状況で何をするかです。その場合、super.accept(visitor)をacceptに追加するだけです(したがって、instanceof-sの短いチェーンになりますが、onlu as階層が深い限り、そしてそれが問題になるほど深くてはいけませんが、手動で全体を記述する必要はありません)。
私はいくつかの潜在的な解決策を考えることができます:
Visitor
実装でプライベートメソッドを作成し、visit
実装で複数のVisitor
メソッドを使用して、そのプライベートメソッドを呼び出します。
上記が多くの場所で繰り返される場合、Visitor
を実装し、visit
実装のサブセットを共通のprotected abstract
メソッドにリダイレクトする抽象クラスの作成を検討できます。
複数のVisitor
インターフェースを作成します。
interface AOrB extends IConfigurationElementVisitor {
<Result> Result accept(AOrBVisitor<Result> visitor);
interface AOrBVisitor<Result> {
Result visit(A a);
Result visit(B b);
}
}
interface ThisCouldBeOfTypeB extends IConfigurationElementVisitor {
<Result> Result accept(ThisCouldBeOfTypeBVisitor<Result> visitor);
interface ThisCouldBeOfTypeBVisitor<Result> {
Result visit(B b);
Result visit(ThisCouldBeOfTypeB visitable);
}
}
class A implements AOrB, ThisCouldBeOfTypeB {...}
class B implements AOrB, ThisCouldBeOfTypeB {...}
class C implements ThisCouldBeOfTypeB {...}
3はあなたが探しているものだと思います。 100%静的なポリモーフィズムであり、型が処理されない場合はコンパイラ警告が生成されます。しかし、私が考えることができる唯一の欠点は、さまざまなVisitable*
インターフェイスがある場合、Visitable*
実装の維持が複雑になる可能性があることです。
ええ、そうです。多くの構成要素に役割を割り当てることにより、それらに共通性を持たせます。あなたはinstanceofで終わるかもしれませんが、DRYの原則に違反する方法ではなく、Javaの静的型付けを回避する方法としてです。
class ConfigurationElementA implements IConfigurationElement, ICommittable {
}
class ConfigurationElementB implements IConfigurationElement, IVerifiable {
}
class Visitor {
void accept(IConfigurationElement element) {
if (element instanceof ICommittable) {
// ...
}
// Note: not a chain of instanceofs.
if (element instanceof IVerifiable) {
// ...
}
}
}
言い換えると、訪問者に構成要素を総称的に受け入れさせ、ロールを介して実装のグループに作用させることができます。構成要素を適宜モデル化することで、必要に応じて具体的にすることができます。
ここで、Martin Fowlerの RoleInterface パターンを認識できます。