2010-01-18 6 views
0

Je travaille avec une hiérarchie de type smallish, quelque chose comme ce qui suit, et disons qu'il n'y aura jamais d'autres types d'animaux dans mon triste monde sans safari (I suis pas du tout inquiet de la résistance à l'expansion):Écrire une API générique pour un type 'famille'

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

J'aimerais traiter tous les animaux de la même aussi loin que l'API est concerné, mais il est évident qu'ils répondent un peu différemment dans les situations ci-dessous :

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

À première vue l'idée d'effectuer des tétées sans mettre un nourrir moi thod sur la classe des animaux (désolé, je sais que l'exemple est d'atteindre à ce stade, mais prétendons pour l'argument que AnimalCare est la vue de mes modèles animaux et je suis assez catégorique sur la séparation des préoccupations) suggérerait un visiteur . Mais ce que je voudrais vraiment faire est de laisser ci-dessus est la seule sorte de consommateurs API de besoin AnimalCare à se soucier, alors que je fais quelque chose comme ce qui suit:

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

Et ainsi de suite. Je sais que la première partie (les méthodes Feed()) est bien de surcharger sans même avoir besoin de génériques, mais ça me laisse toujours une implémentation maladroite pour PickUp (puisque ce n'est pas légal à implémenter comme je l'ai esquissé ci-dessus), misérable comme PickUpDog // PickUpBird etc Je voudrais vraiment éviter d'avoir une "vue" distincte pour les consommateurs d'AnimalCare et ses amis partageant les mêmes idées d'avoir à se préoccuper de.

J'ai joué avec des classes spécialisées imbriquées et d'autres tentatives bizarres de refactorisation de composition ou d'interface, mais je n'arrive pas à faire les choses correctement et je suis coincé. Y a-t-il une façon propre de faire quelque chose comme ce que je veux, ou suis-je résigné à implémenter un AnimalCare pour chaque Animal concret?

EDIT

points de Joel à propos de l'usine/dépôt m'a fait penser un peu plus. Appelons plutôt les méthodes d'intérêt un peu plus raisonnable:

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

Cela aurait fait plus de sens en premier lieu je suppose. Le type d'animal à sortir de PetStore n'est pas connu au moment où Get est appelé, mais dans l'implémentation générale de Get, une fois le type déterminé, il se branche sur la surcharge spécifique. Est-ce que les sous-types spécifiques de PetStore sont la meilleure/seule façon d'avancer ici?

Répondre

1

Je pense que le problème avec AnimalCare est qu'il est essentiellement une usine de Animal cas, ou au moins l'accès à un dépôt Animal . Donc, peut-être que votre fonction PickUp devrait renvoyer un objet Animal au lieu d'un type spécifique.

Vous ne pouvez pas non plus baser AnimalCare sur un type spécifique (AnimalCare<T>)? L'exemple est peu difficile à évaluer en termes d'intention réelle, donc excuses si ces idées ne semblent pas atteindre la cible.

1

modifier
Vous pourriez envisager quelque chose comme ceci:

D'abord, vous avez une interface IAnimal avec vos animaux là-bas. Ils ne connaissent aucune méthode. Ensuite, créez une nouvelle interface comme

Ensuite, créez des clients pour chaque type d'animal comme

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

Vos clients doivent ensuite être stockés dans une liste, et les types peuvent résider dans différents est vous etc. dll juste besoin d'attraper une référence.

Ensuite, il suffit

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

réponse originale
Fondamentalement, vous voulez mettre la responsabilité à l'animal lui-même, comme vous ne pouvez pas forcer votre AnimalCare classe à mettre en œuvre toutes les propriétés pour tous les animaux. Devient alors quelque chose de simple comme:

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

C'est ce dont j'avais peur. Exemple ridicule mis à part, je ne veux pas que la classe Animal ait à connaître les exigences de présentation ou de formatage de AnimalCare et d'autres classes. Tout ce dont j'ai besoin fait partie de l'API publique et j'espérais vraiment avoir un moyen d'avoir les implémentations concrètes dans la classe AnimalCare. –

+0

Vérifier mes modifications. L'implémentation concrète dans AnimalCare peut être en incorporant les classes qui héritent de IAnimalCareClient dans votre classe AnimalCare. –

0

Vous pouvez utiliser des interfaces, je ne sais pas quelles sont les exigences spécifiques, mais si un animal peut avoir un trait qui pourrait être partagé avec d'autres animaux, mais n'a pas d'interface est un bon début.

Par exemple:

interface IFeedable 
{ 
    void Feed(); 
} 

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

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

J'utilise des interfaces tout le temps (probablement les surutilisation), surtout dans un cas comme cela était l'héritage normal pourrait ne pas être la meilleure façon.