私はJavaでボードゲーム(チェスなど)を作成しています。各ピースは独自のタイプです(Pawn
、Rook
など)。アプリケーションのGUI部分では、これらの各部分の画像が必要です。やっていると思うので
_rook.image();
_
uIとビジネスロジックの分離に違反しているので、作品ごとに異なるプレゼンターを作成し、ピースタイプを対応するプレゼンターにマッピングします。
_private HashMap<Class<Piece>, PiecePresenter> presenters = ...
public Image getImage(Piece piece) {
return presenters.get(piece.getClass()).image();
}
_
ここまでは順調ですね。しかし、私は賢明なOOP達人がgetClass()
メソッドを呼び出すと眉をひそめるだろうと感じ、たとえば次のようにビジターを使用することを提案します:
_class Rook extends Piece {
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitRook(this);
}
}
class ImageVisitor implements PieceVisitor<Image> {
@Override
public Image visitRook(Rook rook) {
return rookImage;
}
}
_
私はこの解決策が好きです(ありがとう、達人)が、1つの重大な欠点があります。アプリケーションに新しいピースタイプを追加するたびに、PieceVisitorを新しいメソッドで更新する必要があります。私のシステムを、フレームワークのユーザーが作品とそのプレゼンターの両方の実装のみを提供し、それをフレームワークにプラグインするだけの簡単なプロセスで新しい作品を追加できるボードゲームフレームワークとして使用したいと思います。私の質問:この種の拡張性を可能にするinstanceof
、getClass()
などのないクリーンなOOPソリューションはありますか?
この種の拡張性を可能にするクリーンなOOP instanceof、getClass()などのないソリューションがありますか?
はいあります。
これをお聞きします。現在の例では、ピースタイプを画像にマッピングする方法を見つけています。これはどのようにして駒が移動する問題を解決しますか?
タイプについて尋ねるよりも強力なテクニックは、フォローすることです 教えてください、尋ねないでください 。各ピースがPiecePresenter
インターフェースを取り、それが次のようになった場合はどうなるでしょうか。
class PiecePresenter implements PieceOutput {
BoardPresenter board;
Image pieceImage;
@Override
PiecePresenter(BoardPresenter board, Image image) {
public void display(int rank, int file) {
board.display(pieceImage, rank, file);
}
}
}
構造は次のようになります。
rookWhiteImage = new Image("Rook-White.png");
PieceOutput rookWhiteOutPort = new PiecePresenter(boardPresenter, rookWhiteImage);
PieceInput rookWhiteInPort = new Rook(rookWhiteOutPort);
board[0, 0] = rookWhiteInPort;
使用は次のようになります。
board[rank, file].display(rank, file);
ここでの考え方は、他の事柄について責任を負うことを避け、それについて質問したり、それに基づいて決定を行ったりしないことです。代わりに、何かについて何をすべきかを知っている何かへの参照を保持し、あなたが知っていることについて何かをするようにそれを伝えます。
これにより、ポリモーフィズムが可能になります。あなたが話していることを気にしません。あなたはそれが何を言っているのか気にしません。あなたはそれがあなたがする必要があることをできることを気にしています。
これらを別々のレイヤーに保持し、tell-don't-askに従い、レイヤーをレイヤー間で不当に結合しない方法を示す良い図は this です。
ここでは使用しなかったユースケースレイヤーを追加します(確かに追加できます)が、右下隅に表示されているのと同じパターンに従っています。
また、Presenterが継承を使用していないことにも気づくでしょう。構成を使用しています。継承は、ポリモーフィズムを取得するための最後の手段でなければなりません。構成と委任を使用することを好むデザインが好きです。キーボードのタイピングは少し多いですが、より強力です。
そのことについて何:
モデル(Figureクラス)には、他のコンテキストでも必要になる可能性のある共通のメソッドがあります。
interface ChessFigure {
String getPlayerColor();
String getFigureName();
}
特定の図を表示するために使用される画像は、命名スキーマによってファイル名を取得します。
King-White.png
Queen-Black.png
次に、Javaクラスに関する情報にアクセスせずに適切なイメージをロードできます。
new File(FIGURE_IMAGES_DIR,
String.format("%s-%s.png",
figure.getFigureName(),
figure.getPlayerColor)));
また、成長する可能性のあるクラスのセットに(画像だけでなく)いくつかの情報を添付する必要がある場合、この種の問題の一般的な解決策にも興味があります。」
classesに集中するべきではないと思います。むしろビジネスオブジェクトの観点から考えてください。
そして一般的な解決策は、あらゆる種類のmappingです。私見の秘訣は、そのマッピングをコードから保守しやすいリソースに移動することです。
私の例では、このマッピングをconventionで実行しています。これは実装が非常に簡単で、ビュー関連情報をbusiness modelに追加することを避けています。一方、どこにも表現されていないため、「隠された」マッピングと考えることができます。
別のオプションは、これを別のビジネスケースとして見ることです。マッピングを含む永続化レイヤーを含む独自のMVCレイヤーがあります。
私は、視覚的な情報を含む各部分に対して個別のUI /ビュークラスを作成します。これらのpieceviewクラスのすべてに、モデルの対応する部分/ビジネスのルールへのポインターが含まれています。
たとえば、ポーンを取ります:
class Pawn : public Piece {
public:
Vec2 position() const;
/**
The rest of the piece's interface
*/
}
class PawnView : public PieceView {
public:
PawnView(Piece* piece) { _piece = piece; }
void drawSelf(BoardView* board) const{
board.drawPiece(_image, _piece->position);
}
private:
Piece* _piece;
Image _image;
}
これにより、ロジックとUIを完全に分離できます。ロジックピースポインターを、ピースの移動を処理するゲームクラスに渡すことができます。唯一の欠点は、インスタンス化がUIクラスで発生する必要があることです。
私は実用的なプログラマーであり、クリーンまたはダーティーなアーキテクチャーが何であるかを本当に気にしません。私は要件を信じており、それは簡単な方法で処理されるべきです。
要件は、チェスアプリのロジックがWeb、モバイルアプリ、さらにはコンソールアプリなどのさまざまなプレゼンテーションレイヤー(デバイス)で表されるため、これらの要件をサポートする必要があることです。各デバイスで非常に異なる色、ピース画像を使用することをお勧めします。
public class Program
{
public static void Main(string[] args)
{
new Rook(new Presenter { Image = "rook.png", Color = "blue" });
}
}
public abstract class Piece
{
public Presenter Presenter { get; private set; }
public Piece(Presenter presenter)
{
this.Presenter = presenter;
}
}
public class Pawn : Piece
{
public Pawn(Presenter presenter) : base(presenter) { }
}
public class Rook : Piece
{
public Rook(Presenter presenter) : base(presenter) { }
}
public class Presenter
{
public string Image { get; set; }
public string Color { get; set; }
}
プレゼンターパラメータは、各デバイス(プレゼンテーションレイヤー)で異なる方法で渡す必要があります。つまり、プレゼンテーションレイヤーが各部分の表現方法を決定します。このソリューションの何が問題になっていますか?
私はPiece
をジェネリックにすることでこれにアプローチします。そのパラメーターは、ピースのタイプを識別する列挙型であり、各ピースはそのような1つのタイプへの参照を持っています。次に、UIは以前のように列挙からのマップを使用できます。
public abstract class Piece<T>
{
T type;
public Piece (T type) { this.type = type; }
public T getType() { return type; }
}
enum ChessPieceType { PAWN, ... }
public class Pawn extends Piece<ChessPieceType>
{
public Pawn () { super (ChessPieceType.PAWN); }
これには2つの興味深い利点があります。
まず、ほとんどの静的型付け言語に適用できます。xpectへのピースのタイプでボードをパラメーター化する場合、間違った種類のピースを挿入することはできません。
2番目に、そしておそらくもっと興味深いことに、Java(または他のJVM言語)で作業している場合、各列挙値は単なる独立したオブジェクトではなく、独自のオブジェクトを持つことができることに注意してくださいこれも、ピースタイプオブジェクトをストラテジオブジェクトとして使用して、ピースの動作を顧客に提供できることを意味します。
public class ChessPiece extends Piece<ChessPieceType> {
....
boolean isMoveValid (Move move)
{
return getType().movePatterns().contains (move.asVector()) && ....
public enum ChessPieceType {
public abstract Set<Vector2D> movePatterns();
PAWN {
public Set<Vector2D> movePatterns () {
return Util.makeSet(
new Vector2D(0, 1),
....
(明らかに実際の実装はそれよりも複雑である必要がありますが、うまくいけばあなたはアイデアを理解します)
UIとドメインロジックを完全に抽象化するのに役立つ別のソリューションがあります。ボードはUIレイヤーに公開する必要があり、UIレイヤーはピースと位置の表現方法を決定できます。
これを実現するために、 Fen string を使用できます。フェンストリングは基本的にボードの状態情報であり、現在のピースとボード上のそれらの位置を提供します。したがって、ボードにはFen文字列を介してボードの現在の状態を返すメソッドを含めることができ、UIレイヤーは必要に応じてボードを表すことができます。これが実際に現在のチェスエンジンの動作方法です。チェスエンジンはGUIのないコンソールアプリケーションですが、外部GUIを介して使用します。チェスエンジンは、フェンストリングとチェス表記を介してGUIと通信します。
あなたは私が新しい作品を追加したらどうするのですか?チェスが新しい駒を導入するのは現実的ではありません。それはあなたのドメインに大きな変化をもたらすでしょう。 YAGNIの原則に従ってください。