代码之家  ›  专栏  ›  技术社区  ›  Matt Enright

为类型“family”编写通用API

  •  0
  • Matt Enright  · 技术社区  · 14 年前

    我正在处理一个小型的类型层次结构,类似于下面的内容,让我们假设在我悲伤的没有野生动物的世界里不会有任何其他的动物类型(我一点也不担心扩展的弹性):

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

    就原料药而言,我想对所有的动物都做同样的处理,但显然它们在以下情况下的反应略有不同:

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

    一开始,在动物类上不加饲料方法的情况下进行饲料喂养的想法会让人脸红(抱歉,我知道这个例子已经到了这个地步,但是为了讨论动物是我的动物模型的观点,我对关注的分离非常坚决)会建议一个访客。但是,我真正想做的是让上面的API成为AnimalCare的唯一需要担心的类型,而我要做的事情如下:

    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()方法)可以在不需要泛型的情况下超载,但这仍然会给我留下一个尴尬的pickuplog/pickupbird等实现方式(因为正如我上面所描述的那样,它是不合法的实现方式),我非常希望避免为消费提供单独的“视图”。动物研究所和它志同道合的朋友必须关心。

    我一直在玩嵌套的专门类和其他奇怪的组合或接口重构尝试,但我似乎不能得到正确的,我卡住了。有没有一个干净的方法来做我想做的事情,或者我是否已经听天由命地为每一个具体的动物实现一个动物模型?

    编辑

    乔尔关于工厂/仓库的观点让我想得更多。让我们把感兴趣的方法称为更合理一点:

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

    我想,这一点一开始就更合理了。在调用get时,不知道要从petstore中获取的动物类型,但是在get的一般实现中,一旦确定了类型,它就会分支到特定的重载。PetStore的特定子类型是否是前进的最佳/唯一途径?

    3 回复  |  直到 14 年前
        1
  •  1
  •   Joel Goodwin    14 年前

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

    或者,你能不能不在基地 动物保健 在特定类型上( AnimalCare<T> )?从实际意图来看,这个例子有点难以衡量,因此如果这些想法似乎没有达到目标,我们深表歉意。

        2
  •  1
  •   Jan Jongboom    14 年前

    编辑
    你可以考虑这样的事情:

    首先你有一个接口 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);
        }
    }
    
        3
  •  0
  •   Skurmedel    14 年前

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

    例如:

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

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