17

私は大学のどこかの段階で、使用instanceofは「最後の手段」としてのみ使用すべきであると言われました (その後、多くの場所で読みました)。これを念頭に置いて、私が持っている次のコードが最後の手段であるかどうかを誰でも知ることができます. スタック オーバーフローについて調べてみましたが、同様のシナリオを見つけることができませんでした。

private void allocateUITweenManager() {
   for(GameObject go:mGameObjects){
      if (go instanceof GameGroup) ((GameGroup) go).setUITweenManager(mUITweenManager);
   }
}

どこ

  • mGameObjects配列で、そのうちの一部のみがGameGroup型です
  • GameGroup抽象クラス のサブクラスですGameObject
  • GameGroupUITweenableメソッドを持つインターフェースを使用setUITweenManager()
  • GameObjectインターフェイスを使用しないUITweenable

GameGroup上記のコードを同じように(そしておそらくそうすべき)置き換えることができると思いますUITweenable-同じ質問をするでしょう。

これを回避する別の方法はありinstanceofますか?このコード自体は失敗することはありません (私はそう思いますよね?) が、悪い報道が行われてinstanceofいるように見えることを考えると、私がここで使用している行のどこかで OOP の重大な罪を犯したのinstanceofでしょうか?

前もって感謝します!

4

9 に答える 9

5

大学のコンパイラの授業で知りましVisitor patternたが、あなたのシナリオにも当てはまると思います。以下のコードを検討してください。

public class GameObjectVisitor {

    public boolean visit(GameObject1 obj1) { return true; }
    .
    .
    // one method for each game object
    public boolean visit(GameGroup obj1) { return true; }
}

そして、次のGameObjectようにインターフェイスにメソッドを配置できます。

public interface GameObject {

    .
    .
    public boolean visit(GameObjectVisitor visitor);
}

そして、それぞれGameObjectがこのメソッドを実装します:

public class GameGroup implements GameObject {

    .
    .
    .
    public boolean visit(GameObjectVisitor visitor) {
        visitor.visit(this);
    }
}

これは、複雑な継承階層がある場合に特に便利ですGameObject。あなたの場合、メソッドは次のようになります。

private void allocateUITweenManager() {

    GameObjectVisitor gameGroupVisitor = new GameObjectVisitor() {
        public boolean visit(GameGroup obj1) {
            obj1.setUITweenManager(mUITweenManager);
        }
    };

    for(GameObject go:mGameObjects){
      go.visit(gameGroupVisitor);
   }
}
于 2013-03-17T05:20:45.703 に答える
4

編集

のこの特定のインスタンスから自分を解放するために、ここで実行できる2つの主要なことがありますinstanceof。(しゃれ?)

  1. 私の最初の答えが示唆したように実行し、ターゲットにしているメソッドを反復しているクラスに移動します。この場合、メソッドは親オブジェクトにとって意味がなく、Tedが言ったように汚染されるため、これは理想的ではありません。

  2. 反復するオブジェクトのスコープを、ターゲットメソッドに精通しているオブジェクトだけに縮小します。これがより理想的なアプローチだと思いますが、現在のコード形式では機能しない可能性があります。

個人的にはinstanceof、疫病のように、何かを完全に見逃したような気がするので避けていますが、それが必要な場合もあります。コードがこのようにレイアウトされていて、反復しているオブジェクトのスコープを縮小する方法がない場合は、instanceofおそらく問題なく機能します。しかし、これは、ポリモーフィズムによってコードが将来どのように読みやすく維持されやすくなるかを確認する良い機会のように見えます。

コメントの整合性を維持するために、以下の元の回答を残します。

/編集

個人的には、これが使用する正当な理由ではないと思いますinstanceof。目標を達成するために、いくつかのポリモーフィズムを利用できるように思われます。

あなたはsetUITweenManager(...)の方法を作ることを考えましたGameObjectか?これを行うのは理にかなっていますか?

それが理にかなっている場合は、デフォルトの実装で何もしないGameGroupようにし、メソッドをオーバーライドして、必要なことを実行することができます。この時点で、コードは次のようになります。

private void allocateUITweenManager() {
   for(GameObject go:mGameObjects){
       go.setUITweenManager(mUITweenManager);
   }
}

これは実際のポリモーフィズムですが、現在の状況に最適なアプローチになるかどうかはわかりません。可能であれば、代わりCollectionにオブジェクトを反復処理する方が理にかなっています。UITweenable

于 2013-03-17T04:53:53.983 に答える
2

instanceofの問題は、将来のオブジェクト階層の変更に悩まされる可能性があることです。より良いアプローチは、instanceofを使用する可能性が高い場合に戦略パターンを使用することです。あなたのinstanceofで解決策を作ることは、戦略が解決しようとしている問題に陥っています:多くのifに。何人かの人はコミュニティを設立しました。反IFキャンペーンは冗談かもしれませんが、untipatternは深刻です。長期的なプロジェクトでは、10〜20レベルのif-else-ifを維持するのは苦痛かもしれません。あなたの場合、配列のすべてのオブジェクトに共通のインターフェースを作成しsetUITweenManager、インターフェースを介してそれらすべてに実装する方がよいでしょう。

interface TweenManagerAware{
   setUITweenManager(UITweenManager manager);
}
于 2013-03-17T17:38:13.953 に答える
2

instanceofが推奨されない理由は、OOP ではオブジェクトの型を外部から調べるべきではないためです。代わりに、慣用的な方法は、オーバーライドされたメソッドを使用してオブジェクト自体を動作させることです。あなたの場合、考えられる解決策の1つは、boolean setUITweenManager(...)onを定義して、特定のオブジェクトに対してマネージャーを設定できる場合GameObjectに返すことです。trueただし、このパターンが多くの場所で発生すると、最上位クラスがかなり汚染される可能性があります。したがって、時々instanceof「より少ない悪」です。

この OPP アプローチの問題点は、各オブジェクトが考えられるすべてのユース ケースを「認識」しなければならないことです。クラス階層で機能する新しい機能が必要な場合は、それをクラス自体に追加する必要があります。別のモジュールのように、別の場所に配置することはできません。これは、他の人が提案したように、訪問者パターンを使用して一般的な方法で解決できます。ビジター パターンは、オブジェクトを調べる最も一般的な方法を記述しており、ポリモーフィズムと組み合わせるとさらに便利になります。

他の言語 (特に関数型言語) は異なる原則を使用することに注意してください。考えられるすべてのアクションをどのように実行するかをオブジェクトに「知らせる」代わりに、独自のメソッドを持たないデータ型を宣言します。代わりに、それらを使用するコードは、代数データ型のパターン マッチングを使用してそれらがどのように構築されたかを調べます。私の知る限り、Java に最も近いパターン マッチングのある言語はScalaです。Scala がパターン マッチングをどのように実装するかについての興味深い論文があり、いくつかの可能なアプローチを比較しています: Matching Objects With Patterns。Burak Emir、Martin Odersky、および John Williams。

オブジェクト指向プログラミングのデータは、クラスの階層で編成されます。オブジェクト指向のパターン マッチングの問題は、この階層を外部から探索する方法です。これには通常、実行時の型によるオブジェクトの分類、メンバーへのアクセス、またはオブジェクトのグループのその他の特性の決定が含まれます。このホワイト ペーパーでは、オブジェクト指向分解、ビジター、型テスト/型キャスト、型ケース、ケース クラス、およびエクストラクタの 6 つの異なるパターン マッチング手法を比較します。これらの手法は、簡潔さ、保守性、およびパフォーマンスに関連する 9 つの基準で比較されます。この論文では、2 つの新しいパターン マッチング手法としてケース クラスとエクストラクタを紹介し、それらの組み合わせが確立されたすべての基準でうまく機能することを示しています。

要約すると、OOP ではデータ型を簡単に変更できますが (サブクラスの追加など)、新しい関数 (メソッド) を追加するには、多くのクラスを変更する必要があります。ADTを使用すると、新しい関数を簡単に追加できますが、データ型を変更するには多くの関数を変更する必要があります。

于 2013-03-17T08:34:51.283 に答える
1

同じコレクションに異なるクラスのオブジェクトを混在させることは、私にとって常に少し「怪しい」です。GameObjects の単一の Collection を複数の Collections、単なる GameObjects の 1 つ、別の UITweenables に分割することは可能ですか? (たとえば、クラスをキーとする MultiMap を使用します)。次に、次のようになります。

for (UITweenable uit : myMap.get(UITweenable.class)) {
  uit.setUITweenManager(mUITweenManager);
}

instanceofマップに挿入するときはまだ必要ですが、よりカプセル化されているため、詳細を知る必要のないクライアント コードから隠されています。

ps私はすべてのSW「ルール」について熱狂的ではありませんが、Googleの「Liskov Substitution Principle」については熱狂的です。

于 2013-03-17T05:12:51.300 に答える
0

setUITweenManager何もしGameObjectない実装で宣言できます。

UITweenableインスタンスの配列内のすべてのインスタンスの反復子を返すメソッドを作成できGameObjectます。

また、何らかの抽象化内にディスパッチを効果的に隠す他のアプローチもあります。たとえば、ビジター パターンやアダプター パターンなどです。


...ここで使用している行のどこかで、OOP の重大な罪を犯しinstanceofましたか?

そうではありません(IMO)。

の最悪の問題instanceofは、実装クラスのテストに使用し始めるときです。特に悪い理由は、余分なクラスなどを追加するのが難しくなることです。ここでは、設計にとってより基本的なように見えるinstanceof UITweenableため、その問題は発生していないようです。UITweenable


この種の判断を下すときは、(おそらく) 悪い構成または使用法が悪いと主張されている理由を理解するのが最善です。次に、特定のユースケースを見て、これらの理由が当てはまるかどうか、また、別の方法がユースケースで本当に優れているかどうかを確認します。

于 2013-03-17T05:30:34.477 に答える
0

すべてのゲーム オブジェクトに対して何かを行う必要がある場合に mGameObjects コンテナーを使用し、GameGroup オブジェクト専用の別のコンテナーを保持することができます。

これにより、より多くのメモリが使用され、オブジェクトを追加/削除するときに両方のコンテナーを更新する必要がありますが、オーバーヘッドはそれほど大きくなく、すべてのオブジェクトを非常に効率的にループできます。

于 2013-03-17T06:51:23.060 に答える
0

回避する方法の別の提案がありinstanceofます。

ジェネリック ファクトリを使用している場合を除き、作成した時点で、GameObjectそれが具体的な型であることがわかります。したがってGameGroup、監視可能なオブジェクトを作成する s を渡して、それにリスナーを追加できるようにすることができます。次のように機能します。

public class Game {
    private void makeAGameGroup() {
        mGameObjects.add(new GameGroup(mUITweenManagerInformer));
    }

    private void allocateUITweenManager() {
        mUITweenManagerInformer.fire(mUITweenManager);
    }

    private class OurUITweenManagerInformer extends UITweenManagerInformer {
        private ArrayList<UITweenManagerListener> listeners;

        public void addUITweenManagerListener(UITweenManagerListener l) {
            listeners.add(l);
        }

        public void fire(UITweenManager next) {
            for (UITweenManagerListener l : listeners)
                l.changed(next);
        }
    }
    private OurUITweenManagerInformer mUITweenManagerInformer = new OurUITweenManagerInformer();
}

public interface UITweenManagerInformer {
    public void addUITweenManagerListener(UITweenManagerListener l);
}

public interface UITweenManagerListener {
    public void changed(UITweenManager next);
}

このソリューションに私を引き付けるものは次のとおりです。

  • aUITweenManagerInformerは へのコンストラクタ パラメータであるため、GameGoup渡すのを忘れることはできませんが、インスタンス メソッドでは、呼び出すのを忘れる可能性があります。

  • オブジェクトが必要とする情報 (GameGroup現在の の知識が必要な方法などUITweenManager) をコンストラクターのパラメーターとして渡す必要があることは、直感的に理解できます。私は、これらをオブジェクトが存在するための前提条件と考えるのが好きです。現在の の知識がない場合はUITweenManager、 を作成しないでくださいGameGroup。このソリューションはそれを強制します。

  • instanceof使用されることはありません。

于 2013-03-23T15:36:35.000 に答える
0

このアプローチの問題は、通常、コード内の 1 か所だけに表示されるわけではないため、将来、インターフェイスの別の実装を追加するのが多かれ少なかれ面倒になることです。それを回避するかどうかは、あなたの考慮次第です。YAGNI を適用できる場合もありますが、これが最も簡単な方法です。

ビジターパターンなど、他の人から代替案が提案されていました。

于 2013-03-17T08:59:24.773 に答える