web-dev-qa-db-ja.com

instanceofの代わりにpolymorphismを使用するにはどうすればよいですか?

私は抽象的なボードゲームを作ろうとしています。ゲームでは、プレーヤーは、駒の配置、移動、回転など、1ターン以内に複数のアクションを実行することを選択できます。私の実装が悪いかどうかはわかりません。私は教授から、instanceofとifステートメントのようなものはコードのにおいの兆候であると言われましたが、これを実装する他の方法は本当に考えられません。

このようにゲームを実装する私の現在の動機は、ボードがアクションを実行できるように、さまざまなアクションをリストに動的に順序付けできるようにすることです。ただし、各アクションは異なり、実行方法も異なります。したがって、私は各アクションを任意の順序でリストし、各アクションを実行できる必要があるため、これは私が考えることができる最良の方法でした。

    public class Board {

        private Square[][] squares;

        public void execute(List<Action> actions) {
            for (Action action : actions) {
                if (action instanceof MovingAction) {
                    // 
                } else if (action instanceof RotatingAction) {
                    //
                }
            }
        }
    }


    public interface Action {
    }

    public class MovingAction implements Action {

        private Position positionOfPiece;
        private Position targetPosition;

        public Position getPositionOfPiece() {
             return positionOfPiece;
        }

        public Position getTargetPosition() {
             return targetPosition;
        }

        public MovingAction(Position positionOfPiece, Position targetPosition) {
             this.positionOfPiece = positionOfPiece;
             this.targetPosition = targetPosition;
        }

    }

    public class RotationAction implements Action {

         private Position positionOfPiece;
         private Orientation orientation;

         public RotationAction(Position positionOfPiece, Orientation orientation) {
              this.positionOfPiece = positionOfPiece;
              this.orientation = orientation;
         }

         public Position getPositionOfPiece() {
              return positionOfPiece;
         }

         public Orientation getOrientation() {
              return orientation;
         }

    }

これを実装するより良い方法はありますか、これはこれを行う最善の方法ですか?

2
Michael Newgate

instanceofチェックはコードのにおいです。基本的に、インターフェース内のすべてのロジックとその実装を維持する代わりに、単純にそれを使用する代わりに、呼び出しコードに、どのタイプのアクションがあるかを知る必要があります。どんどん明らかになっていくと思います。

最初に、アクションでインターフェースに実装する予定のメソッドを定義します。次のようになります。

_public interface Action {
    void execute(Square[][] squares);
}
_

Actionが実行することを期待するメソッドを定義することにより、単純にそれを呼び出すことができます。これで、ボードのアクション処理メソッドは次のように単純化されます。

_public void execute(List<Action> actions) {
    for (Action action : actions) {
        action.execute(squares);
    }
}
_

この時点で、あなたのActionクラスは、あなたがinstanceofに持っていたものを実装する責任があり、もしそうならそれをチェックして、仕事をしているアクションでそれを維持します。説明のために、アクションタイプの1つを以下に示します。

_public class MovingAction implements Action {

    private Position positionOfPiece;
    private Position targetPosition;

    public MovingAction(Position positionOfPiece, Position targetPosition) {
         this.positionOfPiece = positionOfPiece;
         this.targetPosition = targetPosition;
    }

    public void execute(Square[][] squares) {
         Square piece = findSquare(positionOfPiece, squares);
         Square target = findSquare(targetPosition, squares);

         target.setValue(piece.getValue());
         piece.setValue(null);
    }

    private Square findSquare(Position position, Square[][] squares) {
         // ...
    }
}
_

このアプローチにより、ボードの動作方法をまったく変更する必要なく、新しいアクションを簡単に追加できるようになりました。各アクションは完全に自己完結型です。

この時点で、上で定義したfindSquare()メソッドのようなアクションをサポートするために必要な一般的なロジックについて考えることができます。そのメソッドはおそらく純粋な関数である可能性があります(処理に必要なすべてが渡され、結果は同じ入力で同じです)。しかし、基本クラスにプッシュダウンできる共通コードがある場合もあります。基本クラスはfindSquare()関数を保持でき、すべてのアクションはすべてのクラスで再定義せずにそれを使用できます。

講師は機能性を批判していませんが、理事会がアクションの実装方法を知っている必要がない設計を使用することを提案しています。基本的に、あなたの行動は単にパッシブなデータコンテナではなく、本当にアクティブになるでしょう。また、実際に公開する必要がないため、アクションの内部状態を非表示にすることもできます。

7
Berin Loritsch