36 votes

Quelles sont les bonnes alternatives à l'héritage multiple dans .NET ?

J'ai rencontré un petit problème avec la hiérarchie de mes classes, dans une application WPF. Il s'agit d'un de ces problèmes où deux arbres d'héritage se rejoignent, et où vous ne trouvez pas de moyen logique pour que l'héritage fonctionne correctement. sans l'héritage multiple. Je me demande si quelqu'un a une idée brillante pour faire fonctionner ce genre de système, sans le rendre impossible à suivre ou à déboguer.

Je suis un ingénieur de bas niveau, donc ma première pensée est toujours, "Oh ! je vais juste écrire certaines de ces classes en C++ natif, et les référencer en externe ! Je pourrai alors m'amuser à la vieille école de l'OO". Hélas, cela n'aide pas lorsque vous avez besoin d'hériter de contrôles gérés...

Permettez-moi de vous montrer un extrait de mon diagramme de classe actuel :

 ____________________________________      _____________________________________
| CustomizableObject                 |    | System.Windows.Controls.UserControl |
|____________________________________|    |_____________________________________|
|   string XAMLHeader()              |                        ▲
|   string XAMLFooter()              |◄--┐                    |
|   CustomizableObject LoadObject()  |   \                    |
|   <Possible other implementations> |    \                   |
|____________________________________|     \                  |
         ▲                      ▲           \                 |
         |                      |            \                |
         |                      |             \               |
 _________________    ______________________   \    _____________________
| SpriteAnimation |  | SpriteAnimationFrame |  └---| CustomizableControl |
|_________________|  |______________________|      |_____________________|
                                                      ▲             ▲
                                                      |             |
                                                      |             |
                                                  ________    _____________
                                                 | Sprite |  | SpriteFrame |
                                                 |________|  |_____________|

Le problème est assez clair : la séparation des arbres d'objets CustomizableObject et CustomizableControl --- et l'insertion de UserControl dans l'un de ces arbres, mais pas dans les deux.

Il n'y a aucun sens pratique à déplacer l'implémentation de CustomizableObject dans ses classes dérivées, puisque l'implémentation ne varie pas par classe. De plus, il serait vraiment déroutant de l'avoir implémentée plusieurs fois. Donc je ne veux vraiment pas faire de CustomizableObject une interface. La solution de l'interface n'a aucun sens pour moi. (Les interfaces ont jamais n'a pas vraiment eu de sens pour moi, pour être honnête...)

Donc je le répète, quelqu'un a une idée brillante ? Cette fois-ci, je suis vraiment dans le pétrin. J'aimerais en apprendre davantage sur la façon de faire fonctionner les interfaces AVEC mon arbre d'objets, plutôt que contre lui. Je fais ce simple moteur de sprites en utilisant WPF et C# comme un exercice solide, plus qu'autre chose. C'est tellement facile à résoudre en C++ - mais je dois trouver comment résoudre ces problèmes dans un environnement géré, plutôt que de jeter mes mains en l'air et de revenir à Win32 dès que les choses se compliquent.

29voto

Ed S. Points 70246

Vous avez deux possibilités : utiliser des interfaces ou utiliser la composition. Honnêtement, les interfaces sont très puissantes, et après avoir lu cette ligne

La solution de l'interface n'a aucun sens pour moi. (Les interfaces n'ont jamais eu beaucoup de sens pour moi, pour être honnête...)

Je pense que tu devrais apprendre à les utiliser correctement. Cela dit, s'il y a simplement une certaine logique dont plusieurs classes ont besoin, mais qu'il n'est pas logique que ces classes héritent de la même classe de base, créez simplement une classe pour encapsuler cette logique et ajoutez une variable membre de cette classe à vos classes qui vous posent problème. De cette façon, toutes les classes contiennent la logique mais peuvent être séparées dans leurs hiérarchies d'héritage. Si les classes doivent implémenter une ou plusieurs interfaces communes, utilisez les interfaces.

17voto

dahlbyk Points 24897

Une approche consiste à utiliser des méthodes d'extension avec une interface pour fournir l'implémentation de votre "classe dérivée", un peu comme System.Linq.Queryable :

interface ICustomizableObject
{
    string SomeProperty { get; }
}

public static class CustomizableObject
{
    public static string GetXamlHeader(this ICustomizableObject obj)
    {
        return DoSomethingWith(obj.SomeProperty);
    }

    // etc
}

public class CustomizableControl : System.Windows.Controls.UserControl, ICustomizableObject
{
    public string SomeProperty { get { return "Whatever"; } }
}

Utilisation : Tant que vous avez une directive using pour (ou que vous êtes dans le même espace de noms que) l'espace de noms où vos méthodes d'extension sont définies :

var cc = new CustomizableControl();
var header = cc.GetXamlHeader();

7voto

Joel Coehoorn Points 190579

Je regarde ça, et CustomizableObject ne fait que hurler pour être transformé en interface (et puisque chaque type concret est convertible en objet, cette partie du nom est redondante). Le problème que vous rencontrez est que vous ne savez pas comment préserver une logique de base qui sera partagée ou ne variera que légèrement selon l'implémentation, et vous voulez stocker cette logique dans l'arbre lui-même afin qu'il fonctionne de manière polymorphe (est-ce un mot ?).

Vous pouvez y parvenir grâce aux délégués. Je ne sais pas exactement quels membres vous posent problème, mais peut-être quelque chose comme ceci :

 ____________________________________      _____________________________________
| ICustomizable                      |    | System.Windows.Controls.UserControl |
|                                    |    |_____________________________________|
|   Func<string> XAMLHeader;         |                        ▲
|   Func<string> XAMLFooter          |◄--┐                    |
|   ICustomizabl LoadObject() |   \                    |
|   <Possible other implementations> |    \                   |
|____________________________________|     \                  |
         ▲                      ▲           \                 |
         |                      |            \                |
         |                      |             \               |
 _________________    ______________________   \    _____________________
| SpriteAnimation |  | SpriteAnimationFrame |  └---| CustomizableControl |
|_________________|  |______________________|      |_____________________|
                                                      ▲             ▲
                                                      |             |
                                                      |             |
                                                  ________    _____________
                                                 | Sprite |  | SpriteFrame |
                                                 |________|  |_____________|

De plus, vous avez probablement une logique qui est vraiment statique et que vous sentez vraiment appartient à avec votre type CustomizableObject. Mais ceci est probablement faux : vous avez construit le type avec l'intention d'utiliser ce type dans une situation spécifique. Par exemple, d'après le contexte, il semble que vous allez créer ces contrôles et animations et les utiliser dans un formulaire Windows. La chose à faire est d'avoir votre propre formulaire qui hérite de la base System.Windows.Form, et ce nouveau type de formulaire devrait connaître ICustomizableObject et savoir comment l'utiliser. C'est là qu'ira votre logique statique.

Cela semble un peu maladroit, mais cela s'avère exact lorsque vous décidez de changer de moteur de présentation. Que se passe-t-il si vous portez ce code vers WPF ou Silverlight ? Ils devront probablement utiliser votre code d'implémentation un peu différemment de celui de Windows Forms, et vous devrez probablement modifier vos implémentations de CustomizableControl. Mais votre logique statique se trouve maintenant exactement au bon endroit.

Enfin, la méthode LoadObject() que vous utilisez ne me semble pas non plus être au bon endroit. Vous dites que vous voulez que chaque type Customizable fournisse une méthode que vous pouvez appeler et qui sait comment se charger/construire. Mais c'est vraiment quelque chose de différent. Vous pourriez vouloir encore une autre interface nommée IConstructable<T> et que votre type ICustomizable l'implémente ( IConstructable<ICustomizable> ).

2voto

Stefan Papp Points 418

J'utilise les mixins dans un tel cas. Les mixins sont un concept puissant utilisé dans de nombreux langages pour ajouter des fonctionnalités à une classe au moment de l'exécution. Les mixins sont bien connus dans de nombreux langages. Le cadre re-mix est un cadre qui apporte la technologie des mixins à .NET.

L'idée est simple : Décorez votre classe avec un attribut de classe qui représente un mixin. Au moment de l'exécution, cette fonctionnalité sera ajoutée à votre classe. Ainsi, vous pouvez émuler l'héritage multiple.

La solution à votre problème pourrait être de faire de CustomizableObject un mixin. Vous aurez besoin du framework re-mix pour cela.

[Utilise(CustomizableObjectMixin)] CustomizableControl : UserControl

Veuillez consulter le site remix.codeplex.com pour plus de détails.

1voto

xanadont Points 2723

On dirait que vous allez devoir recourir aux interfaces et à la composition d'objets.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X