使用继承和多态来解决常见的游戏问题

时间:2010-05-04 20:30:15

标签: java design-patterns oop inheritance

我有两节课;让我们称他们为食人魔和巫师。 (所有字段都是公开的,以便更容易输入示例。)

public class Ogre
{
  int weight;
  int height;
  int axeLength;
}

public class Wizard
{
  int age;
  int IQ;
  int height;
}

在每个课程中,我都可以创建一个名为battle()的方法,该方法将确定如果Ogre遇到Ogre或者向导遇到向导,谁将获胜。这是一个例子。如果食人魔遇到食人魔,那么较重的食人魔会获胜。但如果重量相同,那么长斧的那个就会赢。

public Ogre battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else if (this.axeLength > o.axeLength) return this;
  else if (this.axeLength < o.axeLength) return o;
  else return this;    // default case
}

我们可以为巫师制作类似的方法。

但是如果巫师遇到食人魔怎么办?我们当然可以为此做一个方法,比较一下,比如高度。

public Wizard battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else return this;
}

我们会为符合巫师的Ogres做一个类似的。但如果我们必须在程序中添加更多字符类型,事情就会失控。

这是我被卡住的地方。一个显而易见的解决方案是创建具有共同特征的Character类。 Ogre和Wizard从Character继承并扩展它以包含定义每个特征的其他特征。

public class Character
{
  int height;

  public Character battle(Character c)
  {
    if (this.height > c.height) return this;
    else if (this.height < c.height) return c;
    else return this;
  }
}

有没有更好的方法来组织课程?我查看了策略模式和中介模式,但我不确定它们中的任何一个(如果有的话)在这里可以提供什么帮助。我的目标是达到某种常见的战斗方法,这样如果一个食人魔遇到食人魔就会使用食人魔与食人魔的战斗,但如果食人魔遇到一个巫师,它会使用一个更通用的战斗。此外,如果遇到的角色没有共同的特征怎么办?我们如何决定谁赢得了一场战斗?

编辑:很多很棒的回复!我需要消化它们并找出哪种方法最适合我的情况。

13 个答案:

答案 0 :(得分:19)

visitor pattern“是一种将算法与其操作的对象结构分离的方法”。

对于您的示例,您可以

class Character {
    boolean battle(BattleVisitor visitor) {
       return visitor.visit(this);
    }
}

class Ogre extends Character {..}
class Wizard extends Character {..}
class Dwarf extends Character {..}

interface BattleVisitor {
    boolean visit(Ogre character);
    boolean visit(Wizard character);
    boolean visit(Dwarf character);
}

class OgreBattleVisitor implements BattleVisitor {
    private Ogre ogre;
    OgreBattleVisitor(Ogre ogre) { this.ogre = ogre; }
    boolean visit(Ogre ogre) {
      // define the battle 
    }

    boolean visit(Wizard wizard) {
      // define the battle 
    }
    ...
}

每当发生战斗时:

targetChar.battle(new OgreBattleVisitor(ogre));

为向导和矮人定义一个访客实现以及出现的任何内容。另请注意,我将visit方法的结果定义为boolean(赢或输),而不是返回获胜者。

因此,在添加新类型时,您必须添加:

  • 访问者处理新类型的方法。
  • 处理新类型战斗的实现

现在,事实证明,如果“Ogre vs Wizard”==“Wizard vs Ogre”,你将会有一些重复的代码。我不知道是否是这种情况 - 例如,根据谁先打击,可能会有所不同。此外,你可能想提供完全不同的算法,比如说“沼泽与食人魔的战斗”与“与食人魔的村庄战斗”相比。因此,您可以创建新的访问者(或访问者层次结构),并在需要时应用适当的访问者。

答案 1 :(得分:7)

在两个食人魔具有相同的身高/体重相同的斧头长度的情况下会发生什么?根据你的例子,那个有幸被称为第一个的人将获胜。

我不知道这是否是一个合适的选择,但是如果你选择了一个完全不同的方案并将“战斗得分”归因于每个角色而不是依赖于比较个体特征,那该怎么办呢?您可以在公式中使用字符的属性来提供一些可以与另一个字符进行比较的整数。然后你可以使用通用的battle方法来比较两个分数,并将字符与较高的分数一起返回。

例如,如果食人魔的“战斗分数”是按照他的身高加上他的体重乘以他的斧头长度来计算的,那么巫师的分数是按他的年龄乘以他的智商来计算的?

abstract class Character {
    public abstract int battleScore();

    public Character battle(Character c1, Character c2) {
        (c1.battleScore() > c2.battleScore()) ? return c1 : c2;
    }
}

class Ogre extends Character {
    public int battleScore() {
        return (height + weight) * axeLength;
    }
 }

 class Wizard extends Character {
    public int battleScore() {
        return height + (age * IQ);
    }
 }

答案 2 :(得分:5)

听起来像你想要double dispatch

基本上,您的OgreWizard(可能)会有一个共同的基础。当您从battle的基础调用Ogre方法时,它将在Wizard的基础上调用,并在该基础上调用另一个以Ogre为参数的函数。两个函数调用的多态行为有效地同时为这两种类型提供了多态性。

答案 3 :(得分:4)

如何使用像

这样的方法将战斗逻辑分成自己的类
Battle(Ogre ogre, Wizard wizard)

哪个会返回包含获胜者的对象(或胜者本身,无论如何)。这会将战斗逻辑与战斗者分开,并且还允许你生成,即:

Battle(Creature creat1, Creature creat2)

对于任何生物配对(假设Wizard / Ogre /等都具有'Creature'作为基类)而没有特定逻辑的回退方法。这将允许您添加/编辑/删除战斗逻辑,而无需修改任何生物本身。

答案 4 :(得分:3)

我认为你应该重新思考这一切。

让我们把“魔兽世界”作为战斗如何完成的一个例子,仅仅因为它是一个众所周知的游戏。

你有许多不同的课程,能够做不同的事情,并有自己的优点和缺点。但是,它们都共享一些常见的统计类型。例如,法师比战士有更多的智力,但战士的力量将比法师强很多。

那么他们如何实战呢?好吧,无论班级如何,每个角色都有许多能力可供他们使用。每个技能都会造成一定程度的伤害,一旦其中一个角色的HP下降到0,那个角色就会死亡 - 他们就会失去战斗力。

你应该使用类似的方法:定义一个具有适用于每个人的共同属性的公共基类 - 诸如力量,法术能量,防御和耐力之类的东西。然后,当每种类型的角色进行战斗时,他们可以使用任何一系列攻击或法术 - 每次攻击或法术造成的伤害将取决于攻击者和防御者的统计数据,使用一些合适的公式(具有一些随机性)为了保持它的趣味性,如果巫师不可能击败食人魔,反之亦然,这可能就没那么有趣了。

但是这里还有其他需要考虑的事情:也许你不应该为每种类型使用一个类。如果你能为每个人使用相同的公式,那就更好了 - 即使他们没有相同的能力。你不必在那里编写每个能力的代码,而只需要在文件中列出一个能力及其参数列表,而Character类将使用它来完成所有这些计算。这样可以更容易地调整公式(只有一个地方可以查看),并且更容易调整能力(只需更改文件)。写这个公式有点困难,因为你可能想给予食人魔一个高强度的奖励,而精灵会获得高智力的奖励,但它比X几乎相同的公式更好,每个一个可以影响输出的stat。

答案 5 :(得分:3)

这正是Strategy旨在解决的问题。

让我们回顾Strategy

的各个部分
  • 背景:战斗
  • 策略:如何 你会定义哪个会胜利
  • 具体策略:哪里打架 发生了,并做出了决定。

所以,不要把这个责任留给自己的角色(因为他们总是说,“我赢了!!,不,我赢了,不,我......”)你可以创建一个{ {1}}。

具体实施将决定谁获胜。

strategy in action http://bit.ly/cvvglb

使用http://yuml.me

生成的

图表

您可以定义所有字符同意响应的常用方法(这似乎不是您想要的,当所有字符具有相同的“属性”或类似RefereeStrategy的方法时),这很有用做“盲目”策略。

我正在做第二个(“盲目”策略)

defense():int, attack():int, heal():int

这允许您根据需要插入新的算法(策略 - 裁判 - 模式有用)。

它通过向裁判转发胜利者的决定来保持上下文(你的战斗竞技场)免于“if / elseif / else / if / else”构造,并且隔离你的不同角色彼此。

答案 6 :(得分:2)

一种方法是为所有字符类型(如

)创建一个新界面
public interface Fightable
{
    public Fightable doBattle(Fightable b);
}

然后从那里开始在每个类中实现doBattle。例如,在Ogre类中,你可以检查b是否是Ogre的一个实例(在这种情况下做一件事),一个向导(在这种情况下是另一个)等等......

问题在于,每次添加新类型时,都需要将代码添加到每个字符的每个字符类别,这不是特别可维护的。此外,您必须强调确保操作得到正确维护,即如果您在Ogre类中更改了关于向导的doBattle方法,而不是在关于Ogres的Wizard类中,则可能会出现这样的情况:无论你调用anOgre.doBattle(aWizard)还是aWizard.doBattle(anOgre),结果都会有所不同。

最好的方法是创建一个接受两个角色的战斗类,并通过查看已经传递给它的两个类类型来包含战斗逻辑:这样,你每次只需要改变你的战斗类添加了新的角色类型!您希望封装最有可能最常更改的行为。

答案 7 :(得分:1)

无论如何,你必须为每一个战斗组合定义独特的逻辑(假设逻辑是唯一的) - 你选择使用什么样的设计模式并不重要。唯一的要求是将此逻辑与Ogre和Wizard类分开,并在不同的类中创建战斗方法。我认为你现在正在做的事情是完全没问题的(一旦你将战斗逻辑移到其他地方)而不需要访问者模式,如果这是一些企业游戏我就会使用它:)

不要听取所有关于设计模式的漏洞......

答案 8 :(得分:1)

为什么不根据它的属性为怪物创造一些价值,而不是试图解决每种类型的怪物与其他类型的怪物的战斗。

如果它的价值高于它打架的价值,它就会赢。

为了补偿某些敌人更好地对抗其他敌人,为每种攻击类型实施某种防御

例如,射手攻击范围,食人魔是近战,巫师是魔法。食人魔具有近战防御,远程防御和魔法防御。

然后可以根据它的攻击计算怪物的价值。和敌人各自的盔甲,以及惠普等等。

所以你不需要逐个案例。

答案 9 :(得分:0)

嗯,首先,你的第一个设计并不好,因为你让战士决定谁赢了。如果您使用Mediator,例如类似于提议的战斗类,你将能够集中战斗逻辑并轻松地在一个地方改变任何战斗规则。想象一下,有很多生物......一旦你想要改变两个人的战斗方式,你会去哪里寻找战斗方法?在第一课还是第二课?一些超类?所以Mediator是个好主意。另一个问题是决定使用哪个规则。您可能很容易遇到多个调度问题。

答案 10 :(得分:0)

这样的东西?

class Trait
{
    enum Type
    {
        HEIGHT,
        WEIGHT,
        IQ
    }
    protected Type type;
    protected int value;

    public Trait(int value, Type type)
    {
        this.type = type;
        this.value = value;
    }

    public boolean compareTo(Trait trait)
    {
        if(trait.type != this.type)
            throw new IllegalArgumentException(trait.type+" and "+this.type+" are not comparable traits");
        else
            return this.value - trait.value;
    }
}

class Character
{
    protected Trait[] traits;

    protected Character(Trait[] traits)
    {
        this.traits = traits;
    }

    public Trait getTrait(Trait.Type type)
    {
        for(Trait t : traits)
            if(t.type == type) return t;
        return null;
    }

    public Character doBattleWith(Character that)
    {
        for(Trait thisTrait : traits)
        {
            otherTrait = that.getTrait(thisTrait.type);
            if(otherTrait != null)
            {
                int comp = thisTrait.compareTo(otherTrait);

                if(comp > 0)
                    return this;
                else if (comp < 0)
                    return that;
            }
        }
        return null;
    }
}

class Ogre extends Character
{
    public Ogre(int height, int weight)
    {
        super(new Trait[]{
            new Trait(Type.HEIGHT,height),
            new Trait(Type.WEIGHT,height)});
    }
}

答案 11 :(得分:0)

试试叔叔鲍勃的三重派遣。请参阅我的答案:Managing inter-object relationships

答案 12 :(得分:0)

我知道这有点晚了,但几年前Steve Yegge写了an article几乎这个确切的问题(他甚至使用了一个游戏示例!)。