継承を停止するのはいつですか?


9

むかしむかし、継承についてStack Overflowで質問しました

私はチェスエンジンをOOPファッションで設計すると言った。だから私はピースの抽象クラスからすべてのピースを継承しますが、継承はまだ続きます。コードで表示させてください

public abstract class Piece
{
    public void MakeMove();
    public void TakeBackMove();
}

public abstract class Pawn: Piece {}

public class WhitePawn :Pawn {}

public class BlackPawn:Pawn {}

プログラマーは私の設計を少しエンジニアリングよりも見つけており、色付きのピースのクラスを削除し、以下のようなプロパティメンバーとしてピースの色を保持することを提案しました。

public abstract class Piece
{
    public Color Color { get; set; }
    public abstract void MakeMove();
    public abstract void TakeBackMove();
}

このようにして、ピースは彼の色を知ることができます。実装後、実装が次のようになることを確認しました。

public abstract class Pawn: Piece
{
    public override void MakeMove()
    {
        if (this.Color == Color.White)
        {

        }
        else
        {

        }
    }

    public override void TakeBackMove()
    {
        if (this.Color == Color.White)
        {

        }
        else
        {

        }
    }
}

今度は、色のプロパティが原因でifステートメントが実装されていることがわかります。継承階層に特定のカラーピースが必要だと感じます。

そのような場合、WhitePawn、BlackPawnのようなクラスを持っているでしょうか、それともColorプロパティを保持してデザインに行きますか?

このような問題がなければ、どのように設計を開始したいですか?Colorプロパティを保持するか、継承ソリューションを持っていますか?

編集:私の例が実際の例と完全に一致しない可能性があることを指定したいと思います。したがって、実装の詳細を推測する前に、問題に集中してください。

Colorプロパティを使用すると、ifステートメントで継承を使用した方がよいかどうかを確認するだけです。


3
なぜこのような作品をモデル化するのか、本当によくわかりません。MakeMove()を呼び出すと、どの移動が行われますか?私はあなたが本当にボードの状態をモデル化され始めて、あなたはそのために必要なもののクラスを参照するために必要なものでしょうね
JK。

11
あなたの基本的な誤解は、チェスでは「色」が駒ではなくプレイヤーの属性であることを理解することではないと思います。そのため、「黒人は動く」などと言います。カラーリングは、どのプレーヤーがピースを所有しているかを識別するためにあります。ポーンは、どの色がどの方向であるかに関係なく、同じ移動ルールと制限に従い、どちらの方向が「前方」であるかが決まります。
ジェームズアンダーソン

5
「いつ停止するか」の継承を理解できない場合は、完全に削除することをお勧めします。階層が明らかになった後の設計/実装/メンテナンス段階で継承を再導入できます。私はこのアプローチを使用し、魅力のように機能します
gnat

1
より困難な問題は、ピースの種類ごとにサブクラスを作成するかどうかです。ピースの種類は、ピースの色よりも行動面を決定します。
NoChance

3
ABC(Abstract Base Classes)でデザインを始めたくなった場合は、私たちの支持を得てください。ABCが実際に役立つケースは非常にまれですが、それでも、通常はインターフェイスを使用する方が便利です。代わりに。
エヴァンプライス

回答:


12

車両を含むアプリケーションを設計するとします。このようなものを作成しますか?

class BlackVehicle : Vehicle { }
class DarkBlueVehicle : Vehicle { }
class DarkGreenVehicle : Vehicle { }
// hundreds of other classes...

現実には、色はオブジェクト(車またはチェスの駒)のプロパティであり、プロパティで表す必要があります。

あるMakeMoveTakeBackMove黒人と白人のために違うのですか?まったく違います:ルールは両方のプレーヤーで同じです。つまり、これらの2つの方法は黒と白まったく同じです。つまり、不要な条件を追加しています。

一方、あなたがWhitePawnand を持っている場合、遅かれ早かれあなたが書いてもBlackPawn私は驚かないでしょう:

var piece = (Pawn)this.CurrentPiece;
if (piece is WhitePawn)
{
}
else // The pice is of type BlackPawn
{
}

またはさらに次のようなもの:

public abstract class Pawn
{
    public Color Player
    {
        get
        {
            return this is WhitePawn ? Color.White : Color.Black;
        }
    }
}

// Now imagine doing the same thing for every other piece.

4
@Freshblood directioncolorプロパティを使用するよりも、プロパティを使用して移動する方がよいと思います。色は、ロジックではなくレンダリングにのみ使用するのが理想的です。
Gyan別名Gary Buyn 2012

7
その上、ピースは同じ方向に、前方、後方、左、右に移動します。違いは、ボードのどちら側から開始するかです。道路では、反対車線の車が両方とも前進します。
12

1
@Blrfl directionプロパティはブール値であることが正しいかもしれません。何が悪いのかわかりません。上記のフレッシュブラッドのコメントまでの質問で私を混乱させたのは、彼が色に基づいて動作ロジックを変更したいと思う理由です。色が行動に影響を与えるのは意味がないので、それを理解するのはもっと難しいと思います。やりたいことは、どのプレーヤーがどの駒を所有しているかを区別し、それらを2つの別々のコレクションに入れて、プロパティや継承の必要性を回避することです。ピース自体は、彼らがどのプレイヤーに属しているかを至福に気付かない可能性があります。
Gyan別名Gary Buyn 2012

1
同じことを2つの異なる視点から見ていると思います。両面が赤と青の場合、それらの動作は、黒と白の場合とは異なりますか?色は行動の違いの原因ではないと私が言っているのはそのためです。それは微妙な違いにすぎdirectionませんが、ゲームロジックではplayerプロパティではなくプロパティを使用するのはそのためcolorです。
Gyan別名Gary Buyn 2012

1
@Freshblood- white castle's moves differ from black'sどうやって?キャスリングのことですか?キングサイドキャスティングとクイーンサイドキャスティングは、どちらも白黒で100%対称です。
Konrad Morawski、2012

4

あなたが自問すべきなのは、Pawnクラスでこれらのステートメントが本当に必要な理由です。

    if (this.Color == Color.White)
    {

    }
    else
    {
    }

次のような関数の小さなセットがあれば十分だと私はかなり確信しています

public abstract class Piece
{
    bool DoesPieceHaveSameColor(Piece otherPiece) { /*...*/   }

    Color OtherColor{get{/*...*/}}
    // ...
}

あなたのPieceクラスで、これらの関数を使用して、およびそれらのPawn他の派生物のすべての関数を実装します。これはポーンに固有であるため、ポーンの移動方向をその色で決定する関数は例外である場合がありますが、他のほとんどすべての場合、色固有のコードは必要ありません。Pieceif

もう1つの興味深い問題は、実際には、さまざまな種類のピースに対してさまざまなサブクラスを用意する必要があるかどうかです。各ピースの移動のルールは異なり、ピースの種類ごとに異なるオーバーロード関数を使用することでこれを確実に実装できるので、それは私には合理的で自然なようです。

もちろん、ピースのプロパティを移動ルールから分離することにより、これをより一般的な方法でモデル化しようとすることもできますが、これは、抽象クラスMovingRuleとサブクラス階層MovingRulePawnで終わる可能性がありMovingRuleQueenます...なぜなら、それは簡単に別の形のオーバーエンジニアリングにつながる可能性があるからです。


+1は、色固有のコードがおそらくノーノーであることを指摘しています。白と黒のポーンは、他のすべての駒と同じように、基本的に同じように動作します。
Konrad Morawski、2012

2

拡張機能がオブジェクトの外部機能に影響を与えない場合、継承はそれほど役に立ちません。

Colorプロパティは、Pieceオブジェクトの使用方法には影響しません。たとえば、すべてのピースはおそらくmoveForwardまたはmoveBackwardsメソッドを呼び出すことができますが(移動が正当でない場合でも)、Pieceのカプセル化されたロジックはColorプロパティを使用して、前方または後方への移動を表す絶対値を決定します(-1または+ 「y軸」の1)、APIに関する限り、スーパークラスとサブクラスは同一のオブジェクトです(すべて同じメソッドを公開しているため)。

個人的には、各ピースタイプを表す定数をいくつか定義してから、switchステートメントを使用して、「this」インスタンスの可能な移動を返すプライベートメソッドに分岐します。


ポーンの場合のみ、後方への移動は違法です。また、黒か白かを実際に知る必要はありません。前方と後方を区別するのに十分(かつ論理的)です。Board(例えば)クラスは、片の色に応じに、-1または+1これらの概念の変換を担当することができました。
Konrad Morawski、2012

0

個人的には何も受け継がないだろうPiece。あなたがそれをすることから何も得られないと思うから。おそらく、駒は実際にどのように動くかを気にせず、どのマスが次の移動の有効な目的地であるだけです。

たぶん、あなたはピースのタイプに利用可能な移動パターンを知っている有効な移動計算機を作成することができ、そして有効な目的の正方形のリストを取得することができます、例えば

interface IMoveCalculator
{
    List<Square> GetValidDestinations();
}

class KnightMoveCalculator : IMoveCalculator;
class QueenMoveCalculator : IMoveCalculator;
class PawnMoveCalculator : IMoveCalculator; 
// etc.

class Piece
{
    IMoveCalculator move_calculator;
public:
    Piece(IMoveCalculator mc) : move_calculator(mc) {}
}

しかし、誰がどのピースがどの移動計算機を取得するかを決定しますか?ピースの基本クラスがあり、PawnPieceがある場合は、その仮定を行うことができます。しかし、その速度では、元の質問で設計されたように、作品自体がそれがどのように動くかを実装することができます
Steven Striga

@WeekendWarrior-「個人的には何も継承しないPiece」ピースは単に動きを計算する以上の責任があるように思われます。それが、動きを別の問題として分割する理由です。どの部分がどの計算機を取得するかを決定することは、依存性注入の問題となるでしょう。例var my_knight = new Piece(new KnightMoveCalculator())
Ben Cottrell 2012

これは、フライウェイトパターンの良い候補になります。チェス盤には16のポーンがあり、それらはすべて同じ動作を共有します。この動作は、単一のフライウェイトに簡単に抽出できます。他のすべての部分についても同じことが言えます。
MattDavey

-2

この回答では、質問の作成者が「チェスエンジン」とは、単なる移動検証エンジンではなく、「チェスAI」を意味すると想定しています。

作品とその有効な動きにOOPを使用することは、2つの理由から悪い考えだと思います。

  1. チェスエンジンの強さは、ボードの操作速度に大きく依存します。たとえば、チェスプログラムボードの表現を参照してください。この記事で説明する最適化のレベルを考えると、通常の関数呼び出しは手頃な価格ではないと思います。仮想メソッドを呼び出すと、間接的なレベルが追加され、さらに高いパフォーマンスのペナルティが発生します。OOPのコストは手頃ではありません。
  2. 拡張性などのOOPの利点は、チェスがすぐに新しいピースを取得する可能性が低いため、ピースには役に立ちません。継承ベースの型階層の実行可能な代替策には、列挙型とスイッチの使用が含まれます。非常にローテクでCライクですが、パフォーマンスの面では勝てません。

タイプ階層のピースの色など、パフォーマンスの考慮事項から離れることは、私の考えでは悪い考えです。たとえば、キャプチャのために、2つのピースの色が異なるかどうかを確認する必要がある場合があります。この条件の確認は、プロパティを使用して簡単に実行できます。is WhitePiece-testsを使用してそれを行うことは、比較すると醜いでしょう。

ムーブの生成をピース固有のメソッドに移動することを回避する別の実用的な理由もあります。つまり、ルークとクイーンなど、異なるピースが共通のムーブを共有しているためです。重複するコードを回避するには、共通コードを基本メソッドに因数分解し、さらに別のコストのかかる呼び出しを行う必要があります。

そうは言っても、それがあなたが書いている最初のチェスエンジンである場合、私は間違いなく、クリーンなプログラミング原則を採用し、Craftyの作者が説明した最適化のトリックを避けることをお勧めします。チェスのルール(キャスティング、プロモーション、エンパッサン)と複雑なAIテクニック(ハッシングボード、アルファ-ベータカット)に対処することは十分に困難です。


1
チェスエンジンを速く書くのが目的ではないかもしれません。
フレッシュブラッド

5
-1。「仮説的にはパフォーマンスの問題になる可能性があるので、それは悪い」などの判断は、ほとんど常に純粋な迷信です。そして、継承とは、あなたが言っている意味での「拡張性」だけではありません。チェスのルールはそれぞれ異なるピースに適用され、メソッドの実装が異なるような種類のピースWhichMovesArePossibleは合理的なアプローチです。
Doc Brown、

2
拡張性が必要ない場合は、仮想メソッドディスパッチよりも高速であるため、スイッチ/列挙型に基づくアプローチが推奨されます。チェスエンジンはCPUを大量に消費し、最も頻繁に実行される操作(したがって、効率が重要)は、移動を実行し、それらを元に戻します。その1レベル上に、可能なすべての動きがリストされます。これも時間的に重要になると思います。実際、私はCでチェスエンジンをプログラミングしました。OOPがなくても、ボード表現の低レベルの最適化は重要でした。私を解雇するにはどのような経験が必要ですか?
ジョー

2
@Joh:私はあなたの経験を軽視しませんが、それがOPの目的ではなかったと確信しています。また、低レベルの最適化はいくつかの問題にとって重要な場合がありますが、特にこれまでパフォーマンスの問題に直面していない場合は、それらを高レベルの設計決定の基礎にすることは間違いだと考えています。私の経験では、「時期尚早な最適化がすべての悪の根源である」と言ったとき、Knuthは非常に正しかったです。
Doc Brown

1
-1私はドクに同意する必要があります。実際、OPが話しているこの「エンジン」は、2プレーヤーのチェスゲームの検証エンジンにすぎません。その場合、OOPと他のスタイルのパフォーマンスを考えるのはまったく馬鹿げています。簡単なモーメントの検証は、ローエンドのARMデバイスでもミリ秒かかります。実際に述べられている以上の要件を読まないでください。
ティモシーボールドリッジ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.