web-dev-qa-db-fra.com

Points d'extension via héritage vs via des champs de délégués

En C # /. Net, j'ai une classe que je souhaite fournir des points d'extension. Je peux faire cela soit en utilisant l'héritage:

public class Animal {
    public virtual void Speak() { }
}
public class Dog : Animal {
    public overrides void Speak() => Console.WriteLine("Woof");
}
var dog = new Dog();
dog.Speak();

Ou en utilisant des délégués passés:

public class Animal {
    private Action speak;
    public Animal(Action speak) => this.speak = speak;
    public void Speak() => speak();
}
var dog = new Animal(() => Console.WriteLine("Woof"));
dog.Speak();

Je peux déjà voir des différences entre eux:

  • accès au comportement de base - Si par héritage, la méthode de dépassement peut choisir d'invoquer la méthode de base ou non; Si via des délégués, il n'y a pas d'accès automatique au comportement de base.
  • Peut y avoir un comportement? - Si via héritage, il y a toujours un comportement à Speak, soit le comportement de la classe de base, soit le comportement de classe dérivé. Lorsque vous utilisez des délégués, le champ délégué pourrait potentiellement contenir null (bien que avec des types de référence nullables, cela ne devrait pas arriver).
  • Définition explicite des données/membres scopés - Lors de l'extension par héritage, d'autres membres ou données définis dans la classe dérivée sont explicitement définis comme faisant partie d'une classe. Lorsque vous utilisez des délégués avec des expressions Lambda, les expressions Lambda peuvent accéder à la portée environnante, mais les parties de cette portée ne sont pas nécessairement définies explicitement comme telles (par exemple des variables fermées).

Quand est-il approprié d'exposer des points d'extension via héritage et quand est-il approprié d'utiliser des délégués?

3
Zev Spitz

Méfiez-vous: ce qui suit ne vous donne que une "règle de pointe" approximative, ce qui n'est sûrement pas un tutoriel complet sur la manière d'utiliser correctement l'héritage, mais vous pouvez l'utiliser comme premier "test Litmus" Pour ce que vous demandiez.

Pour créer des points d'extension comportementale, une approche classique populaire est le modèle de stratégie . Lorsque vous regardez le motif en totalité, vous y trouverez:

  • Un comportement abstrait (la stratégie) qui est injecté comme un délégué dans une classe de contexte. Dans votre exemple "animal", cela pourrait être une interface ou une classe de base abstraite ISpeakStrategy, avec des dérivations comme DogSpeakStrategy ou CatSpeakStrategy, où un objet ISpeakStrategy est injecté dans un objet de contexte Animal.

  • Héritage pour permettre l'extension de l'ensemble des stratégies disponibles, sans toucher aucun code existant.

Maintenant, il y a des situations où vous pourriez avoir besoin de points d'extension, mais en utilisant le modèle de stratégie de cette manière rend les choses plus complexes que nécessaire:

  1. lorsque l'interface de stratégie ne nécessite qu'une seule méthode et que le nom de l'interface n'est pas vraiment important, il suffit d'utiliser un seul délégué au lieu d'une hiérarchie héritarière à part entière souffrant de

  2. lorsqu'une séparation entre un objet "contexte" et une stratégie n'est pas requise, car la classe de contexte serait triviale/presque vide, il suffit d'utiliser une hiérarchie de héritage autonome suffit.

Pensez donc à la façon dont le point d'extension ressemblerait à la structure de stratégie, alors envisagez de laisser tout ce qui surcharge votre conception.

5
Doc Brown