Réduction du passe-partout dans la classe qui implémente des interfaces via la composition

11

J'ai une classe: Aqui est un composé d'un certain nombre de classes plus petites, B, Cet D.

B, CEt Dmettre en œuvre des interfaces IB, ICet IDrespectivement.

Depuis Aprend en charge toutes les fonctionnalités de B, Cet D, Aimplémente IB, ICainsi IDque, mais cela conduit malheureusement à beaucoup de réacheminement dans la mise en œuvre deA

Ainsi:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

Existe-t-il un moyen de se débarrasser de cette redirection de passe-partout A? B, Cet Dtous implémentent des fonctionnalités différentes mais communes, et j'aime que cela Aimplémente IB, ICet IDparce que cela signifie que je peux passer en Asoi en tant que dépendance correspondant à ces interfaces, et ne pas avoir à exposer les méthodes d'assistance internes.

Nick Udell
la source
Pourriez-vous dire un peu pourquoi vous avez besoin d'une classe A pour implémenter IB, IC, ID? Pourquoi ne pas simplement exposer BCD en public?
Esben Skov Pedersen
1
Je pense que ma principale raison de préférer la Amise en œuvre IB, ICet IDc'est qu'il semble plus sensé d'écrire Person.Walk()plutôt que Person.WalkHelper.Walk(), par exemple.
Nick Udell

Réponses:

7

Ce que vous recherchez est communément appelé mixins . Malheureusement, C # ne les prend pas nativement en charge.

Il existe peu de solutions de contournement: un , deux , trois et bien d'autres.

En fait, j'aime vraiment le dernier. L'idée d'utiliser une classe partielle générée automatiquement pour générer le passe-partout est probablement la solution la plus proche que vous pouvez trouver:

[pMixins] est un plug-in Visual Studio qui analyse une solution pour les classes partielles décorées avec des attributs pMixin. En marquant votre classe comme partielle, [pMixins] peut créer un fichier code-behind et ajouter des membres supplémentaires à votre classe

Euphorique
la source
2

Bien qu'il ne réduise pas le passe-partout dans le code lui-même, Visual Studio 2015 est désormais livré avec une option de refactoring pour générer automatiquement le passe-partout pour vous.

Pour l'utiliser, créez d'abord votre interface IExampleet votre implémentation Example. Créez ensuite votre nouvelle classe Compositeet faites-la hériter IExample, mais n'implémentez pas l'interface.

Ajoutez une propriété ou un champ de type Exampleà votre Compositeclasse, ouvrez le menu d'actions rapides sur le jeton IExampledans votre Compositefichier de classe et sélectionnez «Implémenter l'interface via« Exemple »» où «Exemple» dans ce cas est le nom du champ ou de la propriété.

Vous devez noter que même si Visual Studio générera et redirigera pour vous toutes les méthodes et propriétés définies dans l'interface vers cette classe d'assistance, il ne redirigera pas les événements au moment où cette réponse a été publiée.

Nick Udell
la source
1

Votre classe en fait trop, c'est pourquoi vous devez implémenter autant de passe-partout. Vous dites dans les commentaires:

il semble plus judicieux d'écrire Person.Walk () par opposition à Person.WalkHelper.Walk ()

Je ne suis pas d'accord. L'acte de marcher est susceptible d'impliquer de nombreuses règles et n'appartient pas à la Personclasse - il appartient à une WalkingServiceclasse avec une walk(IWalkable)méthode où Personne met en œuvre IWalkable.

Gardez toujours à l'esprit les principes SOLID lorsque vous écrivez du code. Les deux plus applicables ici sont la séparation des préoccupations (extraire le code de marche vers une classe distincte) et la séparation des interfaces (diviser les interfaces en tâches / fonctions / capacités spécifiques). Il semble que vous puissiez avoir une partie du I avec vos multiples interfaces, mais que vous annulez ensuite tout votre bon travail en faisant en sorte qu'une seule classe les implémente.

Stuart Leyland-Cole
la source
Dans mon exemple, la fonctionnalité est implémentée dans des classes distinctes, et j'ai une classe composée de quelques-unes de ces méthodes pour regrouper le comportement requis. Aucune implémentation réelle n'existe dans ma plus grande classe, elle est entièrement gérée par les plus petits composants. Peut-être avez-vous raison cependant, et rendre ces classes d'assistance publiques afin qu'elles puissent être directement interagies avec est la voie à suivre.
Nick Udell
4
Quant à l'idée d'une walk(IWalkable)méthode, je ne l'aime pas personnellement car alors les services de marche deviennent la responsabilité de l'appelant, et non de la personne, et la cohérence n'est pas garantie. Tout appelant devrait savoir quel service utiliser pour garantir la cohérence, alors que le conserver dans la classe Person signifie qu'il doit être modifié manuellement pour que le comportement de marche diffère, tout en permettant l'inversion des dépendances.
Nick Udell
0

Ce que vous recherchez, c'est un héritage multiple. Cependant, ni C # ni Java ne l'ont.

Vous pouvez étendre B à partir de A. Cela éliminera le besoin d'un champ privé pour B ainsi que la colle de redirection. Mais vous ne pouvez le faire que pour l'un des B, C ou D.

Maintenant, si vous pouvez faire en sorte que C hérite de B, et D de C, alors il vous suffit d'avoir A étendre D ...;) - juste pour être clair cependant, je ne l'encourage pas vraiment dans cet exemple car il n'y a aucune indication pour ça.

En C ++, vous seriez en mesure de faire hériter A de B, C et D.

Mais l'héritage multiple rend les programmes plus difficiles à raisonner et a ses propres problèmes; De plus, il existe souvent un moyen propre de l'éviter. Pourtant, parfois sans cela, il faut faire des compromis de conception entre la répétition du passe-partout et la façon dont le modèle d'objet est exposé.

C'est un peu comme si vous essayez de mélanger composition et héritage. S'il s'agissait de C ++, vous pourriez utiliser une solution d'héritage pure. Mais comme ce n'est pas le cas, je recommanderais d'adopter la composition.

Erik Eidt
la source
2
Vous pouvez avoir plusieurs héritages en utilisant des interfaces à la fois en java et en c #, tout comme l'op l'a fait dans son exemple de code.
Robert Harvey
1
Certains peuvent envisager d'implémenter une interface de la même manière que l'héritage multiple (comme en C ++ par exemple). D'autres ne seraient pas d'accord, car les interfaces ne peuvent pas présenter de méthodes d'instance héritables, ce que vous auriez si vous avez plusieurs héritages. En C #, vous ne pouvez pas étendre plusieurs classes de base, par conséquent, vous ne pouvez pas hériter des implémentations de méthode d'instance de plusieurs classes, c'est pourquoi vous avez besoin de tout le passe-partout ...
Erik Eidt
Je conviens que l'héritage multiple est mauvais, mais l'alternative de C # / Java à l'héritage unique avec une implémentation d'interfaces multiples n'est pas un remède (sans le transfert d'interface, c'est un cauchemar ergonomique). Après avoir passé du temps avec Golang, je pense que Go a la bonne idée de ne pas avoir d' héritage et de s'appuyer entièrement sur la composition avec le transfert d'interface - mais leur implémentation particulière a également des problèmes. Je pense que la meilleure solution (que personne ne semble avoir encore implémentée) est la composition, avec transfert, avec la possibilité d'utiliser n'importe quel type comme interface.
Dai