Que signifient-ils par un modèle de design complet?
Ils se réfèrent très probablement à une implémentation du modèle d'observateur qui est une construction de langage de base en C #, exposée comme « événements ». L'écoute des événements est possible en y accrochant un délégué. Comme l'a souligné Yam Marcovic,EventHandler
s'agit du type de délégué de base conventionnel pour les événements, mais tout type de délégué peut être utilisé.
Comment la composition s'avère-t-elle facile si un délégué est utilisé?
Cela fait probablement référence à la flexibilité offerte par les délégués. Vous pouvez facilement «composer» certains comportements. Avec l'aide de lambdas , la syntaxe pour ce faire est également très concise. Prenons l'exemple suivant.
class Bunny
{
Func<bool> _canHop;
public Bunny( Func<bool> canHop )
{
_canHop = canHop;
}
public void Hop()
{
if ( _canHop() ) Console.WriteLine( "Hop!" );
}
}
Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );
Faire quelque chose de similaire avec les interfaces vous obligerait à utiliser le modèle de stratégie ou à utiliser une base (abstraite)Bunny
classe de partir de laquelle vous étendez des lapins plus spécifiques.
s'il y a un groupe de méthodes apparentées qui peuvent être appelées, alors utilisez l'interface - Quel est son avantage?
Encore une fois, je vais utiliser des lapins pour montrer comment ce serait plus facile.
interface IAnimal
{
void Jump();
void Eat();
void Poo();
}
class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }
// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump(); bunny.Eat(); bunny.Poo();
IAnimal chick = new Chick();
chick.Jump(); chick.Eat(); chick.Poo();
// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...
si une classe n'a besoin que d'une implémentation de la méthode, utiliser l'interface-comment est-elle justifiée en termes d'avantages?
Pour cela, considérons à nouveau le premier exemple avec le lapin. Si une seule implémentation est nécessaire - aucune composition personnalisée n'est jamais requise -, vous pouvez exposer ce comportement en tant qu'interface. Vous n'aurez jamais à construire les lambdas, vous pouvez simplement utiliser l'interface.
Conclusion
Les délégués offrent beaucoup plus de flexibilité, tandis que les interfaces vous aident à établir des contrats solides. Par conséquent, je trouve le dernier point mentionné, "Une classe peut nécessiter plusieurs implémentations de la méthode." , de loin le plus pertinent.
Une autre raison d'utiliser des délégués est lorsque vous souhaitez exposer uniquement une partie d'une classe dont vous ne pouvez pas ajuster le fichier source.
Comme exemple d'un tel scénario (flexibilité maximale, pas besoin de modifier les sources), considérez cette implémentation de l'algorithme de recherche binaire pour toute collection possible en passant simplement deux délégués.
1) Modèles d'événementiel, le classique est le modèle Observer, voici un bon lien Microsoft Microsoft Talk Observer
L'article auquel vous avez lié n'est pas très bien écrit et rend les choses plus compliquées que nécessaire. L'activité statique est du bon sens, vous ne pouvez pas définir une interface avec des membres statiques, donc si vous voulez un comportement polymorphe sur une méthode statique, vous utiliseriez un délégué.
Le lien ci-dessus parle des délégués et pourquoi ils sont bons pour la composition, donc cela pourrait aider votre requête.
la source
Tout d'abord, bonne question. J'admire votre concentration sur l'utilité plutôt que d'accepter aveuglément les «meilleures pratiques». +1 pour cela.
J'ai déjà lu ce guide. Vous devez vous souvenir de quelque chose à ce sujet - c'est juste un guide, principalement pour les nouveaux arrivants C # qui savent programmer mais ne sont pas si familiers avec la façon de faire C #. Ce n'est pas tant une page de règles qu'une page qui décrit comment les choses sont déjà faites habituellement. Et comme ils sont déjà effectués de cette façon partout, ce pourrait être une bonne idée de rester cohérent.
J'arrive au fait, répondant à vos questions.
Tout d'abord, je suppose que vous savez déjà ce qu'est une interface. Quant à un délégué, il suffit de dire que c'est une structure contenant un pointeur typé vers une méthode, ainsi qu'un pointeur optionnel vers l'objet représentant l'
this
argument de cette méthode. Dans le cas de méthodes statiques, ce dernier pointeur est nul.Il existe également des délégués de multidiffusion, qui sont exactement comme des délégués, mais plusieurs de ces structures peuvent leur être affectées (ce qui signifie qu'un seul appel à Invoke sur un délégué de multidiffusion appelle toutes les méthodes de la liste d'invocation qui lui est affectée).
Que signifient-ils par un modèle de design complet?
Ils signifient l'utilisation d'événements en C # (qui a des mots clés spéciaux pour implémenter de manière sophistiquée ce modèle extrêmement utile). Les événements en C # sont alimentés par des délégués de multidiffusion.
Lorsque vous définissez un événement, comme dans cet exemple:
Le compilateur transforme cela en fait dans le code suivant:
Vous vous abonnez ensuite à un événement en faisant
Qui se compile en
C'est donc un événement en C # (ou .NET en général).
Comment la composition s'avère-t-elle facile si un délégué est utilisé?
Cela peut facilement être démontré:
Supposons que vous ayez une classe qui dépend d'un ensemble d'actions à lui passer. Vous pouvez encapsuler ces actions dans une interface:
Et quiconque souhaitant passer des actions à votre classe devra d'abord implémenter cette interface. Ou vous pourriez leur faciliter la vie en fonction de la classe suivante:
De cette façon, les appelants n'ont qu'à créer une instance de RequiredMethods et à lier des méthodes aux délégués lors de l'exécution. C'est généralement plus facile.
Cette façon de faire est extrêmement bénéfique dans les bonnes circonstances. Pensez-y - pourquoi dépendre d'une interface alors que tout ce qui vous importe vraiment est de vous faire passer une implémentation?
Avantages de l'utilisation d'interfaces lorsqu'il existe un groupe de méthodes connexes
Il est avantageux d'utiliser des interfaces car les interfaces nécessitent normalement des implémentations explicites au moment de la compilation. Cela signifie que vous créez une nouvelle classe.
Et si vous avez un groupe de méthodes connexes dans un seul package, il est avantageux que ce package soit réutilisable par d'autres parties du code. Donc, s'ils peuvent simplement instancier une classe au lieu de construire un ensemble de délégués, c'est plus facile.
Avantages de l'utilisation d'interfaces si une classe n'a besoin que d'une implémentation
Comme indiqué précédemment, les interfaces sont implémentées au moment de la compilation - ce qui signifie qu'elles sont plus efficaces que d'appeler un délégué (qui est un niveau d'indirection en soi).
"Une implémentation" peut signifier une implémentation qui existe un seul endroit bien défini.
Sinon, une implémentation peut provenir de n'importe où dans le programme qui se trouve juste être conforme à la signature de la méthode. Cela permet plus de flexibilité, car les méthodes n'ont besoin que de se conformer à la signature attendue, plutôt que d'appartenir à une classe qui implémente explicitement une interface spécifique. Mais cette flexibilité peut avoir un coût, et rompt en fait le principe de substitution de Liskov , car la plupart du temps vous voulez une explication, car elle minimise les risques d'accidents. Tout comme la frappe statique.
Le terme peut également faire référence aux délégués de multidiffusion ici. Les méthodes déclarées par les interfaces ne peuvent être implémentées qu'une seule fois dans une classe d'implémentation. Mais les délégués peuvent accumuler plusieurs méthodes, qui seront appelées séquentiellement.
Donc, dans l'ensemble, il semble que le guide ne soit pas suffisamment informatif et fonctionne simplement comme ce qu'il est - un guide, pas un livre de règles. Certains conseils peuvent en fait sembler un peu contradictoires. C'est à vous de décider quand il convient d'appliquer quoi. Le guide semble seulement nous donner un chemin général.
J'espère que vos questions ont été répondues à votre satisfaction. Et encore une fois, bravo pour la question.
la source
Si ma mémoire de .NET tient toujours, un délégué est fondamentalement une fonction ptr ou functor. Il ajoute une couche d'indirection à un appel de fonction afin que les fonctions puissent être remplacées sans que le code appelant doive changer. C'est la même chose qu'une interface, sauf qu'une interface regroupe plusieurs fonctions ensemble, et qu'un implémenteur doit les implémenter ensemble.
Un modèle d'événement, d'une manière générale, est celui où quelque chose répond aux événements d'ailleurs (tels que les messages Windows). L'ensemble des événements est généralement ouvert, ils peuvent venir dans n'importe quel ordre et ne sont pas nécessairement liés les uns aux autres. Les délégués fonctionnent bien pour cela, car chaque événement peut appeler une seule fonction sans avoir besoin d'une référence à une série d'objets d'implémentation qui peuvent également contenir de nombreuses fonctions non pertinentes. De plus (c'est là que ma mémoire .NET est vague), je pense que plusieurs délégués peuvent être attachés à un événement.
Compositer, bien que je ne sois pas très familier avec le terme, consiste essentiellement à concevoir un objet pour avoir plusieurs sous-parties, ou des enfants agrégés auxquels le travail est transmis. Les délégués permettent aux enfants d'être mélangés et appariés d'une manière plus ponctuelle où une interface peut être exagérée ou provoquer trop de couplage et la rigidité et la fragilité qui l'accompagne.
L'avantage d'une interface pour les méthodes associées est que les méthodes peuvent partager l'état de l'objet d'implémentation. Les fonctions de délégué ne peuvent pas partager, ni même contenir, si proprement l'état.
Si une classe a besoin d'une seule implémentation, une interface est plus appropriée car dans n'importe quelle classe implémentant la collection entière, une seule implémentation peut être effectuée, et vous obtenez les avantages d'une classe implémentante (état, encapsulation, etc.). Si l'implémentation peut changer en raison de l'état d'exécution, les délégués fonctionnent mieux car ils peuvent être échangés contre d'autres implémentations sans affecter les autres méthodes. Par exemple, s'il y a trois délégués qui ont chacun deux implémentations possibles, vous auriez besoin de huit classes différentes implémentant l'interface à trois méthodes pour prendre en compte toutes les combinaisons possibles d'états.
la source
Le modèle de conception «événementiel» (mieux connu sous le nom de modèle d'observateur) vous permet d'attacher plusieurs méthodes de la même signature au délégué. Vous ne pouvez pas vraiment faire ça avec une interface.
Je ne suis pas du tout convaincu que la composition soit plus facile pour un délégué qu'une interface. C'est une déclaration très étrange. Je me demande s'il veut dire parce que vous pouvez attacher des méthodes anonymes à un délégué.
la source
La plus grande clarification que je puisse offrir:
Alors:
la source
Votre question sur les événements a déjà été bien couverte. Et il est également vrai qu'une interface peut définir plusieurs méthodes (mais en fait pas besoin), alors qu'un type de fonction ne fait que mettre des contraintes sur une fonction individuelle.
La vraie différence est cependant:
Bien sûr, mais qu'est-ce que cela signifie?
Prenons cet exemple (le code est dans haXe car mon C # n'est pas très bon):
Maintenant, la méthode de filtrage permet de passer facilement dans un petit morceau de logique, qui ne connaît pas l'organisation interne de la collection, tandis que la collection ne dépend pas de la logique qui lui est donnée. Génial. Sauf pour un problème:
La collection ne dépend de la logique. La collection suppose de manière inhérente que la fonction transmise est conçue pour tester une valeur par rapport à une condition et renvoyer le succès du test. Notez que toutes les fonctions, qui prennent une valeur en argument et renvoient un booléen ne sont en fait que de simples prédicats. Par exemple, la méthode remove de notre collection est une telle fonction.
Supposons que nous ayons appelé
c.filter(c.remove)
. Le résultat serait une collection avec tous les éléments dec
toutc
lui-même devient vide. C'est regrettable, car naturellement nous nous attendrionsc
à ce qu'il soit invariant.L'exemple est très construit. Mais le problème clé est que le code appelant
c.filter
avec une valeur de fonction comme argument n'a aucun moyen de savoir si cet argument est approprié (c'est-à-dire qu'il conservera finalement l'invariant). Le code qui a créé la valeur de la fonction peut ou non savoir qu'il sera interprété comme un prédicat.Maintenant, changeons les choses:
Qu'est ce qui a changé? Ce qui a changé, c'est que quelle que soit la valeur qui est désormais donnée, elle
filter
a explicitement signé le contrat d'être un prédicat. Bien sûr, des programmeurs malveillants ou extrêmement stupides créent des implémentations de l'interface qui ne sont pas exemptes d'effets secondaires et ne sont donc pas des prédicats.Mais ce qui ne peut plus se produire, c'est que quelqu'un attache une unité logique de données / code, qui, à tort, est interprété comme un prédicat en raison de sa structure externe.
Donc, pour reformuler ce qui a été dit ci-dessus en quelques mots:
L'avantage des relations explicites est que vous pouvez en être certain. L'inconvénient est qu'ils nécessitent des frais généraux d'explicitation. Inversement, l'inconvénient des relations implicites (dans notre cas, la signature d'une fonction correspondant à la signature souhaitée), c'est que vous ne pouvez pas vraiment être sûr à 100% que vous pouvez utiliser les choses de cette façon. L'avantage est que vous pouvez établir des relations sans tous les frais généraux. Vous pouvez simplement regrouper rapidement les choses, car leur structure le permet. Voilà ce que signifie une composition facile.
C'est un peu comme LEGO: vous pouvez simplement brancher une figurine LEGO Star Wars sur un bateau pirate LEGO, simplement parce que la structure extérieure le permet. Maintenant, vous pourriez sentir que c'est terriblement mal, ou ce pourrait être exactement ce que vous voulez. Personne ne va vous arrêter.
la source
int IList.Count { get { ... } }
) ou implicitement (public int Count { get { ... } }
). Cette distinction n'est pas pertinente pour cette discussion, mais elle mérite d'être mentionnée pour éviter de confondre les lecteurs.la source