接口的子类型仅与另一个接口的子类型兼容

Subtypes of an interface only compatible with a subtype of another interface

我一直在为一个问题绞尽脑汁,但我仍然不知道什么是最好的解决方案。由于应用领域是非常技术性的,我将用一个简单的例子来说明我的问题。

假设我有以下接口:

public interface Animal {
    public void feed(AnimalFood food);
}

public interface AnimalFood {
    // some methods
}

以及以下两个 类 实现接口:

public class DogFood implements AnimalFood {
    // some methods
}

public class CatFood implements AnimalFood {
    // some methods
}

public class Dog implements Animal {
    public void feed(AnimalFood food){
        // can only eat dog food
    }
}

public class Cat implements Animal {
    public void feed(AnimalFood food){
        // can only eat cat food
    }
}

这意味着每次我提供 DogCat 实例时,我都必须验证接收到的 DogFoodCatFood 实例并抛出如果这不是正确的食物,则例外。

这对我来说很难闻,我很确定违反了 Liskov 替换原则!

是否有设计模式,或者什么是管理这种情况的优雅方式?

您似乎正在使用 Java。如果是这样,您可以使用泛型来限制 feed

中的参数类型
public interface Animal<T extends AnimalFood> {
      void feed(T food);
}



public class Dog implements Animal<DogFood> {
    public void feed(DogFood food){
        // can only eat dog food
    }
}

public class Cat implements Animal<CatFood> {
    public void feed(CatFood food){
        // can only eat cat food
    }
}

由于这个问题似乎与语言无关,我将用 C# 回答,但这些想法将适用于 Java 和类似的问题。

我们可以IAnimalFood声明为通用接口,但这会产生编译时间错误,而不是运行您要求的时间例外,如果您尝试将猫食喂给狗:

    void Main()
    {
        new Cat().Feed(new UserQuery.CatFood());
        new Dog().Feed(new UserQuery.CatFood()); // won't compile
    }
    
    public interface IAnimalFood { }
    
    public class DogFood : IAnimalFood { }
    
    public class CatFood : IAnimalFood { }
    
    public interface IAnimal<TFood> where TFood : IAnimalFood
    {
        void Feed(TFood food);
    }
    
    public class Dog : IAnimal<DogFood> {
        public void Feed(DogFood food){
            // can only eat dog food
        }
    }
    
    public class Cat : IAnimal<CatFood> {
        public void Feed(CatFood food){
            // can only eat cat food
        }
    }

似乎没有任何合理的 运行 时间替代方法来检查所提供食物的 类型 除了 声明模式 我以前成功使用过:添加到食物类型 属性 指定它是什么食物:

void Main()
{
    new Dog().Feed(new UserQuery.CatFood());
}

public enum FoodType
{
   CatFood,
   DogFood,
   BirdFeed
}

public interface IAnimalFood {
   FoodType TypeOfFood { get; }
}

public class DogFood : IAnimalFood {
   public FoodType TypeOfFood { get { return FoodType.DogFood; } }
}

public class CatFood : IAnimalFood
{
   public FoodType TypeOfFood { get { return FoodType.CatFood; } }
}

public interface IAnimal
{
    void Feed(IAnimalFood food);
}

public class Dog : IAnimal {
    public void Feed(IAnimalFood food){
        // can only eat dog food
        if (food.TypeOfFood != FoodType.DogFood)
         throw new Exception("You must be joking pal! I don't eat that!");
    }
}

结果:

Exception

You must be joking pal! I don't eat that!

或者,让 AnimalFoods 实现一个标志枚举(位域),它指定可以吃它们的动物类型。

无论哪种方式(一种食物 属性 或一种可以吃掉我的动物 属性):

  • 您不必进行类型检查

  • 没有编译时约束

  • 能吃的食物种类或动物很好 封装到 AnimalFood class

这也可以与其他地方建议的工厂想法相结合。

为了用泛型补充已接受的答案,让所有 Animal 实例都能够通过工厂方法设计模式创建自己的食物类型可能很有用。

这是 GoF 模式:

以下是将其应用于您的案例的方法: