Was verstehen sie unter einem ereignisreichen Designmuster?
Sie beziehen sich höchstwahrscheinlich auf eine Implementierung des Beobachtermusters, das ein Kernsprachenkonstrukt in C # ist und als " Ereignisse " dargestellt wird. Das Abhören von Ereignissen ist möglich, indem ein Delegierter an sie angehängt wird. Wie Yam Marcovic hervorhob, EventHandler
ist dies der herkömmliche Basisdelegatentyp für Ereignisse, es kann jedoch jeder Delegatentyp verwendet werden.
Wie einfach gestaltet sich die Komposition, wenn ein Delegierter eingesetzt wird?
Dies bezieht sich wahrscheinlich nur auf das Flexibilitätsangebot der Delegierten. Sie können leicht ein bestimmtes Verhalten "komponieren". Mit Hilfe von Lambdas ist auch die Syntax dafür sehr prägnant. Betrachten Sie das folgende Beispiel.
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 );
Wenn Sie mit Schnittstellen etwas Ähnliches tun, müssen Sie entweder das Strategiemuster oder eine (abstrakte) Basisklasse verwenden Bunny
, von der aus Sie spezifischere Hasen erweitern.
Wenn es eine Gruppe verwandter Methoden gibt, die aufgerufen werden können, verwenden Sie die Schnittstelle. Welchen Nutzen hat sie?
Ich werde wieder Hasen benutzen, um zu demonstrieren, wie es einfacher wäre.
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();
...
Wenn eine Klasse nur eine Implementierung der Methode benötigt, verwenden Sie die Schnittstelle. Wie ist dies in Bezug auf den Nutzen gerechtfertigt?
Betrachten Sie dazu noch einmal das erste Beispiel mit dem Hasen. Wenn nur eine Implementierung erforderlich ist - es ist keine benutzerdefinierte Komposition erforderlich -, können Sie dieses Verhalten als Schnittstelle verfügbar machen. Sie müssen die Lambdas nie konstruieren, Sie können einfach die Schnittstelle verwenden.
Fazit
Delegierte bieten viel mehr Flexibilität, und Schnittstellen helfen Ihnen, starke Verträge abzuschließen. Daher finde ich den zuletzt genannten Punkt: "Eine Klasse benötigt möglicherweise mehr als eine Implementierung der Methode." , bei weitem die relevanteste.
Ein weiterer Grund für die Verwendung von Delegaten besteht darin, dass Sie nur einen Teil einer Klasse verfügbar machen möchten, aus der Sie die Quelldatei nicht anpassen können.
Betrachten Sie als Beispiel für ein solches Szenario (maximale Flexibilität, keine Notwendigkeit, Quellen zu ändern) diese Implementierung des binären Suchalgorithmus für jede mögliche Sammlung, indem Sie nur zwei Delegaten übergeben.
1) Eventing-Muster, auch der Klassiker ist das Observer-Muster, hier ist ein guter Microsoft-Link Microsoft Talk Observer
Der Artikel, auf den Sie verlinkt haben, ist nicht sehr gut geschrieben und macht die Dinge komplizierter als nötig. Das statische Geschäft ist vernünftig. Sie können keine Schnittstelle mit statischen Elementen definieren. Wenn Sie also ein polymorphes Verhalten für eine statische Methode wünschen, würden Sie einen Delegaten verwenden.
Der obige Link behandelt die Delegierten und die Gründe für die Komposition. Dies kann bei Ihrer Anfrage hilfreich sein.
quelle
Zuallererst eine gute Frage. Ich bewundere, dass Sie sich auf den Nutzen konzentrieren, anstatt blind "Best Practices" zu akzeptieren. +1 dafür.
Ich habe diesen Leitfaden schon einmal gelesen. Sie müssen sich an etwas erinnern - es ist nur eine Anleitung, hauptsächlich für C # -Neulinge, die wissen, wie man programmiert, aber nicht so vertraut mit der C # -Methode sind. Es ist nicht nur eine Seite mit Regeln, sondern eine Seite, die beschreibt, wie die Dinge normalerweise bereits erledigt werden. Und da sie bereits überall auf diese Weise ausgeführt werden, ist es möglicherweise eine gute Idee, konsequent zu bleiben.
Ich werde auf den Punkt kommen und Ihre Fragen beantworten.
Zunächst gehe ich davon aus, dass Sie bereits wissen, was eine Schnittstelle ist. Für einen Delegaten reicht es zu sagen, dass es sich um eine Struktur handelt, die einen typisierten Zeiger auf eine Methode sowie einen optionalen Zeiger auf das Objekt enthält, das das
this
Argument für diese Methode darstellt. Bei statischen Methoden ist der letztere Zeiger null.Es gibt auch Multicast-Delegaten, die genau wie Delegaten sind, denen jedoch möglicherweise mehrere dieser Strukturen zugewiesen sind (dh ein einzelner Aufruf zum Aufrufen eines Multicast-Delegaten ruft alle Methoden in der ihm zugewiesenen Aufrufliste auf).
Was verstehen sie unter einem ereignisreichen Designmuster?
Dies bedeutet, dass Ereignisse in C # verwendet werden (das über spezielle Schlüsselwörter verfügt, um dieses äußerst nützliche Muster zu implementieren). Ereignisse in C # werden von Multicast-Delegierten unterstützt.
Wenn Sie ein Ereignis definieren, wie in diesem Beispiel:
Der Compiler wandelt dies tatsächlich in den folgenden Code um:
Anschließend abonnieren Sie eine Veranstaltung mit
Welches kompiliert bis zu
Das ist Eventing in C # (oder .NET im Allgemeinen).
Wie einfach gestaltet sich die Komposition, wenn ein Delegierter eingesetzt wird?
Dies kann leicht demonstriert werden:
Angenommen, Sie haben eine Klasse, die von einer Reihe von Aktionen abhängt, die an sie übergeben werden sollen. Sie können diese Aktionen in eine Schnittstelle einbetten:
Und jeder, der Aktionen an Ihre Klasse übergeben wollte, musste diese Schnittstelle zuerst implementieren. Oder Sie könnten ihr Leben leichter machen, indem Sie von der folgenden Klasse abhängen:
Auf diese Weise müssen die Aufrufer nur eine Instanz von RequiredMethods erstellen und Methoden zur Laufzeit an die Delegaten binden. Dies ist normalerweise einfacher.
Diese Vorgehensweise ist unter den richtigen Umständen äußerst vorteilhaft. Denken Sie darüber nach - warum sollten Sie auf eine Schnittstelle angewiesen sein, wenn Sie sich nur für eine Implementierung interessieren?
Vorteile der Verwendung von Schnittstellen bei einer Gruppe verwandter Methoden
Es ist vorteilhaft, Schnittstellen zu verwenden, da Schnittstellen normalerweise explizite Implementierungen zur Kompilierungszeit erfordern. Dies bedeutet, dass Sie eine neue Klasse erstellen.
Und wenn Sie eine Gruppe verwandter Methoden in einem einzigen Paket haben, ist es vorteilhaft, wenn dieses Paket von anderen Teilen des Codes wiederverwendet werden kann. Wenn sie also einfach eine Klasse instanziieren können, anstatt eine Gruppe von Delegierten zu erstellen, ist das einfacher.
Vorteile der Verwendung von Schnittstellen, wenn eine Klasse nur eine Implementierung benötigt
Wie bereits erwähnt, werden Schnittstellen in der Kompilierungszeit implementiert. Dies bedeutet, dass sie effizienter sind als das Aufrufen eines Delegaten (was per se eine Indirektionsebene darstellt).
"Eine Implementierung" kann eine Implementierung bedeuten, die an einem einzigen genau definierten Ort vorhanden ist.
Andernfalls kann eine Implementierung von einer beliebigen Stelle im Programm kommen, die zufällig der Methodensignatur entspricht. Dies ermöglicht mehr Flexibilität, da Methoden nur der erwarteten Signatur entsprechen müssen, anstatt zu einer Klasse zu gehören, die explizit eine bestimmte Schnittstelle implementiert. Diese Flexibilität kann jedoch mit Kosten verbunden sein und sogar das Liskov-Substitutionsprinzip verletzen , da in den meisten Fällen explizite Aussagen erforderlich sind , da die Wahrscheinlichkeit von Unfällen minimiert wird. Genau wie beim statischen Tippen.
Der Begriff könnte sich hier auch auf Multicast-Delegierte beziehen. Von Interfaces deklarierte Methoden können nur einmal in einer implementierenden Klasse implementiert werden. Delegaten können jedoch mehrere Methoden akkumulieren, die nacheinander aufgerufen werden.
Alles in allem sieht es so aus, als ob der Leitfaden nicht informativ genug ist und lediglich als das fungiert, was er ist - ein Leitfaden, kein Regelwerk. Einige Ratschläge klingen tatsächlich etwas widersprüchlich. Es liegt an Ihnen, zu entscheiden, wann es richtig ist, was anzuwenden. Der Führer scheint uns nur einen allgemeinen Weg zu geben.
Ich hoffe, Ihre Fragen wurden zu Ihrer Zufriedenheit beantwortet. Und wieder ein dickes Lob für die Frage.
quelle
Wenn meine Erinnerung an .NET noch besteht, ist ein Delegat im Grunde eine Funktion ptr oder functor. Es fügt einem Funktionsaufruf eine Indirektionsebene hinzu, sodass Funktionen ersetzt werden können, ohne dass der aufrufende Code geändert werden muss. Dies ist dasselbe, was eine Schnittstelle tut, mit der Ausnahme, dass eine Schnittstelle mehrere Funktionen zusammenpackt und ein Implementierer sie zusammen implementieren muss.
Ein Ereignismuster ist im Großen und Ganzen ein Muster, bei dem etwas auf Ereignisse von außerhalb reagiert (z. B. Windows-Nachrichten). Die Ereignismenge ist in der Regel unbefristet, sie kann in beliebiger Reihenfolge erfolgen und muss nicht unbedingt miteinander in Beziehung stehen. Die Delegierten arbeiten gut daran, dass jedes Ereignis eine einzelne Funktion aufrufen kann, ohne dass ein Verweis auf eine Reihe von Implementierungsobjekten erforderlich ist, die möglicherweise auch zahlreiche irrelevante Funktionen enthalten. Außerdem (hier ist mein .NET-Speicher vage) können meiner Meinung nach mehrere Delegierte an ein Ereignis angehängt werden.
Compositing ist, obwohl ich mit dem Begriff nicht sehr vertraut bin, im Grunde genommen das Entwerfen eines Objekts mit mehreren Unterteilen oder aggregierten untergeordneten Objekten, an die die Arbeit weitergegeben wird. Die Delegierten gestatten es den Kindern, auf eine ad-hoc-Art und Weise gemischt und aufeinander abgestimmt zu werden, wenn eine Schnittstelle überladen ist oder zu viel Kopplung und die damit einhergehende Steifheit und Zerbrechlichkeit verursacht.
Der Vorteil einer Schnittstelle für verwandte Methoden besteht darin, dass die Methoden den Status des implementierenden Objekts gemeinsam nutzen können. Delegatfunktionen können den Status nicht so sauber freigeben oder sogar enthalten.
Wenn eine Klasse eine einzelne Implementierung benötigt, ist eine Schnittstelle besser geeignet, da in jeder Klasse, die die gesamte Auflistung implementiert, nur eine Implementierung durchgeführt werden kann und Sie die Vorteile einer implementierenden Klasse (Status, Kapselung usw.) nutzen können. Wenn sich die Implementierung aufgrund des Laufzeitstatus ändern könnte, funktionieren Delegaten besser, da sie für andere Implementierungen ausgetauscht werden können, ohne die anderen Methoden zu beeinträchtigen. Wenn beispielsweise drei Delegaten mit jeweils zwei möglichen Implementierungen vorhanden sind, benötigen Sie acht verschiedene Klassen, die die Schnittstelle mit drei Methoden implementieren, um alle möglichen Kombinationen von Zuständen zu berücksichtigen.
quelle
Mit dem Entwurfsmuster "Eventing" (besser bekannt als "Observer Pattern") können Sie dem Delegaten mehrere Methoden derselben Signatur hinzufügen. Das kann man mit einer Schnittstelle nicht wirklich machen.
Ich bin überhaupt nicht davon überzeugt, dass Komposition für einen Delegierten einfacher ist als eine Schnittstelle. Das ist eine sehr merkwürdige Aussage. Ich frage mich, ob er meint, weil Sie anonyme Methoden an einen Delegierten anhängen können.
quelle
Die größte Klarstellung, die ich anbieten kann:
So:
quelle
Ihre Frage zu Veranstaltungen wurde bereits ausführlich behandelt. Und es ist auch wahr, dass eine Schnittstelle kann mehrere Methoden definieren (aber eigentlich nicht muss), während ein Funktionstyp immer nur puts Einschränkungen auf einer individuellen Funktion.
Der wirkliche Unterschied ist jedoch:
Klar, aber was heißt das?
Nehmen wir folgendes Beispiel (Code ist in haXe, da mein C # nicht sehr gut ist):
Mit der Filtermethode ist es jetzt ganz einfach, nur einen kleinen Teil der Logik zu übergeben, der die interne Organisation der Sammlung nicht kennt, während die Sammlung nicht von der ihr zugewiesenen Logik abhängt. Groß. Abgesehen von einem Problem:
Die Sammlung hängt von der Logik ab. Die Auflistung geht von Natur aus davon aus, dass die übergebene Funktion einen Wert gegen eine Bedingung testet und den Erfolg des Tests zurückgibt. Beachten Sie, dass nicht alle Funktionen, die einen Wert als Argument verwenden und einen Booleschen Wert zurückgeben, bloße Prädikate sind. Zum Beispiel ist die Entfernungsmethode unserer Sammlung eine solche Funktion.
Angenommen, wir haben angerufen
c.filter(c.remove)
. Das Ergebnis wäre eine Sammlung mit allen Elementen vonc
whilec
selbst wird leer. Dies ist bedauerlich, da wir natürlich erwarten würden, dass esc
sich um eine Invariante handelt.Das Beispiel ist sehr konstruiert. Das Hauptproblem ist jedoch, dass der Code, der
c.filter
mit einem Funktionswert als Argument aufruft, nicht wissen kann, ob dieses Argument geeignet ist (dh letztendlich die Invariante konserviert). Der Code, der den Funktionswert erstellt hat, weiß möglicherweise, dass er als Prädikat interpretiert wird.Nun wollen wir die Dinge ändern:
Was hat sich verändert? Was sich geändert hat, ist, dass jeder Wert, dem jetzt beigemessen wird, den Vertrag als Prädikat ausdrücklich unterzeichnet
filter
hat . Natürlich erstellen böswillige oder extrem dumme Programmierer Implementierungen der Schnittstelle, die keine Nebenwirkungen haben und daher keine Prädikate sind. Was aber nicht mehr passieren kann, ist, dass jemand eine logische Einheit von Daten / Code bindet, die aufgrund ihrer äußeren Struktur fälschlicherweise als Prädikat interpretiert wird.Um das, was oben gesagt wurde, in wenigen Worten zu formulieren:
Der Vorteil expliziter Beziehungen ist, dass Sie sich dessen sicher sein können. Der Nachteil ist, dass sie expliziten Aufwand erfordern. Umgekehrt besteht der Nachteil impliziter Beziehungen (in unserem Fall stimmt die Signatur einer Funktion mit der gewünschten Signatur überein) darin, dass Sie nicht hundertprozentig sicher sein können, dass Sie die Dinge auf diese Weise verwenden können. Der Vorteil ist, dass Sie Beziehungen ohne den ganzen Aufwand aufbauen können. Man kann Dinge einfach schnell zusammenwerfen, weil ihre Struktur es erlaubt. Das ist, was einfache Komposition bedeutet.
Es ist ein bisschen wie bei LEGO: Man kann einfach eine Star Wars LEGO Figur auf ein LEGO Piratenschiff stecken, einfach weil es die äußere Struktur erlaubt. Jetzt könnten Sie das Gefühl haben, dass das schrecklich falsch ist, oder es könnte genau das sein, was Sie wollen. Niemand wird dich aufhalten.
quelle
int IList.Count { get { ... } }
) oder implizit (public int Count { get { ... } }
) implementiert werden . Diese Unterscheidung ist für diese Diskussion nicht relevant, sollte jedoch erwähnt werden, um die Leser nicht zu verwirren.quelle