为“家庭”类型编写通用API

时间:2010-01-18 16:03:35

标签: c# generics oop polymorphism

我正在使用一个小型的层次结构,类似于以下内容,并且可以说在我悲伤的野生动物园世界中不会有任何其他动物类型(我完全不担心扩展的弹性):

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);
}

乍一看,在没有在Animal类上放置feed方法的情况下执行feeding的想法(对不起,我知道这个例子正在达到这一点,但是为了让争论起来,假装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);
}

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

3 个答案:

答案 0 :(得分:1)

修改
你可能会考虑这样的事情:

首先,你的动物就有一个界面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);
    }
}

答案 1 :(得分:1)

我认为AnimalCare的问题在于它本质上是Animal个实例的工厂,或者至少访问Animal存储库。因此,您的PickUp函数可能会返回Animal对象而不是特定类型。

或者,您是否可以将AnimalCare基于特定类型(AnimalCare<T>)?这个例子在实际意图方面很难衡量,如果这些想法似乎没有达到标准,那么道歉。

答案 2 :(得分:0)

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

例如:

interface IFeedable
{
    void Feed();
}

interface IMammal<TChild>
    where TChild: IMammal<TChild>
{
    IEnumerable<TChild> Reproduce();
}

class Dog: IFeedable, IMammal<Puppy>
{
    // ...
}

我一直使用接口(可能过度使用接口),特别是在这种情况下,正常继承可能不是最佳方式。