C# 面向对象编程中的表达式类型组合

C# 面向对象编程中的表达式类型组合,c#,typescript,oop,design-patterns,composition,C#,Typescript,Oop,Design Patterns,Composition,假设我们想写一个RPG游戏,它有3种基本角色类型:战士、法师和弓箭手。 此外,我们还组合了骑士、圣骑士和游侠等角色类型。在TypeScript中,我们可以描述如下字符的接口: // fighter can fight // mage can cast // archer can shoot // paradin can cast and fight // knight can fight and shoot // ranger can cast a

假设我们想写一个RPG游戏,它有3种基本角色类型:战士、法师和弓箭手。 此外,我们还组合了骑士、圣骑士和游侠等角色类型。在TypeScript中,我们可以描述如下字符的接口:

    // fighter can fight
    // mage can cast
    // archer can shoot
    // paradin can cast and fight
    // knight can fight and shoot
    // ranger can cast and shoot

    type Fighter = {fight: () => string}
    type Mage = {cast: () => string}
    type Archer = {shoot: () => string}
    type Knight = Fighter & Archer
    type Paladin = Fighter & Mage
    type Ranger = Archer & Mage
public class Hero
{
    private readonly string name;
    private readonly IEnumerable<IHeroAbility> meleeAbilities;
    private readonly IEnumerable<IHeroAbility> magicAbilities;
    private readonly IEnumerable<IHeroAbility> rangedAbilities;

    public Hero(
        string name,
        [Optional] IEnumerable<IHeroAbility> meleeAbilities,
        [Optional] IEnumerable<IHeroAbility> magicAbilities,
        [Optional] IEnumerable<IHeroAbility> rangedAbilities)
    {
        this.name = name;

        var defaultAbilities = new List<IHeroAbility> { new OhCrap() };
        this.meleeAbilities = meleeAbilities ?? defaultAbilities;
        this.magicAbilities = magicAbilities ?? defaultAbilities;
        this.rangedAbilities = rangedAbilities ?? defaultAbilities;
    }

    public void MeleeAttack()
    {
        var ability = meleeAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void MagicAttack()
    {
        var ability = magicAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void RangedAttack()
    {
        var ability = rangedAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

}
问题是如何仅使用OOP技术实现这些接口(假设我们使用的是主流OOP语言,如C#)

我没有成功地用没有行为复制的继承来表达这一点。目前我看到的唯一方式是:

class Paladin {
  constructor(private fighter: Fighter, private mage: Mage) {}

  fight() {this.fighter.fight()}
  cast() {this.mage.cast()}
}

但我不喜欢这种方法,因为任何时候我想要创造一个圣骑士,我也必须创造一个法师和一个基本上是同一个人的战士,也许试着把你的合成推进一点。您只需要一种具有不同能力的类型,例如:

public class Hero
{
    private readonly string name;
    private readonly IEnumerable<IHeroAbility> abilities;

    public Hero(string name, [Optional] IEnumerable<IHeroAbility> abilities)
    {
        this.name = name;
        this.abilities = abilities ?? new List<IHeroAbility> { new OhCrap() };
    }

    public void Attack()
    {
        Console.WriteLine(name);

        foreach (var ability in abilities) ability.Execute();

        Console.WriteLine();
    }
}
然后创作你的英雄类型:

class Program
{
    static void Main(string[] args)
    {
        IHeroAbility swingSwordAbility = new SwingSwordAbility();
        IHeroAbility shootArrowAbility = new ShootArrowAbility();
        IHeroAbility castFireBallAbility = new CastFireBallAbility();

        var fighter = new Hero("The Fighter", new[] { swingSwordAbility });
        fighter.Attack();

        var mage = new Hero("The Mage", abilities: new[] { castFireBallAbility });
        mage.Attack();

        var archer = new Hero("The Archer", abilities: new[] { shootArrowAbility });
        archer.Attack();

        var knight = new Hero("The Knight", abilities: new[] { swingSwordAbility, shootArrowAbility });
        knight.Attack();

        var paladin = new Hero("The Paladin", abilities: new[] { swingSwordAbility, castFireBallAbility });
        paladin.Attack();

        var ranger = new Hero("The Ranger",abilities: new[] { shootArrowAbility, castFireBallAbility });
        ranger.Attack();

    }
}
如果您想要更明确的动作组,可以将
Hero
类修改为如下内容:

    // fighter can fight
    // mage can cast
    // archer can shoot
    // paradin can cast and fight
    // knight can fight and shoot
    // ranger can cast and shoot

    type Fighter = {fight: () => string}
    type Mage = {cast: () => string}
    type Archer = {shoot: () => string}
    type Knight = Fighter & Archer
    type Paladin = Fighter & Mage
    type Ranger = Archer & Mage
public class Hero
{
    private readonly string name;
    private readonly IEnumerable<IHeroAbility> meleeAbilities;
    private readonly IEnumerable<IHeroAbility> magicAbilities;
    private readonly IEnumerable<IHeroAbility> rangedAbilities;

    public Hero(
        string name,
        [Optional] IEnumerable<IHeroAbility> meleeAbilities,
        [Optional] IEnumerable<IHeroAbility> magicAbilities,
        [Optional] IEnumerable<IHeroAbility> rangedAbilities)
    {
        this.name = name;

        var defaultAbilities = new List<IHeroAbility> { new OhCrap() };
        this.meleeAbilities = meleeAbilities ?? defaultAbilities;
        this.magicAbilities = magicAbilities ?? defaultAbilities;
        this.rangedAbilities = rangedAbilities ?? defaultAbilities;
    }

    public void MeleeAttack()
    {
        var ability = meleeAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void MagicAttack()
    {
        var ability = magicAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void RangedAttack()
    {
        var ability = rangedAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

}
公共类英雄
{
私有只读字符串名称;
私有只读IEnumerable可扩展性;
私人只读IEnumerable Magicability;
私有只读IEnumerable rangedAbilities;
公众英雄(
字符串名,
[可选]IEnumerable meleeAbilities,
[可选]IEnumerable Magicability,
[可选]IEnumerable rangedAbilities)
{
this.name=名称;
var defaultAbilities=新列表{new OhCrap()};
this.meleeAbilities=meleeAbilities??default能力;
this.magicAbilities=magicAbilities??默认能力;
this.rangedAbilities=rangedAbilities??默认能力;
}
公开无效MeleeAttack()
{
var-ability=meleeaabilities.First();
Console.WriteLine(名称);
执行()的能力;
}
公共无效MagicAttack()
{
var ability=magicAbilities.First();
Console.WriteLine(名称);
执行()的能力;
}
公共无效RangeAttack()
{
var ability=rangedAbilities.First();
Console.WriteLine(名称);
执行()的能力;
}
}
然后,你可以开始构建它,以融入更复杂的逻辑,比如与能力、冷却、能力选择等相关的成本


希望这有帮助

你的
Paladin
类接近于,可以按原样翻译成C#,也许有一些
readonly
可以对其进行一些改进

使用组合(此处由)被认为是比使用继承(请参阅)更强大的设计,但它可能感觉不太自然,这就是为什么您说:

但我不喜欢这种方法,因为任何时候我想要创造一个圣骑士,我也必须创造一个法师和一个基本上是同一个人的战士

“同一个人”在语义上与继承比与组合更相关,但会导致更脆弱的设计。从角色或能力(*)的角度思考更灵活:治疗者,德鲁伊不是一个人,而是一个人/玩家/角色可以拥有的能力


(*)术语取自,但我仍然希望您的解决方案更简单、更明确。为了更进一步,我们需要一个参考场景列表和/或正在运行的原型(或任何现有程序)来评估设计和代码…

为此,我建议使用“装饰设计模式”

在那之后,你可以让你的主角加入一些装饰者的特定行为。例如:

var  Fighter = new Charactermaker();
var  Mage  = new Charactermaker();
var  Archer = new Charactermaker();

KnifeDecorator addKnight = new KnifeDecorator(Fighter);
Paladin Decorator addPaladin= new PaladinDecorator(addKnight);
我有一些建议给你。您还可以使用“flyweight设计模式”使应用程序更轻。通过在多个对象之间共享状态的公共部分,而不是在每个对象中保留所有数据,它允许您将更多的对象放入可用的RAM中

有关更多信息,请参见流动链接:


如果您可以在运行时将这些技术中的任何一种应用于任何字符,该怎么办?c#和typescript接口的工作方式不同,在c#中,您必须明确声明您正在实现一个接口。你得想一个不同的办法approach@Stefan,我不知道你是什么意思。不支持技术的运行时组合needed@Tal为什么这种差异很重要?你好,艾伯特。谢谢你的回答!在你的解决方案中有一件事困扰着我:英雄类不知道能力的逻辑,它只知道能力是“可执行的”。假设我们想添加另一个可以治疗的基本类型“治疗者”和可以治疗和射击的组合类“德鲁伊”。在一种情况下,您想要heal(),在另一种情况下,您想要shot(),所以hero类应该包含一些违反单一责任原则的选择逻辑。因此,我的示例中的功能基本上是封装状态和行为的命令,这样客户机对象就不必“知道”任何使用它的能力。把英雄简单地看作一个门面。所有行为都应推迟(如其他评论中所述转发),从而避免任何违反单一责任的行为。我“强迫”我样本中的正面(英雄)有选择行为来保持简短。阿尔伯特,再次感谢你的回答!我有几个问题:为什么我们需要额外的分类级别(malee/magic/range)?如果我们添加了不适合这些类别的能力,该怎么办?英雄类已经看起来像上帝的对象,我觉得它不能很好地扩展。我注意到的另一件事是:如果能力的执行取决于每个能力所特有的某个参数集,那么在这种情况下,我们不能给他们接口IHeroAbility和多态调用能力。Execute()不再可能了Hi Romain,感谢您抽出时间回答!在这种情况下,通过“转发”进行合成似乎是最好的解决方案。但我不理解“复合”部分。我认为当我们使用三的时候会用到复合,所以我看不出有什么联系here@OleksandrTerletskyi如果我理解你的意思