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:
class MyClass {
// Note: EventHandler is just a multicast delegate,
// that returns void and accepts (object sender, EventArgs e)!
public event EventHandler MyEvent;
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Le compilateur transforme cela en fait dans le code suivant:
class MyClass {
private EventHandler MyEvent = null;
public void add_MyEvent(EventHandler value) {
MyEvent += value;
}
public void remove_MyEvent(EventHandler value) {
MyEvent -= value;
}
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Vous vous abonnez ensuite à un événement en faisant
MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;
Qui se compile en
MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));
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:
interface RequiredMethods {
void DoX();
int DoY();
};
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:
sealed class RequiredMethods {
public Action DoX;
public Func<int> DoY();
}
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.