Написание универсального API для типа «семья» - PullRequest
0 голосов
/ 18 января 2010

Я работаю с небольшой иерархией типов, что-то вроде следующего, и допустим, что в моем печальном мире без сафари никогда не будет других типов животных (меня совсем не беспокоит устойчивость к расширению ):

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 (извините, я знаю, что пример подходит к этому моменту, но давайте притворяться, что аргумент в пользу AnimalCare - это представление моих моделей Animal). и я довольно непреклонен в разделении проблем) предложил бы посетителю. Но то, что я действительно хотел бы сделать, это позволить приведенному выше быть единственным видом 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 ()) можно просто перегрузить, даже не требуя обобщений, но это все еще оставляет меня с неловкой реализацией для PickUp (так как ее нелегально реализовать, как я нарисовал выше), что-то такие жалкие, как PickUpDog // PickUpBird и т. д. Мне бы очень хотелось, чтобы у потребителей AnimalCare и его друзей-единомышленников не было отдельного «представления» о том, о чем следует беспокоиться.

Я играл с вложенными специализированными классами и другими странными попытками реорганизации композиции или интерфейса, но я не могу понять, что это правильно, и я застрял. Есть ли чистый способ сделать что-то наподобие того, что я хочу, или я смирился с внедрением AnimalCare для каждого конкретного Animal?

EDIT

Точка зрения Джоэла о фабрике / хранилище заставила меня задуматься. Давайте вместо этого назовем интересующие нас методы более разумными:

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

Это было бы более разумно, во-первых, я полагаю. Тип Animal, который нужно получить из PetStore, неизвестен во время вызова Get, но в общей реализации Get, когда тип определен, он переходит к определенной перегрузке. Являются ли определенные подтипы PetStore лучшим / единственным способом продвижения вперед?

Ответы [ 3 ]

1 голос
/ 18 января 2010

Я думаю, что проблема с AnimalCare состоит в том, что это, по сути, фабрика для Animal экземпляров или, по крайней мере, доступ к репозиторию Animal. Поэтому, возможно, ваша PickUp функция должна возвращать объект Animal вместо определенного типа.

В качестве альтернативы, не могли бы вы основать AnimalCare на конкретном типе (AnimalCare<T>)? Пример немного сложен для оценки с точки зрения фактического намерения, поэтому извиняюсь, если эти идеи, похоже, не достигли цели.

1 голос
/ 18 января 2010

редактировать
Вы можете рассмотреть что-то вроде этого:

Сначала у вас есть интерфейс 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);
    }
}
0 голосов
/ 18 января 2010

Вы можете использовать интерфейсы, я не знаю, какие особые требования существуют, но если у животного может быть одна черта, которая может быть общей с другими животными, но не имеющей интерфейса, это хорошее начало.

Например:

interface IFeedable
{
    void Feed();
}

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

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

Я использую интерфейсы все время (возможно, злоупотребляя ими), особенно в том случае, если это нормальное наследование, возможно, не лучший способ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...