Delegate vs Interfaces - Gibt es weitere Erläuterungen?

23

Nach dem Lesen des Artikels - Verwendung von Stellvertretern anstelle von Schnittstellen (C # -Programmierhandbuch ) - benötige ich Unterstützung beim Verstehen der folgenden Punkte, die ich (für mich) als nicht so klar befunden habe. Gibt es Beispiele oder detaillierte Erklärungen dazu?

Verwenden Sie einen Delegaten, wenn:

  • Ein Eventing-Entwurfsmuster wird verwendet.
  • Es ist wünschenswert, eine statische Methode einzukapseln.
  • Eine einfache Zusammensetzung ist erwünscht.
  • Eine Klasse benötigt möglicherweise mehr als eine Implementierung der Methode.

Verwenden Sie eine Schnittstelle, wenn:

  • Es gibt eine Gruppe verwandter Methoden, die aufgerufen werden können.
  • Eine Klasse benötigt nur eine Implementierung der Methode.

Meine Fragen sind,

  1. Was verstehen sie unter einem ereignisreichen Designmuster?
  2. Wie einfach gestaltet sich die Komposition, wenn ein Delegierter eingesetzt wird?
  3. Wenn es eine Gruppe verwandter Methoden gibt, die aufgerufen werden können, verwenden Sie die Schnittstelle. Welchen Nutzen hat sie?
  4. Wenn eine Klasse nur eine Implementierung der Methode benötigt, verwenden Sie die Schnittstelle. Wie ist dies in Bezug auf den Nutzen gerechtfertigt?
WinW
quelle

Antworten:

11

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, EventHandlerist 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.

Steven Jeuris
quelle
12

Ein Delegat ist so etwas wie eine Schnittstelle für eine einzelne Methodensignatur, die nicht explizit wie eine reguläre Schnittstelle implementiert werden muss. Sie können es auf der Flucht konstruieren.

Eine Schnittstelle ist nur eine Sprachstruktur, die einen Vertrag darstellt - "Ich garantiere hiermit, dass ich die folgenden Methoden und Eigenschaften zur Verfügung stelle".

Ich stimme auch nicht vollständig zu, dass Delegierte in erster Linie nützlich sind, wenn sie als Lösung für das Beobachter- / Abonnentenmuster verwendet werden. Es ist jedoch eine elegante Lösung für das "Java-Ausführlichkeitsproblem".

Für Ihre Fragen:

1 & 2)

Wenn Sie ein Ereignissystem in Java erstellen möchten, verwenden Sie normalerweise eine Schnittstelle, um das Ereignis zu verbreiten.

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

Dies bedeutet, dass Ihre Klasse alle diese Methoden explizit implementieren und Stubs für alle Methoden bereitstellen muss, auch wenn Sie sie nur implementieren möchten KeyPress(int key).

In C # würden diese Ereignisse als Delegatenlisten dargestellt, die durch das Schlüsselwort "event" in c # ausgeblendet werden, eine für jedes einzelne Ereignis. Dies bedeutet, dass Sie einfach abonnieren können, was Sie möchten, ohne Ihre Klasse mit öffentlichen "Schlüssel" -Methoden usw. zu belasten.

+1 für die Punkte 3-5.

Zusätzlich:

Stellvertreter sind sehr nützlich, wenn Sie beispielsweise eine "Zuordnungs" -Funktion bereitstellen möchten, die eine Liste erstellt und jedes Element in eine neue Liste projiziert, mit der gleichen Anzahl von Elementen, die sich jedoch in irgendeiner Weise unterscheiden. Im Wesentlichen IEnumerable.Select (...).

IEnumerable.Select akzeptiert a Func<TSource, TDest>, einen Delegaten, der eine Funktion umschließt, die ein TSource-Element verwendet und dieses Element in ein TDest-Element umwandelt.

In Java müsste dies über eine Schnittstelle implementiert werden. Es gibt oft keinen natürlichen Ort, um eine solche Schnittstelle zu implementieren. Wenn eine Klasse eine Liste enthält, die auf irgendeine Weise transformiert werden soll, ist es nicht ganz selbstverständlich, dass sie die Schnittstelle "ListTransformer" implementiert, zumal es möglicherweise zwei verschiedene Listen gibt, die auf unterschiedliche Weise transformiert werden sollten.

Natürlich können Sie auch anonyme Klassen verwenden, die ein ähnliches Konzept haben (in Java).

Max
quelle
Denken Sie daran, Ihren Beitrag zu bearbeiten, um seine Fragen tatsächlich zu beantworten
Yam Marcovic
@YamMarcovic Du hast recht, ich bin ein bisschen in freier Form herumgewandert, anstatt seine Fragen direkt zu beantworten. Trotzdem denke ich, dass es ein bisschen über die Mechanik erklärt. Außerdem waren seine Fragen etwas weniger gut formuliert, als ich die Frage beantwortete. : p
Max
Natürlich. Passiert mir auch, und es hat seinen Wert an sich. Deshalb habe ich es nur vorgeschlagen , mich aber nicht darüber beschwert. :)
Yam Marcovic
3

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.

Ian
quelle
3

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 thisArgument 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:

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
  }
}

Der Compiler wandelt dies tatsächlich in den folgenden Code um:

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
  }
}

Anschließend abonnieren Sie eine Veranstaltung mit

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

Welches kompiliert bis zu

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

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:

interface RequiredMethods {
  void DoX();
  int DoY();
};

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:

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

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.

Yam Marcovic
quelle
2

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.

kylben
quelle
1

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.

pdr
quelle
1

Die größte Klarstellung, die ich anbieten kann:

  1. Ein Delegat definiert eine Funktionssignatur - welche Parameter eine übereinstimmende Funktion akzeptiert und was sie zurückgibt.
  2. Eine Schnittstelle behandelt eine ganze Reihe von Funktionen, Ereignissen, Eigenschaften und Feldern.

So:

  1. Wenn Sie einige Funktionen mit derselben Signatur verallgemeinern möchten, verwenden Sie einen Delegaten.
  2. Wenn Sie das Verhalten oder die Qualität einer Klasse verallgemeinern möchten, verwenden Sie eine Schnittstelle.
John Fisher
quelle
1

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:

  • Funktionswerte werden durch strukturelle Untertypen (dh implizite Kompatibilität mit der angeforderten Struktur) an Funktionstypen angepasst . Das heißt, wenn ein Funktionswert eine Signatur hat, die mit dem Funktionstyp kompatibel ist, ist dies ein gültiger Wert für den Typ.
  • Instanzen werden durch nominelle Subtypisierung (dh explizite Verwendung eines Namens) an Schnittstellen angepasst . Das heißt, wenn eine Instanz Mitglied einer Klasse ist, die die angegebene Schnittstelle explizit implementiert, ist die Instanz ein gültiger Wert für die Schnittstelle. Wenn für das Objekt jedoch nur alle von den Schnittstellen angeforderten Mitglieder und alle Mitglieder kompatible Signaturen haben, die Schnittstelle jedoch nicht explizit implementieren, ist dies kein gültiger Wert für die Schnittstelle.

Klar, aber was heißt das?

Nehmen wir folgendes Beispiel (Code ist in haXe, da mein C # nicht sehr gut ist):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

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 von cwhilecselbst wird leer. Dies ist bedauerlich, da wir natürlich erwarten würden, dass es csich um eine Invariante handelt.

Das Beispiel ist sehr konstruiert. Das Hauptproblem ist jedoch, dass der Code, der c.filtermit 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:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

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 filterhat . 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:

  • Schnittstellen bedeuten die Verwendung von nominaler Typisierung und damit expliziter Beziehungen
  • Delegierte verwenden strukturelle Typisierung und damit implizite Beziehungen

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.

back2dos
quelle
1
Es ist erwähnenswert, dass "das Interface explizit implementiert" (unter "nominal subtyping") nicht mit der expliziten oder impliziten Implementierung von Interface-Mitgliedern identisch ist. In C # müssen Klassen die von ihnen implementierten Schnittstellen immer explizit deklarieren, wie Sie sagen. Schnittstellenelemente können jedoch entweder explizit (z. B. 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.
Phoog
@phoog: Ja, danke für die Änderung. Ich wusste nicht einmal, dass der erste möglich war. Aber ja, die Art und Weise, wie eine Sprache die Schnittstellenimplementierung tatsächlich erzwingt, ist sehr unterschiedlich. In Objective-C wird beispielsweise nur eine Warnung ausgegeben, wenn diese nicht implementiert ist.
back2dos
1
  1. Ein Eventing-Entwurfsmuster impliziert eine Struktur, die einen Publish- und Subscribe-Mechanismus umfasst. Ereignisse werden von einer Quelle veröffentlicht, jeder Abonnent erhält eine eigene Kopie des veröffentlichten Elements und ist für seine eigenen Aktionen verantwortlich. Das ist ein lose gekoppelter Mechanismus, da der Verlag nicht einmal wissen muss, dass die Abonnenten da sind. Von Abonnenten ganz zu schweigen, ist nicht obligatorisch (es kann keine geben)
  2. Komposition ist "einfacher", wenn ein Delegat im Vergleich zu einer Schnittstelle verwendet wird. Schnittstellen definieren eine Klasseninstanz als "Art von Handler" -Objekt, bei dem eine Kompositionskonstruktionsinstanz "einen Handler" zu haben scheint, wodurch das Erscheinungsbild der Instanz durch die Verwendung eines Delegaten weniger eingeschränkt und flexibler wird.
  3. Aus dem nachstehenden Kommentar geht hervor, dass ich meinen Beitrag verbessern musste. Siehe diesen als Referenz: http://bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip
quelle
1
3. Warum sollten Delegierte mehr Casting benötigen und warum ist eine engere Kopplung von Vorteil? 4. Sie müssen keine Ereignisse verwenden, um Delegaten zu verwenden, und bei Delegaten ist es wie beim Halten einer Methode - Sie wissen, dass es sich um den Rückgabetyp und den Typ der Argumente handelt. Warum sollte eine in einer Schnittstelle deklarierte Methode die Behandlung von Fehlern vereinfachen als eine an einen Delegaten angehängte Methode?
Yam Marcovic
Wenn Sie nur die Spezifikation eines Delegierten haben, haben Sie Recht. Bei der Ereignisbehandlung müssen Sie jedoch (zumindest meine Referenz) immer eine Art eventArgs erben, um als Container für benutzerdefinierte Typen zu fungieren, anstatt sicher auf einen Wert testen zu können, den Sie immer verwenden würden müssen Ihren Typ auspacken, bevor Sie die Werte verarbeiten.
Carlo Kuip
Der Grund, warum ich denke, dass eine in einer Schnittstelle definierte Methode die Behandlung von Fehlern erleichtern würde, besteht darin, dass der Compiler die Ausnahmetypen überprüfen kann, die von dieser Methode ausgelöst werden. Ich weiß, es ist kein überzeugender Beweis, aber sollte vielleicht als Präferenz angegeben werden?
Carlo Kuip
1
Erstens sprachen wir von Delegierten gegen Schnittstellen, nicht von Ereignissen gegen Schnittstellen. Zweitens ist es nur eine Konvention und keineswegs obligatorisch, ein Event auf einen EventHandler-Delegierten zu gründen. Aber auch hier geht es nicht darum, über Ereignisse zu sprechen. Wie für Ihren zweiten Kommentar - Ausnahmen sind in C # nicht aktiviert. Sie werden mit Java verwechselt (das übrigens nicht einmal Delegierte hat).
Yam Marcovic
Es wurde ein Thread zu diesem Thema gefunden, der wichtige Unterschiede erklärt: bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip