Was ist der Zweck einer Marker-Schnittstelle?
quelle
Dies ist eine Art Tangente, die auf der Antwort von "Mitch Wheat" basiert.
Wenn ich sehe, dass Leute die Richtlinien für das Framework-Design zitieren, möchte ich im Allgemeinen immer Folgendes erwähnen:
Im Allgemeinen sollten Sie die Richtlinien für das Framework-Design die meiste Zeit ignorieren.
Dies liegt nicht an Problemen mit den Richtlinien für das Framework-Design. Ich denke, das .NET Framework ist eine fantastische Klassenbibliothek. Ein Großteil dieser Fantastizität ergibt sich aus den Richtlinien für das Framework-Design.
Die Entwurfsrichtlinien gelten jedoch nicht für den meisten Code, der von den meisten Programmierern geschrieben wurde. Ihr Zweck ist es, die Erstellung eines großen Frameworks zu ermöglichen, das von Millionen von Entwicklern verwendet wird, um das Schreiben von Bibliotheken nicht effizienter zu gestalten.
Viele der darin enthaltenen Vorschläge können Sie dazu führen, Dinge zu tun, die:
Das .net-Framework ist groß, wirklich groß. Es ist so groß, dass es absolut unvernünftig wäre anzunehmen, dass jemand detailliertes Wissen über jeden Aspekt davon hat. In der Tat ist es viel sicherer anzunehmen, dass die meisten Programmierer häufig auf Teile des Frameworks stoßen, die sie noch nie zuvor verwendet haben.
In diesem Fall sind die Hauptziele eines API-Designers:
Die Framework-Design-Richtlinien fordern Entwickler dazu auf, Code zu erstellen, der diese Ziele erreicht.
Das bedeutet, Dinge wie das Vermeiden von Vererbungsebenen zu tun, selbst wenn dies das Duplizieren von Code bedeutet, oder den gesamten Code, der Ausnahmen auslöst, an "Einstiegspunkte" zu verschieben, anstatt gemeinsam genutzte Helfer zu verwenden (damit Stapelspuren im Debugger sinnvoller sind) und vieles mehr von anderen ähnlichen Dingen.
Der Hauptgrund, warum diese Richtlinien die Verwendung von Attributen anstelle von Markierungsschnittstellen vorschlagen, liegt darin, dass das Entfernen der Markierungsschnittstellen die Vererbungsstruktur der Klassenbibliothek wesentlich zugänglicher macht. Ein Klassendiagramm mit 30 Typen und 6 Ebenen der Vererbungshierarchie ist im Vergleich zu einem mit 15 Typen und 2 Hierarchieebenen sehr entmutigend.
Wenn wirklich Millionen von Entwicklern Ihre APIs verwenden oder Ihre Codebasis wirklich groß ist (z. B. über 100.000 LOC), kann das Befolgen dieser Richtlinien sehr hilfreich sein.
Wenn 5 Millionen Entwickler 15 Minuten damit verbringen, eine API zu lernen, anstatt 60 Minuten damit, sie zu lernen, ergibt sich eine Nettoeinsparung von 428 Mannjahren. Das ist viel Zeit.
An den meisten Projekten sind jedoch nicht Millionen von Entwicklern oder 100K + LOC beteiligt. In einem typischen Projekt mit beispielsweise 4 Entwicklern und etwa 50.000 Loc sind die Annahmen sehr unterschiedlich. Die Entwickler im Team haben ein viel besseres Verständnis für die Funktionsweise des Codes. Das bedeutet, dass es viel sinnvoller ist, zu optimieren, um schnell qualitativ hochwertigen Code zu erstellen und die Anzahl der Fehler sowie den Aufwand für Änderungen zu verringern.
Wenn Sie 1 Woche lang Code entwickeln, der mit dem .net-Framework kompatibel ist, und 8 Stunden lang Code schreiben, der einfach zu ändern ist und weniger Fehler aufweist, kann dies zu Folgendem führen:
Ohne 4.999.999 andere Entwickler, die die Kosten übernehmen, lohnt es sich normalerweise nicht.
Das Testen auf Markierungsschnittstellen führt beispielsweise zu einem einzelnen "Ist" -Ausdruck und führt zu weniger Code als beim Suchen nach Attributen.
Mein Rat ist also:
virtual protected
VorlagenmethodeDoSomethingCore
anstelle vonDoSomething
ist nicht so viel zusätzliche Arbeit und Sie kommunizieren klar, dass es sich um eine Vorlagenmethode handelt ... IMNSHO, die Leute, die Anwendungen schreiben, ohne die API (But.. I'm not a framework developer, I don't care about my API!
) zu berücksichtigen, sind genau die Leute, die viele Duplikate schreiben ( und auch undokumentierten und normalerweise unlesbaren Code, nicht umgekehrt.Markierungsschnittstellen werden verwendet, um die Fähigkeit einer Klasse zu markieren, zur Laufzeit eine bestimmte Schnittstelle zu implementieren.
Die Richtlinien für das Interface-Design und das .NET-Typ-Design - Interface-Design raten von der Verwendung von Marker-Interfaces zugunsten der Verwendung von Attributen in C # ab. Wie @Jay Bazuzi jedoch betont, ist es einfacher, nach Marker-Interfaces zu suchen als nach Attributen:
o is I
Also stattdessen:
In den .NET-Richtlinien wurde Folgendes empfohlen:
quelle
Da in jeder anderen Antwort angegeben wurde, dass sie vermieden werden sollten, wäre es nützlich, eine Erklärung zu haben, warum.
Erstens, warum Markierungsschnittstellen verwendet werden: Sie sind vorhanden, damit der Code, der das Objekt verwendet, das sie implementiert, prüfen kann, ob sie diese Schnittstelle implementieren, und das Objekt in diesem Fall anders behandelt.
Das Problem bei diesem Ansatz ist, dass die Kapselung unterbrochen wird. Das Objekt selbst hat jetzt indirekte Kontrolle darüber, wie es extern verwendet wird. Darüber hinaus verfügt es über Kenntnisse über das System, in dem es verwendet werden soll. Durch Anwenden der Markierungsschnittstelle schlägt die Klassendefinition vor, dass es an einem Ort verwendet werden soll, der die Existenz des Markers überprüft. Es hat implizites Wissen über die Umgebung, in der es verwendet wird, und versucht zu definieren, wie es verwendet werden soll. Dies widerspricht der Idee der Kapselung, da es Kenntnisse über die Implementierung eines Teils des Systems hat, der vollständig außerhalb seines eigenen Bereichs existiert.
Auf praktischer Ebene verringert dies die Portabilität und Wiederverwendbarkeit. Wenn die Klasse in einer anderen Anwendung wiederverwendet wird, muss auch die Schnittstelle kopiert werden. In der neuen Umgebung hat sie möglicherweise keine Bedeutung, sodass sie vollständig redundant ist.
Als solches ist der "Marker" Metadaten über die Klasse. Diese Metadaten werden nicht von der Klasse selbst verwendet und sind nur für (einige!) Externen Client-Codes von Bedeutung, damit das Objekt auf eine bestimmte Weise behandelt werden kann. Da dies nur für den Clientcode von Bedeutung ist, sollten sich die Metadaten im Clientcode und nicht in der Klassen-API befinden.
Der Unterschied zwischen einer "Marker-Schnittstelle" und einer normalen Schnittstelle besteht darin, dass eine Schnittstelle mit Methoden der Außenwelt mitteilt, wie sie verwendet werden kann, während eine leere Schnittstelle impliziert, dass sie der Außenwelt mitteilt, wie sie verwendet werden soll.
quelle
IConstructableFromString<T>
angibt, dass eine KlasseT
nur implementiertIConstructableFromString<T>
werden darf, wenn sie ein statisches Mitglied hat ...public static T ProduceFromString(String params);
kann eine Begleitklasse zur Schnittstelle eine Methode anbietenpublic static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>
; Wenn der Clientcode eine Methode wie diese hätteT[] MakeManyThings<T>() where T:IConstructableFromString<T>
, könnte man neue Typen definieren, die mit dem Clientcode arbeiten könnten, ohne den Clientcode ändern zu müssen, um mit ihnen umzugehen. Wenn die Metadaten im Clientcode enthalten wären, könnten keine neuen Typen zur Verwendung durch den vorhandenen Client erstellt werden.T
und der Klasse, die ihn verwendet, befindet sichIConstructableFromString<T>
jedoch eine Methode in der Schnittstelle, die ein bestimmtes Verhalten beschreibt, sodass es sich nicht um eine Markierungsschnittstelle handelt.ProduceFromString
im obigen Beispiel würde dies jedoch nicht umfassen die Schnittstelle in irgendeiner Weise, außer dass die Schnittstelle als Markierung verwendet wird, um anzugeben, von welchen Klassen erwartet werden soll, dass sie die erforderliche Funktion implementieren.Markierungsschnittstellen können manchmal ein notwendiges Übel sein, wenn eine Sprache keine diskriminierten Vereinigungstypen unterstützt .
Angenommen, Sie möchten eine Methode definieren, die ein Argument erwartet, dessen Typ genau A, B oder C sein muss. In vielen funktionalen Erstsprachen (wie F # ) kann ein solcher Typ sauber definiert werden als:
In OO-Erstsprachen wie C # ist dies jedoch nicht möglich. Die einzige Möglichkeit, hier etwas Ähnliches zu erreichen, besteht darin, die Schnittstelle IArg zu definieren und damit A, B und C zu "markieren".
Natürlich könnten Sie die Verwendung der Markierungsschnittstelle vermeiden, indem Sie einfach den Typ "Objekt" als Argument akzeptieren, aber dann würden Sie die Ausdruckskraft und ein gewisses Maß an Typensicherheit verlieren.
Diskriminierte Gewerkschaftstypen sind äußerst nützlich und existieren seit mindestens 30 Jahren in funktionalen Sprachen. Seltsamerweise haben bis heute alle gängigen OO-Sprachen diese Funktion ignoriert - obwohl sie eigentlich nichts mit funktionaler Programmierung an sich zu tun hat, sondern zum Typensystem gehört.
quelle
Foo<T>
für jeden Typ einen eigenen SatzT
statischer Felder enthältT
soll mit arbeiten. Die Verwendung einer generischen Schnittstellenbeschränkung für den TypT
würde zum Zeitpunkt des Compilers prüfen, ob der angegebene Typ zumindest die Gültigkeit beansprucht, obwohl er nicht sicherstellen kann, dass er tatsächlich gültig ist.Eine Markierungsschnittstelle ist nur eine leere Schnittstelle. Eine Klasse würde diese Schnittstelle als Metadaten implementieren, die aus irgendeinem Grund verwendet werden sollen. In C # verwenden Sie häufiger Attribute, um eine Klasse zu markieren, aus den gleichen Gründen, aus denen Sie eine Markierungsschnittstelle in anderen Sprachen verwenden würden.
quelle
Über eine Markierungsschnittstelle kann eine Klasse so markiert werden, dass sie auf alle untergeordneten Klassen angewendet wird. Eine "reine" Marker-Schnittstelle würde nichts definieren oder erben. Ein nützlicherer Typ von Markierungsschnittstellen kann eine sein, die eine andere Schnittstelle "erbt", aber keine neuen Mitglieder definiert. Wenn es beispielsweise eine Schnittstelle "IReadableFoo" gibt, könnte man auch eine Schnittstelle "IImmutableFoo" definieren, die sich wie ein "Foo" verhält, aber jedem, der sie verwendet, verspricht, dass nichts ihren Wert ändert. Eine Routine, die ein IImmutableFoo akzeptiert, kann es wie ein IReadableFoo verwenden, aber die Routine akzeptiert nur Klassen, die als Implementierung von IImmutableFoo deklariert wurden.
Ich kann mir nicht viele Verwendungsmöglichkeiten für "reine" Marker-Interfaces vorstellen. Das einzige, an das ich denken kann, wäre, wenn EqualityComparer (von T) .Default Object.Equals für jeden Typ zurückgeben würde, der IDoNotUseEqualityComparer implementiert, selbst wenn der Typ auch IEqualityComparer implementiert. Dies würde es einem ermöglichen, einen nicht versiegelten unveränderlichen Typ zu haben, ohne das Liskov-Substitutionsprinzip zu verletzen: Wenn der Typ alle Methoden im Zusammenhang mit Gleichheitsprüfungen versiegelt, könnte ein abgeleiteter Typ zusätzliche Felder hinzufügen und sie veränderbar machen, aber die Mutation solcher Felder würde dies nicht tun. ' Sie können mit Basismethoden nicht sichtbar sein. Es ist möglicherweise nicht schrecklich, eine nicht versiegelte unveränderliche Klasse zu haben und entweder die Verwendung von EqualityComparer.Default zu vermeiden oder abgeleitete Klassen zu vertrauen, um IEqualityComparer nicht zu implementieren.
quelle
Diese beiden Erweiterungsmethoden lösen die meisten Probleme, von denen Scott behauptet, dass sie Markierungsschnittstellen gegenüber Attributen bevorzugen:
Jetzt hast du:
gegen:
Ich kann nicht sehen, wie das Erstellen einer API mit dem ersten Muster fünfmal so lange dauert wie mit dem zweiten, wie Scott behauptet.
quelle
Die Marker-Schnittstelle ist eigentlich nur eine prozedurale Programmierung in einer OO-Sprache. Eine Schnittstelle definiert einen Vertrag zwischen Implementierern und Verbrauchern mit Ausnahme einer Markierungsschnittstelle, da eine Markierungsschnittstelle nur sich selbst definiert. Die Markierungsschnittstelle versagt also direkt vor dem Tor, um eine Schnittstelle zu sein.
quelle
Marker sind leere Schnittstellen. Ein Marker ist entweder da oder nicht.
Klasse Foo: IConfidential
Hier markieren wir Foo als vertraulich. Keine tatsächlichen zusätzlichen Eigenschaften oder Attribute erforderlich.
quelle
Die Marker-Schnittstelle ist eine vollständig leere Schnittstelle ohne Body / Datenelemente / Implementierung.
Eine Klasse implementiert bei Bedarf eine Markierungsschnittstelle. Sie dient lediglich zum " Markieren ". bedeutet, dass die JVM darüber informiert wird, dass die bestimmte Klasse zum Klonen bestimmt ist. Lassen Sie sie daher klonen. Diese spezielle Klasse dient zum Serialisieren ihrer Objekte. Lassen Sie daher zu, dass ihre Objekte serialisiert werden.
quelle