【问题标题】:Writing a generic API for a type 'family'为“family”类型编写通用 API
【发布时间】:2010-01-18 16:03:35
【问题描述】:

我正在使用一个较小的类型层次结构,如下所示,并且可以说在我悲伤的无野生动物园世界中永远不会有任何其他动物类型(我一点也不担心扩展的弹性):

public abstract class Animal {};
public sealed class Dog : Animal {};
public sealed class Cat : Animal {};
public sealed class Turtle : Animal {};
public sealed class Bird : Animal {};

就 API 而言,我想以同样的方式对待所有动物,但显然它们在以下情况下的反应略有不同:

public class AnimalCare {
    public void Feed<T> (T pet) where T: Animal;
    public T PickUp<T> (PetStore store);
}

起初,在不将 feed 方法放在 Animal 类上的情况下执行喂养的想法(对不起,我知道这个例子已经到了这一点,但是为了争论,我们假设 AnimalCare 是我的 Animal 模型的视图我非常坚持关注点分离)会建议访问者。但我真正想做的是让上述成为 AnimalCare 的 API 消费者唯一需要担心的类型,而我执行以下操作:

public class AnimalCare {
    public void Feed<T> (T pet) where T : Animal;
    public T PickUp<T> (PetStore store);
    public void Feed<Dog> (Dog rover)
    {
        // dump out alpo, lift toilet seat
    }
    public void Feed<Turtle> (Turtle franklin)
    {
        // do turtles eat? let him figure it out himself.
    } // ...etc.

    public Dog PickUp<Dog> (PetStore store)
    {
        // get bone, tennis ball, leash
    }
    public Bird PickUp<Bird> (PetStore store)
    {
        // make sure not dead, nailed to perch
    } // ...etc.
}

等等。我知道第一部分(Feed () 方法)可以在不需要泛型的情况下重载,但这仍然给我留下了一个尴尬的 PickUp 实现(因为它不合法,正如我上面所描绘的那样),一些像 PickUpDog//PickUpBird 等悲惨的。我非常希望避免对 AnimalCare 的消费者及其志同道合的朋友有单独的“看法”。

我一直在尝试嵌套专门的类和其他奇怪的组合或接口重构尝试,但我似乎无法正确处理并陷入困境。有没有一种干净的方法来做我想做的事情,或者我是否愿意为每个具体的动物实施 AnimalCare?

编辑

Joel 关于工厂/存储库的观点让我想了更多。让我们将感兴趣的方法称为更合理一点:

public class AnimalPresentation {
    public void Show (Dog dog);
    public void Show (Cat cat);
    //..etc.
    public Animal Get (PetStore store);
}

我想这首先会更有意义。在调用 Get 时不知道要从 PetStore 中取出的 Animal 的类型,但是在 Get 的一般实现中,一旦确定了类型,它就会分支到特定的重载。 PetStore 的特定子类型是最佳/唯一的前进方式吗?

【问题讨论】:

    标签: c# generics oop polymorphism


    【解决方案1】:

    我认为AnimalCare 的问题在于它本质上是Animal 实例的工厂,或者至少可以访问Animal 存储库。所以也许你的PickUp 函数应该返回一个Animal 对象而不是一个特定的类型。

    或者,您不能将AnimalCare 建立在特定类型 (AnimalCare&lt;T&gt;) 上吗?这个例子很难根据实际意图来衡量,所以如果这些想法似乎没有达到目标,请道歉。

    【讨论】:

      【解决方案2】:

      编辑
      您可能会考虑这样的事情:

      首先你有一个界面IAnimal,上面有你的动物。他们不知道任何方法。然后创建一个新的界面,如

      interface IAnimalCareClient<T>
      {
          void Init(T animal);
          void Feed();
          T Pickup(PetStore store);
      }
      

      然后为每种动物类型创建客户端,例如

      class DogClient : IAnimalCareClient<Dog>
      {
          void Init(Dog animal) { //bla }
          void Feed() {}
          Dog Pickup(PetStore store) { //bla again }
      }
      

      然后您的客户端应该存储在某个列表中,并且类型可以驻留在各种 dll 等中。您只需要获取参考。

      那么就

      class AnimalCare
      {
          void Feed<T>(T animal)
          {
               //GrabWorkerFromStack<T>() should return the type IAnimalCareClient<T>
               IAnimalCareClient<T> worker = Activator.CreateInstance(GrabWorkerFromStack<T>());
               worker.Init(animal);
               worker.Feed();
          }
      }
      

      原始答案
      基本上你想对动物本身负责,因为你不能强迫你的AnimalCare 类实现所有动物的所有属性。然后变成简单的东西:

      interface IAnimal
      {
          void Feed();
          IAnimal Pickup(PetStore store);
      }
      

      class Dog : IAnimal
      {
          void Feed() { /* feed */ }
          IAnimal Pickup(PetStore store) { /* grab animal and return */ }
      }
      

      class AnimalCare
      {
          void Feed(IAnimal animal)
          {
              animal.Feed();
          }
      
          T Pickup<T>(T animal, PetStore store) where T: IAnimal
          {
               return (T)animal.Pickup(store);
          }
      }
      

      【讨论】:

      • 这就是我害怕的。除了荒谬的例子,我不希望 Animal 类必须了解 AnimalCare 和其他类的表示或格式要求。我需要的一切都是公共 API 的一部分,我真的希望有一种方法可以在 AnimalCare 类中实现具体实现。
      • 检查我的编辑。 AnimalCare 中的具体实现可以通过在 AnimalCare 类中嵌入继承 IAnimalCareClient 的类。
      【解决方案3】:

      你可以使用接口,我不知道有什么具体要求,但是如果一个动物可能有一个可以与其他动物共享的特征,但不需要接口是一个好的开始。

      例如:

      interface IFeedable
      {
          void Feed();
      }
      
      interface IMammal<TChild>
          where TChild: IMammal<TChild>
      {
          IEnumerable<TChild> Reproduce();
      }
      
      class Dog: IFeedable, IMammal<Puppy>
      {
          // ...
      }
      

      我一直在使用接口(可能过度使用它们),尤其是在这种情况下,因为这是正常的继承可能不是最好的方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-18
        • 1970-01-01
        • 1970-01-01
        • 2015-05-05
        • 1970-01-01
        • 2014-12-09
        • 1970-01-01
        相关资源
        最近更新 更多