28

私のプロジェクトの1つでは、レコードタイプの抽象クラスを継承する2つの「データ転送オブジェクト」RecordType1とRecordType2があります。この "instanceof"オペレータの使用は悪い設計を考慮していますか?

両方のRecordTypeオブジェクトを、同じRecordProcessorクラスで "process"メソッド内で処理する必要があります。

public RecordType process(RecordType record){ 

    if (record instanceof RecordType1) 
     return process((RecordType1) record); 
    else if (record instanceof RecordType2) 
     return process((RecordType2) record); 

    throw new IllegalArgumentException(record); 
} 

public RecordType1 process(RecordType1 record){ 
    // Specific processing for Record Type 1 
} 

public RecordType2 process(RecordType2 record){ 
    // Specific processing for Record Type 2 
} 

私はスコット・マイヤーズは、以下で効果的なC++を書き込む読んだ:私の最初に考えたのは、2つの特定のプロセスメソッドに委譲を次のように汎用的な処理方法を作成することでした

「いつでもあなたオブジェクトのタイプがT1の場合はフォームのコードを記述してから、何かを実行しますが、タイプがT2の場合は他の操作を行います。

もし彼が正しければ、私は自分自身を叩いているはずです。私は実際にこれが悪いデザインであることは実際には分かりません(もちろん、誰かがRecordTypeをサブクラス化し、それを扱うジェネリックの "Process"メソッドに別の行を追加せずにRecordType3を追加しないとNPEを作成しません)理論的にはこれらのレコードで実行したい多くの異なるタイプの処理が可能なので、実際には私にはあまり意味がありません。

これは悪い設計とみなされる理由を説明し、これらのレコードを処理中のクラスに処理する責任を引き続き与える何らかの代替手段を提供できますか?

UPDATE:だけ明確にするthrow new IllegalArgumentException(record);

  • return nullを変更

    • 、シンプルRecordType.process()メソッドは十分ではない三つの理由があります:まず、処理は本当にあまりにも遠くに除去されますRecordTypeのサブクラスで独自のメソッドを使用できるようにするためです。また、理論的には異なるプロセッサによって実行されることができる、異なるタイプの処理の完全なスルーが存在する。最後に、RecordTypeは、内部で定義された最小限の状態変更メソッドを持つ単純なDTOクラスとして設計されています。
  • +1

    通常、悪い兆候です。しかし、あなたがしようとしていることを理解する必要があります。それは疑問からは分かりません。一般的には、すべてのタイプが同じ機能を持っているようですので、インターフェイスを使用することができます – Yossale

    +0

    RecordType1と2の違いは何ですか?おそらくそれらを同じインターフェースに従わせることができますか? – kba

    +1

    あなたは一般的にそれをしたくありませんが、論理が一度しか表示されていない場合は、おそらくあなたはそれを使っています。複数の場所で表現された同じ 'if(instanceof X)'ロジックを見てみると、本当に自分自身を叩かなければならないときです。 –

    答えて

    24

    典型的には、Visitorパターンがそのような場合に使用されます。コードはもう少し複雑ですが、新しいRecordTypeサブクラスを追加した後は、にはにロジックを実装する必要があります。それ以外の場合はコンパイルされません。 instanceofといっしょに、1か所か2つの場所を逃すのはとても簡単です。

    例:

    public abstract class RecordType { 
        public abstract <T> T accept(RecordTypeVisitor<T> visitor); 
    } 
    
    public interface RecordTypeVisitor<T> { 
        T visitOne(RecordType1 recordType); 
        T visitTwo(RecordType2 recordType); 
    } 
    
    public class RecordType1 extends RecordType { 
        public <T> T accept(RecordTypeVisitor<T> visitor) { 
         return visitor.visitOne(this); 
        } 
    } 
    
    public class RecordType2 extends RecordType { 
        public <T> T accept(RecordTypeVisitor<T> visitor) { 
         return visitor.visitTwo(this); 
        } 
    } 
    

    使用法(一般的な戻り値の型を注意してください):

    String result = record.accept(new RecordTypeVisitor<String>() { 
    
        String visitOne(RecordType1 recordType) { 
         //processing of RecordType1 
         return "Jeden"; 
        } 
    
        String visitTwo(RecordType2 recordType) { 
         //processing of RecordType2 
         return "Dwa"; 
        } 
    
    }); 
    

    また、私は例外をスロー推薦:

    throw new IllegalArgumentException(record); 
    

    代わりのnullたときにどちらを返しますタイプが見つかりました。

    +0

    これは有望そうです...だから、もっと複雑であるという事実を除いて、このパターンを使うことの最大のメリットは何ですか?単にそれを拡張しようとし、ケースを含めることを忘れてフレームワークを破ることができないのでしょうか?またtrzyとczteryはどこですか(ちょうど冗談)? – depthfirstdesigner

    +1

    @nomizzz:はい、このパターンは、コンパイル時にサブクラス固有のロジックを実装するよう強制します。またウィキペディアは、私が気づいていなかった別のメリットをもたらします。「*訪問者オブジェクトは州*を持つことができます。私はおそらくあなたをGoFパターンブックにリダイレクトするべきである;-)それを引用するのではなく。そして、はい、コードは少し複雑であり、ある程度は読みにくくなっています。 –

    +0

    これは 'Visitor'の本当に素晴らしい実装です。ほとんどの人は一般的な型の境界を忘れて、強い型付けを失い始めます。 –

    2

    私の提案:

    public RecordType process(RecordType record){ 
        return record.process(); 
    } 
    
    public class RecordType 
    { 
        public RecordType process() 
        { 
         return null; 
        } 
    } 
    
    public class RecordType1 extends RecordType 
    { 
        @Override 
        public RecordType process() 
        { 
         ... 
        } 
    } 
    
    public class RecordType2 extends RecordType 
    { 
        @Override 
        public RecordType process() 
        { 
         ... 
        } 
    } 
    

    あなたが実行する必要があるコードは、モデルが、あなたは、二重派遣やビジターパターンの種類を使用する必要があります(UIのように)知っているべきではない何かに結合されている場合。

    http://en.wikipedia.org/wiki/Double_dispatch

    +0

    !!!私はこれがjavaであることを認識しなかった。私は答えを変えています... – ivowiblo

    +0

    私は私の質問で議論したように、私は最初にその解決策を考えました。それにはいくつかの問題があります。 まず、RecordTypeのサブクラスで独自のメソッドを使用するには、処理が実際にはあまりにもRecordTypeから削除されています。また、理論的には異なるプロセッサによって実行されることができる、異なるタイプの処理の完全なスルーが存在する。最後に、RecordTypeは、内部で定義された最小限の状態変更メソッドを持つ単純なDTOクラスとして設計されています。 – depthfirstdesigner

    +0

    @ivowiblo OPの提案にしたがって 'RecordType'要約を残しておき、' process() 'メソッドも同様に抽象化してみませんか? – matsev

    0

    別の可能なアプローチは)プロセスを(作る(または、おそらくそれ「doSubclassProcess()」とは、物事を明確にしている場合)コール(RECORDTYPE中)抽象的、およびサブクラスの実際の実装を持っているだろう。例えば

    class RecordType { 
        protected abstract RecordType doSubclassProcess(RecordType rt); 
    
        public process(RecordType rt) { 
        // you can do any prelim or common processing here 
        // ... 
    
        // now do subclass specific stuff... 
        return doSubclassProcess(rt); 
        } 
    } 
    
    class RecordType1 extends RecordType { 
        protected RecordType1 doSubclassProcess(RecordType RT) { 
         // need a cast, but you are pretty sure it is safe here 
         RecordType1 rt1 = (RecordType1) rt; 
         // now do what you want to rt 
         return rt1; 
        } 
    } 
    

    いくつかのタイプミスがあるのを見てみてください。

    +0

    一般に、コンポジション/インターフェースは継承よりも優れていますが、それでもまともなフェアなデザイン提案です。 – kba

    +0

    これは「ちょうど1回か2回」かどうかによって異なります。その場合、私のアプローチは大丈夫ですか、「これは私たちのコードで多く発生します」ということです。その場合は、パターン(または同様のもの)が良いでしょう。 HMMは、後者を示すOPの説明を見ただけで、Visitorやインタフェースを使用する必要があります。 – user949300

    0

    デザインは目的や制約を知らずに終了する手段であり、誰かがあなたのデザインがその特定の状況に適しているかどうか、またどのように改善するかを判断することはできません。

    しかし、オブジェクト指向設計では、メソッドの実装を別のクラスに保ち、各タイプごとに別々の実装を依然として持つ標準的なアプローチはvisitor patternです。

    PS:コードレビューでは、報告するよりもむしろバグを伝播する可能性があるため、return nullにフラグを付けます。考えてみましょう:到達不能なコードパスは、未定義の動作を引き起こすのではなく例外をスローするはずです。

    0

    あなたの例のように1つのデザインが悪いと思われる場合は、訪問者パターン(該当する場合)を使用しないでください。

    もう1つは効率です。 instanceofは、等価性を使用してclassオブジェクトと比較するなどの他の手法と比較して、かなり遅いです。

    ビジターパターン、通常効果的でエレガントな解決策を使用する場合、支持classビジターインスタンス間のマッピングのためMapを使用しています。大きいif ... elseブロックでinstanceofのチェックは非常に効果がありません。

    関連する問題