什么时候停止继承?


9

很久以前,我在Stack Overflow上问了一个关于继承的问题。

我说过我以OOP方式设计国际象棋引擎。因此,我继承了Piece抽象类的所有片段,但继承仍然存在。让我按代码显示

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();
}

这样,Piece可以知道他的颜色。实施后,我已经看到我的实施如下。

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
        {

        }
    }
}

现在,我看到keep color属性导致实现中的if语句。这让我感到我们需要继承层次结构中的特定颜色块。

在这种情况下,您将拥有诸如WhitePawn,BlackPawn之类的类,还是会通过保留Color属性来进行设计?

没有看到这样的问题,您将如何开始设计?保留Color属性还是具有继承解决方案?

编辑:我想指定我的示例可能与现实生活中的示例不完全匹配。因此,在尝试猜测实现细节之前,只需集中问题。

我实际上只是问问使用Color属性是否会导致语句更好地使用继承?


3
我真的不明白为什么您要为这样的模型建模。当您调用MakeMove()时,它将执行哪一步?我说你真的需要启动的造型板的状态,看你需要什么类为
JK。

11
我认为您的基本误解是没有意识到国际象棋中的“颜色”是玩家的真正属性,而不是棋子的属性。这就是为什么我们说“黑棋移动”等原因,这里的颜色用来识别哪个玩家拥有该棋子。棋子遵循相同的运动规则和限制,无论它是什么颜色,唯一的变化就是哪个方向是“向前”。
James Anderson

5
当您无法确定“何时停止”继承时,最好将其完全删除。您可以在以后的设计/实施/维护阶段重新引入继承,这时层次结构对您来说很明显。我用这个方法,就像一个魅力
蚊蚋

1
更具挑战性的问题是是否为每种作品类型创建一个子类。片断类型比片断颜色决定更多的行为方面。
NoChance 2012年

3
如果您想从ABC(抽象基类)开始设计,这对我们都有利,而对我们却没有帮助... ABC实际上有用的情况非常罕见,即使如此,使用接口通常更有用代替。
Evan Plaice 2012年

Answers:


12

假设您设计了一个包含车辆的应用程序。你创造这样的东西吗?

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

在现实生活中,颜色是对象(汽车或国际象棋)的一种属性,必须由一种属性表示。

MakeMoveTakeBackMove黑人和白人不同?一点都不:规则对于两个玩家都是相同的,这意味着对于黑人和白人来说,这两种方法将完全相同,这意味着您要添加不需要的条件。

另一方面,如果您有WhitePawnBlackPawn,那么您迟早会写下以下内容也不会令我感到惊讶:

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我认为拥有一个direction属性并将其用于移动比使用color属性会更好。理想情况下,颜色应仅用于渲染,而不用于逻辑。
吉安(Gyan)加里(Gary)

7
此外,作品确实朝着同一方向移动,即向前,向后,向左,向右。区别在于它们从板的哪一侧开始。在道路上,相对车道上的汽车都向前行驶。

1
@Blrfl您可能是正确的,该direction属性是一个布尔值。我看不出这是怎么回事。直到Freshblood在上面发表评论之前,我对这个问题感到困惑的是他为什么要基于颜色更改运动逻辑。我要说这很难理解,因为颜色影响行为毫无意义。如果您要做的只是区分哪个玩家拥有哪个棋子,您可以将其放入两个单独的集合中,并避免需要属性或继承。这些乐曲本身可能很幸福地没有意识到它们属于哪个玩家。
吉安(aka)加里(Gary Buy)2012年

1
我认为我们正在从两个不同的角度看待同一件事。如果两个面都是红色和蓝色,那么它们的行为是否与黑色和白色时不同?我会说不,这就是我说颜色不是任何行为差异的原因。这只是一个微妙的区别,这就是为什么我会在游戏逻辑中使用a direction或也许是player属性而不是一个color
吉安(aka)加里(Gary Buy)2012年

1
@Freshblood- white castle's moves differ from black's怎么样?你是说cast?国王侧铸件和女王侧铸件对于黑色和白色都是100%对称的。
Konrad Morawski

4

您应该问自己的是,为什么在Pawn类中确实需要这些语句:

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

    }
    else
    {
    }

我很确定拥有一小部分功能就足够了

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

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

在您的Piece类中,并使用这些函数实现中的所有功能Pawn(以及的其他派生Piece),而无需执行if上述任何声明。唯一的例外可能是根据其颜色来确定Pawn的移动方向的功能,因为这是Pawn特有的,但是在几乎所有其他情况下,您都不需要任何颜色特定的代码。

另一个有趣的问题是,您是否真的应该为不同类型的片段提供不同的子类。对我来说,这似乎是合理且自然的,因为每个棋子的移动规则是不同的,并且您可以肯定地通过为每种棋子使用不同的重载函数来实现这一点。

当然,一个可以尝试在一个更通用的方法,来模拟这种来自他们的移动规则分离片的性能,但是这可能在抽象类中结束MovingRule和一个子类层次结构MovingRulePawnMovingRuleQueen...我不会开始认为方式,因为它很容易导致另一种形式的过度工程。


+1指出特定颜色的代码可能是禁忌。白色和黑色棋子的行为基本上与其他所有棋子一样。
康拉德·莫劳斯基

2

当扩展不影响对象的外部功能时,继承没有用。

颜色属性不影响一片对象是如何使用的。例如,可以想象所有片段都可以调用moveForward或moveBackwards方法(即使该移动不合法),但是片段的封装逻辑使用Color属性确定代表向前或向后运动的绝对值(-1或+在“ y轴”上为1),就您的API而言,您的超类和子类是相同的对象(因为它们都公开了相同的方法)。

就个人而言,我将定义一些代表每种片段类型的常量,然后使用switch语句分支到私有方法中,这些私有方法返回“此”实例的可能动作。


向后移动仅在典当的情况下是非法的。而且,他们实际上不必知道他们是黑还是白-足以(且合乎逻辑)使他们区分向前还是向后。的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,则可以进行假设。但是按照这样的速度,作品本身可以按照最初的问题设计出自己的运动方式
史蒂文·斯特里加

@WeekendWarrior-“我个人根本不会继承任何东西Piece”。看起来,比起简单地计算移动量,Piece还要​​承担更多的责任,这就是将移动量分开作为单独关注点的原因。确定哪个计算器将获得哪个计算器将是依赖注入的问题,例如var my_knight = new Piece(new KnightMoveCalculator())
Ben Cottrell 2012年

这将是轻量级模式的很好的候选人。棋盘上有16个棋子,它们的行为相同。可以很容易地将此​​行为提取到单个flyweight中。其他所有部件也是如此。
MattDavey 2012年

-2

在这个答案中,我假设问题的作者称“ chess engine”为“ chess AI”,而不仅仅是移动验证引擎。

我认为对零件及其有效运动使用OOP是一个坏主意,原因有两个:

  1. 国际象棋引擎的强度在很大程度上取决于它能以多快的速度操纵棋盘,例如参见国际象棋程序棋盘表示法。考虑到本文描述的优化级别,我认为常规函数调用负担不起。调用虚拟方法将增加一个间接级别,并导致更高的性能损失。那里的OOP成本无法承受。
  2. OOP的优点(例如可扩展性)对于棋子没有用,因为国际象棋不太可能很快获得新棋子。对于基于继承的类型层次结构,可行的替代方法包括使用枚举和切换。科技含量很低,类似C,但是在性能方面却难以超越。

在我看来,放弃性能方面的考虑,包括类型层次结构中部件的颜色,是一个坏主意。您会发现自己需要检查两块是否具有不同的颜色(例如用于捕获)。使用属性可以很容易地检查此条件。is WhitePiece相比之下,使用-tests进行比较麻烦。

还有另一个实际原因,需要避免将移动生成转移到特定于片段的方法,即,不同的片段共享共同的移动,例如,车子和皇后。为了避免重复代码,您将必须将通用代码分解为基本方法,并进行另一个昂贵的调用。

话虽如此,如果这是您正在编写的第一个国际象棋引擎,我绝对建议您采用简洁的编程原则,并避免使用Crafty作者描述的优化技巧。处理国际象棋规则(铸造,晋升,传球)和复杂的AI技术(哈希板,alpha-beta剪切)的细节具有挑战性。


1
编写国际象棋引擎的速度可能不会太快。
Freshblood 2012年

5
-1。恕我直言,像“假设它可能会成为性能问题,所以很糟糕”这样的判断几乎都是纯迷信。在您提到继承的意义上,继承不仅仅是“可扩展性”。不同的国际象棋规则适用于不同的棋子,并且每种棋子对方法的不同实现WhichMovesArePossible都是一种合理的方法。
Doc Brown

2
如果您不需要可扩展性,则基于switch / enum的方法是可取的,因为它比虚拟方法的调度要快。国际象棋引擎占用大量CPU,最经常执行的操作(因此对效率至关重要)正在执行移动并将其撤消。列出所有可能的动作仅比其高一个级别。我希望这也将是时间紧迫的。事实是,我已经用C语言编写了象棋引擎。即使没有OOP,对棋盘表示的低级优化也很重要。您需要解雇我的什么样的经历?
2012年

2
@Joh:我不会不尊重你的经历,但是我很确定那不是OP所追求的。尽管低级优化对于某些问题可能很重要,但我认为将它们作为高级设计决策的基础是错误的,尤其是当到目前为止还没有遇到任何性能问题时。我的经验是,Knuth说“过早的优化是万恶之源”时是非常正确的。
Doc Brown

1
-1我必须同意Doc。事实是,OP所谈论的“引擎”可能只是2人棋牌游戏的验证引擎。在那种情况下,考虑OOP与任何其他样式的性能是完全荒谬的,即使在低端ARM设备上,力矩的简单验证也将花费毫秒。不要比实际陈述更多地了解需求。
蒂莫西·鲍德里奇
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.