オブジェクトをプレゼンターにマッピングするクリーンなOOP方法


13

私は、それぞれの作品は、独自のタイプ(のようであるジャワ、中(例えばチェスなど)ボードゲームを作成していますPawnRookなど)。アプリケーションのGUI部分では、これらの各部分の画像が必要です。やることは

rook.image();

UIとビジネスロジックの分離に違反しているため、作品ごとに異なるプレゼンターを作成し、作品タイプを対応するプレゼンターにマッピングします。

private HashMap<Class<Piece>, PiecePresenter> presenters = ...

public Image getImage(Piece piece) {
  return presenters.get(piece.getClass()).image();
}

ここまでは順調ですね。ただし、getClass()メソッドを呼び出すと慎重なOOPの第一人者が眉をひそめ、次のようなビジターを使用することをお勧めします。

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を新しいメソッドで更新する必要があります。私のシステムをボードゲームフレームワークとして使用して、フレームワークのユーザーがピースとそのプレゼンターの両方の実装のみを提供し、それをフレームワークにプラグインするだけの簡単なプロセスで新しいピースを追加できるようにします。私の質問:このような拡張性を可能にするinstanceofgetClass()などのないクリーンなOOPソリューションはありますか?


チェスの駒の種類ごとに独自のクラスを持つ目的は何ですか?ピースオブジェクトは、1つのピースの位置、色、およびタイプを保持しています。そのために、おそらくPieceクラスと2つの列挙(PieceColor、PieceType)が必要です。
から来る

@COMEFROMよく、明らかに異なる種類のピースには異なる動作があります。したがって、たとえばルークとポーンを区別するカスタムコードが必要です。そうは言っても、一般的には、すべてのタイプを処理し、Strategyオブジェクトを使用して動作をカスタマイズする標準のピースクラスが必要です。
ジュール

@Julesと、それ自体に動作を含むすべてのピースに個別のクラスを持たせるよりも、各ピースに戦略を立てることの利点は何でしょうか
lishaak

2
ゲームのルールを、個々のピースを表すステートフルオブジェクトから分離することの明確な利点の1つは、問題をすぐに解決できることです。ターンベースのボードゲームを実装するとき、私は通常、ルールモデルと状態モデルをまったく混ぜません。
から来た

2
ルールを分離したくない場合は、6つのPieceTypeオブジェクト(クラスではない!)で移動ルールを定義できます。とにかく、直面している問題を回避する最善の方法は、懸念を分離し、本当に有用な場合にのみ継承を使用することだと思います。
から来た

回答:


10

この種の拡張性を可能にする、instanceof、getClass()などのないクリーンなOOPソリューションはありますか?

はいあります。

これを聞かせてください。現在の例では、ピースの種類を画像にマップする方法を見つけています。作品が移動する問題をどのように解決しますか?

タイプについて尋ねるよりも強力なテクニックは、聞かないで、Tellに従うことです。各ピースが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);

ここでの考え方は、他のことについて責任を負うことを求めず、それに基づいて決定を下さないことによって、責任を負うことを避けることです。代わりに、何かについて何をすべきかを知っている何かへの参照を保持し、あなたが知っていることについて何かをするようにそれを伝えます。

これにより、ポリモーフィズムが可能になります。あなたが話していることを気にしません。あなたはそれが何を言おうと気にしません。あなたはそれがあなたがする必要があることをすることができることだけ気にします。

別々の層でこれらを保つ優れ図は、教えて-ドント・アスク以下の、そしてどのように不当層に結合しない層にあることを示し、この

ここに画像の説明を入力してください

ここでは使用していないユースケースレイヤーを追加します(もちろん追加できます)が、右下隅に表示されているのと同じパターンに従っています。

また、Presenterは継承を使用しません。構成を使用します。継承は、ポリモーフィズムを取得する最後の手段でなければなりません。私は作曲と委任の使用を支持するデザインを好みます。それはもう少しキーボード入力ですが、それははるかに強力です。


2
これはおそらく良い答えだと思いますが(+1)、あなたのソリューションがクリーンアーキテクチャの良い例であるとは確信していません。Clean Architectureが防止しようとしているのは、この種のカップリングではありませんか?そして、あなたの答えはまったく綴りません。エンティティとプレゼンターの間のマッピングは、エンティティをインスタンス化する人によって処理されるようになりました。これはおそらく代替ソリューションよりもエレガントですが、新しいエンティティが追加されたときに変更する3番目の場所です。現在は訪問者ではなく工場です。
アモン

@amonは、私が言ったように、ユースケースレイヤーがありません。テリー・プラチェットは、この種のことを「子どもたちに嘘」と呼びました。私は圧倒的な例を作らないようにしています。私がClean Architectureに来る学校に連れて行かれる必要があると思うなら、ここで私を仕事に連れて行ってください
candied_orange

申し訳ありませんが、私はあなたを学校に送りたくありません。このアーキテクチャパターンをよりよく理解したいだけです。文字通り、「クリーンアーキテクチャ」と「六角形アーキテクチャ」の概念に出会ったのは1か月も前ではありませんでした。
アモン

その場合の@amonは、ハードに見栄えをよくしてから、タスクを実行します。あなたがした場合、私はそれが大好きです。私はまだ自分自身でこの部分を理解しています。現時点では、メニュー駆動のPythonプロジェクトをこのスタイルにアップグレードする作業をしています。クリーンアーキテクチャの重要なレビューはここで見つけることができます
candied_orange

1
@lishaakのインスタンス化は主に発生する可能性があります。内側の層は外側の層を知りません。外層はインターフェースについてのみ知っています。
candied_orange

5

そのことについて何:

モデル(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)));

潜在的に成長しているクラスのセットに(画像だけでなく)情報を添付する必要がある場合、この種の問題の一般的な解決策にも興味があります。」

クラスにそれほど集中するべきではないと思います。むしろ、ビジネスオブジェクトの観点から考えます

そして一般的な解決策は、あらゆる種類のマッピングです。私見の秘trickは、そのマッピングをコードから保守しやすいリソースに移動することです。

私の例では、ことで、このマッピングを行い大会実装するのは非常に簡単で、にビュー関連の情報を追加するために回避したビジネスモデル。一方、どこにも表現されていないため、「隠された」マッピングと考えることができます。

別のオプションは、これを、マッピングを含む永続化レイヤーを含む独自のMVCレイヤーを備えた個別のビジネスケースと見なすことです。


これは、この特定のシナリオに対して非常に実用的で現実的なソリューションであると考えています。また、潜在的に成長するクラスのセットに(画像だけでなく)いくつかの情報を添付する必要がある場合、この種の問題の一般的な解決策にも興味があります。
lishaak

3
@lishaak:ここでの一般的なアプローチは、ビジネスオブジェクトに十分なメタデータを提供して、リソースまたはユーザーインターフェイス要素への一般的なマッピングを自動的に行うことです。
ドックブラウン

2

視覚的な情報を含む各ピースに対して個別のUI / viewクラスを作成します。これらの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クラスで発生する必要があることです。


OK、だから私はいくつか持っているとしましょうPiece* pPawnViewそれを表示するために作成する必要があり、RookViewまたはではないことをどのように知るのKingViewですか?または、新しいピースを作成するたびに、付随するビューまたはプレゼンターをすぐに作成する必要がありますか?これは基本的に、依存関係を逆にした@CandiedOrangeのソリューションです。その場合、PawnViewコンストラクターはa Pawn*だけでなく、を取ることもできPiece*ます。
アモン

はい、申し訳ありませんが、PawnViewコンストラクターはPawn *を取得します。また、必ずしもPawnViewとPawnを同時に作成する必要はありません。100個のポーンを持つことができるが、一度に視覚化できるのは10個だけであるゲームがあるとします。この場合、複数のポーンのポーンビューを再利用できます。
ラッセジェイコブス

そして、私は自分の2セントを分かち合いますが、@ CandiedOrangeのソリューションに同意します。
ラッセジェイコブス

0

私はこれをPiece汎用的にすることでアプローチします。そのパラメーターは、ピースのタイプを識別する列挙のタイプで、各ピースはそのようなタイプへの参照を持ちます。次に、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にピースの種類を使用してボードをパラメーター化する場合、間違った種類のピースを挿入することはできません。

第二に、おそらくより興味深いことに、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),
                    ....

(明らかに、実際の実装はそれよりも複雑である必要がありますが、うまくいけばアイデアが得られます)


0

私は実用的なプログラマーであり、クリーンアーキテクチャまたはダーティアーキテクチャが何であるかは本当に気にしません。私は要件を信じており、それは簡単な方法で処理されるべきです。

要件は、チェスアプリのロジックが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; }
}

ご覧のとおり、各デバイス(プレゼンテーションレイヤー)でプレゼンターパラメーターを異なる方法で渡す必要があります。つまり、プレゼンテーションレイヤーが各ピースの表現方法を決定します。このソリューションの何が問題になっていますか?


まず、作品はプレゼンテーション層が存在し、画像が必要であることを知っている必要があります。一部のプレゼンテーションレイヤーで画像が不要な場合はどうなりますか?次に、ピースはプレゼンターなしでは存在できないため、UIレイヤーによってインスタンス化する必要があります。ゲームがUIを必要としないサーバーで実行されると想像してください。次に、UIがないため、ピースをインスタンス化できません。
リシャーク

表現者をオプションのパラメーターとして定義することもできます。
Freshblood

0

UIとドメインロジックを完全に抽象化するのに役立つ別のソリューションがあります。ボードはUIレイヤーに公開する必要があり、UIレイヤーはピースと位置の表現方法を決定できます。

これを実現するには、Fen stringを使用できます。フェンストリングは基本的にボードの状態情報であり、現在のピースとボード上の位置を示します。したがって、ボードにはFen文字列を介してボードの現在の状態を返すメソッドがあり、UIレイヤーは必要に応じてボードを表すことができます。これが実際に現在のチェスエンジンの動作方法です。チェスエンジンはGUIのない​​コンソールアプリケーションですが、外部GUI経由で使用します。チェスエンジンは、fen文字列とチェス表記法を介してGUIと通信します。

あなたは私が新しい作品を追加したらどうなるかと尋ねていますか?チェスが新しいピースを紹介するのは現実的ではありません。それはあなたのドメインに大きな変化をもたらすでしょう。したがって、YAGNIの原則に従ってください。


まあ、このソリューションは確かに機能的であり、私はアイデアを高く評価しています。ただし、チェスに限定されています。一般的な問題を説明するために、チェスを例として使用しました(質問でそれを明確にしたかもしれません)。提案するソリューションは他のドメインでは使用できません。正しく言うと、新しいビジネスオブジェクト(ピース)でそれを拡張する方法はありません。実際、チェスをより多くの駒で伸ばす方法はたくさんあります
。--lishaak
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.