分离接口和构造函数注入

Separating interfaces and Constructor injection

现在我有一种进退两难的心理。有一个类似于下面的class结构:

public interface IMammal
{
    void Eat();
}

public interface IBarking
{
    void Bark();
}

IBarkingIMammal 的实例。从理论上讲,我们的动物可以是其中之一,也可以只是其中之一。 Cow就是大家看到的IMammal,而Dog就是IMammalIBarking。从理论上讲,我们甚至可以拥有会吠叫但不是哺乳动物的人。

public class Mammal : IMammal
{
    public void Eat()
    {
        Console.Write("Om-nom-nom");
    }
}

public class Cow : Mammal
{
}

public class Dog : Mammal, IBarking
{
    public void Bark()
    {
        Console.Write("Bark-bark!!!");
    }
}

这是一个Farm,那里只有一只动物:

public class Farm
{
    private readonly IMammal _animal;

    public Farm(IMammal animal)
    {
        _animal = animal;
    }

    public void Feed()
    {
        _animal.Eat();
    }

    public void Guard()
    {
        var dog = _animal as IBarking;
        if (dog != null)
            dog.Bark();
    }
}

我在这里看到的问题是我们假设 IBarking 总是 IMammal。这个设计有什么问题,如何解决?

这样简化的例子几乎没有任何意义。你错过了 "problem case" 以及你想做什么。显示实现 IBarking 但不实现 IMammal 的 class 以及将其传递给 Farm 时出现的问题。无论如何,给定前提:

  • 接口 IMammal 存在。
  • 接口 IBarking 存在。
  • A class 实施 IBarking 没有 实施 IMammal.
  • Class 构造函数 Farm 必须接受 IBarking IMammal.
  • 当前 class 构造函数接受 IMammal.

在这种情况下,您要么需要一个新的构造函数、一个新的私有成员和更多代码以在两者之间进行选择,要么需要一个重叠的接口。我会选择后者:IFarmable.

那么你需要:

  • public interface IMammal : IFarmable
  • public interface IBarking : IFarmable
  • public Farm(IFarmable farmable) { ... }

您很可能还有其他限制,例如 "but I want to call Eat() on the variable passed into the constructor",但是您的描述 ("we assume that IBarking is always IMammal")不正确或不完整,您需要将 Eat() 移动到 IFarmable 界面。

我会尝试解读你的意图

  1. 你想要一个农场,让动物长大(以后被宰杀作为食物)。
  2. 您可能需要另一只动物来保护它们(自从您试过施法后可能需要)。

更好的设计是:

public class Farm
{
    private readonly IMammal[] _animals;

    public Farm(IMammal[] animals)
    {
        _animals = animals;
    }

    public void Feed()
    {
       foreach (var animal in _animals)
            animal.Eat();
    }

    public IBarking GuardingAnimal { get; set; }

    public void Guard()
    {
        if (GuardingAnimal != null)
            GuardingAnimal .Bark();
    }
}

您的设计更改:

  1. 我说清楚了crystal可以有守卫动物
  2. 守卫动物是可选的(因为它是通过 属性 而不是构造函数分配的)。

我之所以要做出这种区分,是因为大多数动物都是被动的(你喂养和收获它们),而守卫动物有特定的用例,因此不应隐藏在其他动物中。

如果你想喂狗,你应该让那个接口继承IMammal(除非你在哺乳动物中引入更多功能,在这种情况下你应该提取IFeedable或类似的)。