views:

92

answers:

3

I'm working with a smallish type hierarchy, something like the following, and lets say there won't ever be any other Animal types in my sad safari-less world (I'm not at all worried about resilience to expanding):

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

I'd like to treat all of the animals similarly as far as the API is concerned, but obviously they respond just a little differently in the below situations:

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

At first blush the idea of performing feedings without putting a feed method on the Animal class (sorry, I know the example is reaching at this point, but lets pretend for the sake of argument that AnimalCare is the view to my Animal models and I'm pretty adamant about separation of concerns) would suggest a visitor. But what I would really like to do is let the above be the only sort of API consumers of AnimalCare need to worry about, while I do something like the following:

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.
}

And so on. I know the first part (the Feed () methods) are fine to just overload without even needing generics, but that still leaves me with an awkward implementation for PickUp (since its not legal to implement as I've sketched it above), something miserable like PickUpDog//PickUpBird etc. I would very much like to avoid having a separate "view" for consumers of AnimalCare and its like-minded friends to have to be concerned with.

I've been playing around with nesting specialized classes and other bizarre attempts at composition or interface refactoring, but I can't seem to get it right and I'm stuck. Is there a clean way to do something like what I want, or am I resigned to implementing an AnimalCare for each concrete Animal?

EDIT

Joel's point about factory/repository made me think some more. Let's instead call the methods of interest a little more reasonable:

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

That would have made more sense in the first place I suppose. The type of Animal to be gotten out of PetStore isn't known at the time Get is called, but in the general implementation of Get, once the type is determined, it branches to the specific overload. Is specific subtypes of PetStore the best/only way forward here?

+1  A: 

edit
You might consider something like this:

First you have an interface IAnimal with your animals on there. They don't know about any methods. Then create a new interface like

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

Then create clients for every animal type like

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

Your clients should then be stored in some list, and the types can reside in various dll's etc. You just need to grab a reference.

Then just

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

original answer
Basically you want to lay the responsibility at the animal itself, as you cannot force your AnimalCare class to implement all properties for all animals. Then becomes something simple like:

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);
    }
}
Jan Jongboom
That's what I was afraid of. Ridiculous example aside, I don't want the Animal class to have to have knowledge of the presentation or formatting requirements of the AnimalCare and other classes. Everything I need is part of the public API and I was really hoping for a way to have the concrete implementations in the AnimalCare class.
Matt Enright
Check my edit. Concrete implementation in AnimalCare can be by embedding the classes that inherit IAnimalCareClient<T> in your AnimalCare class.
Jan Jongboom
+1  A: 

I think the problem with AnimalCare is that it is essentially a factory for Animal instances, or at least access to an Animal repository. So maybe your PickUp function should return an Animal object instead of a specific type.

Alternatively, could you not base AnimalCare on a specific type (AnimalCare<T>) ? The example is little hard to gauge in terms of actual intention, so apologies if these ideas don't seem to hit the mark.

Joel Goodwin
A: 

You can use interfaces, I don't know what specific requirements there is, but if an animal may have one trait that might be shared with other animals, but doesn't have to an interface is a good start.

For example:

interface IFeedable
{
    void Feed();
}

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

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

I use interfaces all the time (probably overusing them), especially in a case as this were normal inheritance might not be the best way.

Skurmedel