【问题标题】:Is there a good way of decoupling similar code obtained by inheriting the same interface in c#?有没有很好的解耦c#中继承相同接口得到的类似代码的方法?
【发布时间】:2019-06-21 16:51:05
【问题描述】:

假设,在代码中,我们有一个 IEnemy 接口,它上面有一个名为 Attack() 的方法。假设我们有五个来自 IEnemy 接口的敌人。在其中三个类中,我们使用完全相同的 Attack 方法实现。在其中之一中,我们也使用相同的代码,但在方法的某处更改了一两行代码。而且,在最后一个类中,我们仍然有相同的实现,但是在方法的某处添加/删除了一两行代码。您对解耦这段代码有什么建议吗?

我尝试过重写方法,如果我们在方法中间更改某些内容,则该方法不起作用。 我尝试使用委托作为参数,当我们想在方法中的其他地方更改某些内容时,它不起作用。 我尝试使用接口的扩展方法来制作默认实现,但其中两个类仍然具有解耦代码。

interface IEnemy
{
    void Attack();
}

class Enemy1 : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("This is an enemy");
        Console.WriteLine("Enemy is jumping");
        Console.WriteLine("Enemy is attacking");
    }
}

class Enemy2 : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("This is an enemy");
        Console.WriteLine("Enemy is jumping");
        Console.WriteLine("Enemy is attacking");
    }
}

class Enemy3 : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("This is an enemy");
        Console.WriteLine("Enemy is jumping");
        Console.WriteLine("Enemy is attacking");
    }
}

//Let's say this enemy is not capable of jumping, so we want to remove the code that says enemy is jumping.
class Enemy4 : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("This is an enemy");
        Console.WriteLine("Enemy is attacking");
    }
}

//Let's say this is the boss and instead of jumping, it will roar.
//So we want to change the code that says enemy is jumping to enemy is roaring.
class Enemy5 : IEnemy
{
    public void Attack()
    {
        Console.WriteLine("This is an enemy");
        Console.WriteLine("Enemy is roaring");
        Console.WriteLine("Enemy is attacking");
    }
}

【问题讨论】:

  • 您应该使用基类来代替virtual 实现,如果需要,可以覆盖该实现,或者使用默认的基类实现。
  • 但是当我想在方法的中间改变一些东西时,我不能在不解耦的情况下改变它,所以我仍然需要重新编写实现,不是吗?
  • @HeroOfSkies 在你的情况下不是直接的,即使是抽象的,但是如果敌人可以跳跃或咆哮或其他什么,它必须是父类可用的通用布尔属性。然后父类可以使用 if / else 语句显示正确的消息。
  • @Franck 谢谢你的回答,也谢谢你的建议:)
  • 您可以将跳跃、攻击和咆哮隔离到它们自己的类中,IEnemy 可以通过一些抽象来依赖它们。这样,每个敌人都只是各种行为的组合,而不是使用继承。

标签: c# oop interface polymorphism decoupling


【解决方案1】:

我会将接口替换为具有默认实现的抽象基类,然后将Attack 方法分解为单独的可覆盖步骤。我将Attack 设置为虚拟的,用于完全拥有自己攻击模式的敌人。

abstract class BaseEnemy {
    public virtual void Attack() {
        AttackIdentify();
        AttackSignal();
        AttackAttack();
    }

    protected virtual void AttackIdentify() {
        Console.WriteLine("This is an enemy");
    }

    protected virtual void AttackSignal() {
        Console.WriteLine("Enemy is jumping");
    }

    protected virtual void AttackAttack() {
        Console.WriteLine("Enemy is attacking");
    }
}

class Enemy1 : BaseEnemy {
    protected override void AttackIdentify() {
        Console.WriteLine("This is an enemy 1");
    }
}

class Enemy2 : BaseEnemy {
}

class Enemy3 : BaseEnemy {
    protected override void AttackIdentify() {
        Console.WriteLine("This is an enemy 3");
    }
}

//Let's say this enemy is not capable of jumping, so we want to remove the code that says enemy is jumping.
class Enemy4 : BaseEnemy {
    protected override void AttackSignal() { }
}

//Let's say this is the boss and instead of jumping, it will roar.
//So we want to change the code that says enemy is jumping to enemy is roaring.
class Enemy5 : BaseEnemy {
    protected override void AttackSignal() {
        Console.WriteLine("Enemy is roaring");
    }
}

如果你还需要接口IEnemy,你可以让BaseEnemy实现它。

【讨论】:

  • 这很好,因为您基本上建立了一个攻击模板,并能够对其进行自定义。
  • 我也想过这种模式,但似乎写得太多了,所以我认为我把事情复杂化了,但这仍然是我能找到的最好的。谢谢:)
  • @HeroOfSkies 有了抽象基类,您只需要在需要时编写额外的代码——这对您来说写得太多了吗?看Enemy2...
  • @HeroOfSkies 如果您确信... :) - 不是想争论,只是想了解您的 POV...
  • 请注意:AttackIdentifyAttackSignalAttackAttack 应该是 protected,而不是 public
【解决方案2】:

这里是它的抽象类版本的基本示例。

public abstract class Enemy
{
    public bool CanJump { get; set; } = false;
    public bool CanRoar { get; set; } = false;
    public bool CanAttack { get; set; } = false;

    public virtual void Attack()
    {
        Console.WriteLine("This is an enemy");

        if (CanJump)
        {
            Console.WriteLine("Enemy is jumping");
        }

        if (CanRoar)
        {
            Console.WriteLine("Enemy is roaring");
        }

        if (CanAttack)
        {
            Console.WriteLine("Enemy is attacking");
        }
    }
}

public class Enemy1 : Enemy
{
    public Enemy1()
    {
        CanJump = true;
        CanRoar = true;
    }
}

public class Enemy2 : Enemy
{
    public Enemy2()
    {
        CanRoar = true;
        CanAttack = true;
    }
}

public class Enemy3 : Enemy
{
    public Enemy3()
    {
        CanRoar = true;
        CanAttack = true;
    }

    public override void Attack()
    {
        base.Attack();

        Console.WriteLine("Custom thing");
    }
}

您会注意到Enemy1Enemy2 的工作方式相同,但设置了不同的属性,当您调用Attack 方法时将相应地显示这些属性。我希望您注意的有趣部分是覆盖该方法的Enemy3 类。这可以通过在 Enemy 抽象类中创建方法 Virtual 来实现。

此覆盖允许在类Enemy 中调用基类方法Attack,此外它还显示自定义值。所以它非常灵活。

请注意,这对于提供的示例非常有效,但是通过您的班级名称,我可以很容易地假设真正的项目是什么,这不是正确的方法。您不应该为每个敌人创建不同的类,但这超出了问题的范围。

【讨论】:

  • 我认为将if 逻辑放在基础Attack 方法中并不是一个好主意。您应该将其委托给特定的敌人。
  • 这对我来说似乎很不灵活。你别无选择,只能继续强迫所有敌人携带他们可能不关心的功能,即使他们这样做了,他们也必须遵守基地决定他们可能不想要的执行顺序。让基础为攻击生命周期提供一组方法,每个方法都可以为其提供自定义操作可能会更好。
  • @ChiefTwoPencils 它不是完全灵活的。目标是使其格式灵活。在最好的情况下,您不会为每个敌人都有一个类,而是一个具有通过工厂类自定义的一组动作的单个类。我只是尽量保留他的原始格式,以免让新开发者感到困惑。
  • @Franck 实际上我从未想过工厂。我在开发方面并不是那么新(尽管直到最近我还没有真正使用过设计模式)所以你能解释一下我如何使用工厂来做到这一点吗?
猜你喜欢
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
  • 2023-03-25
  • 2011-01-23
  • 2011-12-07
相关资源
最近更新 更多