【问题标题】:Separating interfaces and Constructor injection分离接口和构造函数注入
【发布时间】:2015-11-03 21:51:03
【问题描述】:

现在我有一种困境。有一个类结构类似于以下:

public interface IMammal
{
    void Eat();
}

public interface IBarking
{
    void Bark();
}

IBarkingIMammal 的实例。从理论上讲,我们的动物可以是其中之一,也可以只是其中之一。 Cow 如您所见IMammalDogIMammalIBarking。理论上,我们甚至可以拥有会吠但不是哺乳动物的人。

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。这个设计有什么问题,怎么解决?

【问题讨论】:

  • 您假设Dog 始终是IMammal,尽管IMammal 可能不是Dog。我不确定您在哪里看到 IBarking 在代码中被假定为 IMammal?你正在选择一个具体的类型,你可以做像var barker = _animal as IBarking 这样的事情,这会让你的问题有意义......但即使这样也不能假设IBarking 是哺乳动物,只是哺乳动物可能 i> 实施它。
  • @RonBeyer 对不起。这是我的错。我已经更正了代码,现在我将其转换为IBarking。情况是我不能提供任何其他类型的IBarking,除了那些实现IMammal的人。
  • 你的实现,顺便说一句,只能接受IMammals,而Guard只会做IBarking,所以如果Guard做任何事情,它只能接受吠叫的哺乳动物。如果您希望它接受所有IBarking,无论它们是哺乳动物,那么您需要在构造函数中使用IBarking,并在Feed 中执行相同的检查。也许你需要做一个更高级的接口,比如IAliveIFauna,可以封装哺乳动物和吠叫的“东西”(假设吠叫的东西是活的)。

标签: c# oop dependency-injection architecture


【解决方案1】:

这种简化的例子几乎没有任何意义。你错过了“问题案例”以及你想做什么。显示实现IBarking 但未实现IMammal 的类,以及将其传递给Farm 时会出现什么问题。无论如何,给定前提:

  • 接口IMammal 存在。
  • 接口IBarking 存在。
  • 实现IBarking 的类没有实现IMammal
  • 类构造函数Farm 必须接受IBarking IMammal
  • 当前类构造函数接受IMammal

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

那么你需要:

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

您很可能还有其他限制,例如 “但我想在传递给构造函数的变量上调用 Eat(),但随后您的描述(“我们假设 @987654337 @ 始终为IMammal") 不正确或不完整,您需要将Eat() 移动到IFarmable 接口。

【讨论】:

    【解决方案2】:

    我会尝试解读您的意图

    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. 我已经明确表示可以有守护动物
    2. 守护动物是可选的(因为它是通过属性而不是构造函数分配的)。

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

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

    【讨论】:

    • 重点不是实现一个理想的农场。这只是一个例子。问题是现在我有一段类似的代码,对我来说感觉有点臭。我无法决定是否应该修改它,如果是,那么如何修改。
    猜你喜欢
    • 2023-03-08
    • 1970-01-01
    • 2018-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    相关资源
    最近更新 更多