Ein Unterschied im Stil: IDictionary vs Dictionary

75

Ich habe einen Freund, der gerade erst in die .NET-Entwicklung einsteigt, nachdem er sich jahrelang in Java entwickelt hat, und nachdem ich mir einen Teil seines Codes angesehen habe, stelle ich fest, dass er ziemlich oft Folgendes tut:

IDictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

Er deklariert das Wörterbuch eher als Schnittstelle als als Klasse. Normalerweise würde ich Folgendes tun:

Dictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

Ich würde die IDictionary-Schnittstelle nur verwenden, wenn sie benötigt wird (z. B. um das Wörterbuch an eine Methode zu übergeben, die eine IDictionary-Schnittstelle akzeptiert).

Meine Frage ist: Gibt es irgendwelche Vorteile für seine Art, Dinge zu tun? Ist dies eine gängige Praxis in Java?

Zügel
quelle
11
Für lokale Variablen macht es wirklich keinen Sinn.
Joren
5
Eine gute Praxis ABER passen sie vom Leistungskosten: nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue
MaYaN
1
Die beiden Hauptimplementierungen von IDictionary( Dictionaryund ConcurrentDictionary) weisen sehr unterschiedliche Leistungsmerkmale auf (z. B. Countist bei ersteren sehr schnell und bei letzteren sehr langsam). Daher würde ich empfehlen, die konkrete Klasse (und nicht IDictionary) in C # anzugeben, sofern dies möglich ist, damit der Verbraucher weiß, wie er das Wörterbuch performant verwendet.
mjwills
1
Darüber hinaus IDictionaryhat einige Leistung "Macken" - nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue .
mjwills

Antworten:

66

Wenn IDictionary ein "allgemeinerer" Typ als Dictionary ist, ist es sinnvoll, den generischeren Typ zum Deklarieren von Variablen zu verwenden. Auf diese Weise müssen Sie sich nicht so sehr um die der Variablen zugewiesene Implementierungsklasse kümmern, und Sie können den Typ in Zukunft problemlos ändern, ohne viel folgenden Code ändern zu müssen. In Java wird dies beispielsweise häufig als besser angesehen

List<Integer> intList=new LinkedList<Integer>();

als es zu tun ist

LinkedList<Integer> intList=new LinkedList<Integer>();

Auf diese Weise bin ich sicher, dass der folgende Code die Liste als Liste und nicht als LinkedList behandelt, was es in Zukunft einfach macht, LinkedList für Vector oder eine andere Klasse, die List implementiert, auszutauschen. Ich würde sagen, dass dies Java und der guten Programmierung im Allgemeinen gemeinsam ist.

Chris
quelle
24
+1, aber vielleicht möchten Sie das Wort "allgemein" anstelle von "generisch" verwenden, an das bereits eine Menge Bedeutung angehängt ist :)
AakashM
28

Diese Praxis ist nicht nur auf Java beschränkt.

Es wird häufig auch in .NET verwendet, wenn Sie die Instanz des Objekts von der von Ihnen verwendeten Klasse entkoppeln möchten. Wenn Sie die Schnittstelle anstelle der Klasse verwenden, können Sie den Sicherungstyp bei Bedarf ändern, ohne den Rest Ihres Codes zu beschädigen.

Sie werden auch feststellen, dass diese Vorgehensweise häufig mit dem Umgang mit IoC-Containern und der Instanziierung mithilfe des Factory-Musters verwendet wird.

Justin Niessner
quelle
1
Sie verlieren jedoch alle Funktionen, die Dictionary möglicherweise bietet und die nicht in IDictionary enthalten sind.
Zügel
10
Wenn Sie jedoch stattdessen IDictionary verwenden können, verwenden Sie die Funktionalität zunächst nicht.
Justin Niessner
1
Aber wenn Sie nur die Funktionalität in IDictionary benötigen ... Es ist einfacher, IDictionary in Dictionary zu ändern als umgekehrt :)
Svish
1
Es ist einfacher, ein Wörterbuch in ein IDictionary umzuwandeln, als umgekehrt. :)
Zügel
10
Der Verlust der Funktionalität, die Dictionary bietet, IDictionary jedoch nicht, ist der springende Punkt dieser Redewendung. Wenn Sie Ihre lokale Variable als IDictionary deklarieren, müssen Sie die Verwendung dieser Variablen auf das beschränken, was über die Schnittstelle verfügbar ist. Wenn Sie später feststellen, dass Sie eine andere Klasse verwenden müssen, die IDictionay anstelle von Dictionary implementiert, können Sie dies tun, indem Sie die eine Zeile ändern, die den Konstruktor für konkrete Klassen aufruft (da Sie wissen, dass Ihr Code ihn nur als IDictionary und nicht als Dictionary verwendet ).
Stephen C. Steel
15

Ihr Freund folgt dem sehr nützlichen Prinzip:

"Abstrakt von Implementierungsdetails"

Vitaliy Liptchinsky
quelle
6
Wir beschäftigen uns jedoch mit Implementierungsdetails. Wir erstellen keine Schnittstelle zu einer Klasse - wir deklarieren einfach eine lokale Variable.
Zügel
8
Nicht wirklich, IMHO. Wenn Ihnen die Mitglieder der Benutzeroberfläche ausreichen, würde ich Ihnen wirklich empfehlen, sich an die Benutzeroberfläche zu halten. Machen Sie Ihren Code so allgemein wie möglich.
Vitaliy Liptchinsky
7

Sie sollten immer versuchen, auf die Schnittstelle und nicht auf die konkrete Klasse zu programmieren.

In Java oder einer anderen objektorientierten Programmiersprache.

In der .NET-Welt wird häufig ein verwendet, Ium anzugeben, dass es sich um eine Schnittstelle handelt, die Sie verwenden. Ich denke, das ist häufiger, weil sie in C # nicht haben implementsundextends Vererbung zwischen Klasse und Schnittstelle darauf verweisen.

Ich denke, Molke würde tippen

 class MyClass:ISome,Other,IMore 
 { 
 }

Und man kann sagen , ISomeeine IMoreSchnittstellen sind währendOther eine Klasse

In Java ist so etwas nicht nötig

 class MyClass extends Other implements Some, More {
 }

Das Konzept gilt weiterhin, Sie sollten versuchen, auf der Schnittstelle zu codieren.

OscarRyz
quelle
3
Ich glaube nicht, dass seine Frage war, wie Schnittstellen oder funktionieren oder warum sie mit I beginnen. Außerdem müssen Basisklassen vor Schnittstellen deklariert werden. Wäre public class MyClass : BaseClass, IFirstInterface, ISecondInterfacealso richtig. Ich habe am Anfang einer Benutzeroberfläche nichts Besonderes. Der Compiler behandelt es nicht anders. Es ist ein Marker für andere Programmierer. Sie könnten Ihre Schnittstellen ohne sie benennen. . . aber andere Programmierer würden dich wahrscheinlich hassen.
Tandrewnichols
7

Für lokale Variablen und private Felder, bei denen es sich bereits um Implementierungsdetails handelt, ist es besser, konkrete Typen als Schnittstellen für die Deklarationen zu verwenden, da die konkreten Klassen eine Leistungssteigerung bieten (der direkte Versand ist schneller als der virtuelle / Schnittstellenversand). Die JIT kann auch Methoden einfacher einbinden, wenn Sie in den Details der lokalen Implementierung nicht unnötig in Schnittstellentypen umwandeln. Wenn eine Instanz eines konkreten Typs von einer Methode zurückgegeben wird, die eine Schnittstelle zurückgibt, erfolgt die Umwandlung automatisch.

Sam Harwell
quelle
4
Dies ist eine Mikrooptimierung, die nur durchgeführt werden sollte, wenn Sie einen bestimmten Leistungsengpass lösen müssen. Sie sollte nicht das Design beeinflussen.
Yishai
8
Es ist keine Mikrooptimierung. Sie befinden sich bereits in der Implementierung, in der Sie eine konkrete Implementierung ausgewählt haben. Warum tun Sie so, als hätten Sie es nicht auf Kosten der Leistung getan?
Sam Harwell
5

Soweit ich gesehen habe, verwenden Java-Entwickler häufiger Abstraktion (und Entwurfsmuster) als .NET-Entwickler. Dies scheint ein weiteres Beispiel dafür zu sein: Warum die konkrete Klasse deklarieren, wenn er im Wesentlichen nur mit den Schnittstellenmitgliedern arbeitet?

Gergely Orosz
quelle
Ich war bis zum zweiten Satz bei dir. Wie sonst sollen Sie einen Verweis auf einen Schnittstellenimplementierer erhalten? Sie müssen etwas instanziieren, das es implementiert. new IDictionary<string, double>()würde nicht funktionieren und macht sowieso keinen Sinn.
Tom W
Ich verstehe nicht wirklich ... mein Punkt war, dass jeder, der jemals das Wörterbuchmitglied verwendet, nur IDictionary-Funktionen verwenden möchte. Natürlich müssen Sie etwas instanziieren, aber wenn Sie dieses Muster verwenden, bedeuten die folgenden Codes dasselbe für jeden, der die Klasse verwendet: IDictionary <T1, T2> dictionary = new Dictionary <T1, T2> (); und IDictionary <T1, T2> dictionary = new ConcurrentDictionary <T1, T2> (); Wenn Sie keine Dictionary- oder ConcreteDictionary-spezifischen Mitglieder verwenden, ist es sinnvoll, sie nicht als Typ der Klassenvariablen zu deklarieren und eine Abstraktionsebene hinzuzufügen.
Gergely Orosz
4

In den meisten Fällen wird der Schnittstellentyp (IDictionary) angezeigt, der verwendet wird, wenn das Mitglied externem Code ausgesetzt ist, unabhängig davon, ob dieser sich außerhalb der Assembly oder nur außerhalb der Klasse befindet. In der Regel verwenden die meisten Entwickler den konkreten Typ intern für eine Klassendefinition, während sie eine gekapselte Eigenschaft mithilfe des Schnittstellentyps verfügbar machen. Auf diese Weise können sie die Funktionen des konkreten Typs nutzen. Wenn sie jedoch den konkreten Typ ändern, muss sich die Schnittstelle der deklarierenden Klasse nicht ändern.

Widget der öffentlichen Klasse
{
    privates Wörterbuch <string, string> map = new Dictionary <string, string> ();
    public IDictionary <string, string> Map
    {
        get {return map; }}
    }}
}}

später kann werden:

Klasse SpecialMap <TKey, TValue>: IDictionary <TKey, TValue> {...}

Widget der öffentlichen Klasse
{
    private SpecialMap <string, string> map = new SpecialMap <string, string> ();
    public IDictionary <string, string> Map
    {
        get {return map; }}
    }}
}}

ohne die Benutzeroberfläche von Widget zu ändern und anderen Code ändern zu müssen, der sie bereits verwendet.

Travis Heseman
quelle
3

In der beschriebenen Situation würde fast jeder Java-Entwickler die Schnittstelle verwenden, um die Variable zu deklarieren. Die Art und Weise, wie die Java-Sammlungen verwendet werden, ist wahrscheinlich eines der besten Beispiele:

Map map = new HashMap();
List list = new ArrayList();

Vermutlich wird in vielen Situationen nur eine lose Kopplung erreicht.

NickDK
quelle
3

Java-Sammlungen enthalten eine Vielzahl von Implementierungen. Daher ist es für mich viel einfacher, davon Gebrauch zu machen

List<String> myList = new ArrayList<String>();

Wenn ich dann in Zukunft merke, dass "myList" threadsicher sein muss, um diese einzelne Zeile einfach in zu ändern

List<String> myList = new Vector<String>();

Und ändern Sie keine andere Codezeile. Dies schließt auch Getter / Setter ein. Wenn Sie sich zum Beispiel die Anzahl der Implementierungen von Map ansehen, können Sie sich vorstellen, warum dies eine gute Praxis sein könnte. In anderen Sprachen, in denen es nur ein paar Implementierungen für etwas gibt (sorry, kein großer .NET-Typ), aber in Objective-C gibt es wirklich nur NSDictionary und NSMutableDictionary. Es macht also nicht so viel Sinn.

Bearbeiten:

Es ist mir nicht gelungen, einen meiner Schlüsselpunkte zu treffen (nur mit dem Getter / Setter darauf angespielt).

Das Obige ermöglicht Ihnen:

public void setMyList(List<String> myList) {
    this.myList = myList;
}

Und der Client, der diesen Aufruf verwendet, muss sich nicht um die zugrunde liegende Implementierung kümmern. Verwenden eines beliebigen Objekts, das der Listenschnittstelle entspricht.

MarkPowell
quelle
Ändern dieser Zeile in: Vector <String> myList = new Vector <String> (); ändert auch nur eine Zeile.
tster
1
@tster - aber welcher andere Code ruft ArrayList-spezifische Methoden auf, die geändert werden müssten, weil Vector nicht genau dieselbe Methode hat?
Gordon Tucker
public void setMyList (List <String> list) {myList = list; } Ist gültig, wenn es als Liste deklariert wird, nicht, wenn eine direkte Implementierung verwendet wird. Das heißt, ein Client kann jede Liste verwenden, die er möglicherweise hat, ohne sich Gedanken über die Konvertierung in Ihre Implementierung machen zu müssen.
MarkPowell
3

Ich komme aus einer Java-Welt und stimme zu, dass das Mantra "Programm zu einer Schnittstelle" in Sie eingearbeitet ist. Indem Sie auf eine Schnittstelle programmieren, nicht auf eine Implementierung, erweitern Sie Ihre Methoden für zukünftige Anforderungen.

Anjisan
quelle
1
es sei denn, der zukünftige Bedarf besteht in einer Änderung der Vertragsunterzeichnung, z. B. einer neuen Eigenschaft oder Methode.
Matthew Whited
3

IDictionaryist eine Schnittstelle und Dictionaryist eine Klasse.

DictionaryGeräte IDictionary.

Dies bedeutet, dass es möglich ist, Dictionarymit / nach IDictionaryInstanz auf eine Instanz zu verweisen und die meisten DictionaryMethoden und Eigenschaften über eine IDictionaryInstanz aufzurufen .

Dies wird dringend empfohlen, um so viele Schnittstellen wie möglich zu verwenden, da Schnittstellen die Module und Baugruppen der Anwendungen abstrahieren, Polymorphismus ermöglichen, der in vielen Situationen und Fällen sowohl sehr häufig als auch nützlich ist und das Ersetzen eines Moduls durch ein anderes ermöglicht, ohne die anderen Module zu berühren .

Angenommen, der Programmierer hat in der Gegenwart geschrieben:

IDictionary<string> dictionary = new Dictionary<string>();

Und dictionaryruft jetzt die Methoden und Eigenschaften von auf Dictionary<string>.

In der Zukunft haben die Datenbanken in der Größe gewachsen, und wir erfahren, dass Dictionary<string>zu langsam, so dass wir ersetzen wollen Dictionary<string>durch RedBlackTree<string>, was schneller ist.

Alles, was getan werden muss, ist, die obige Anweisung zu ersetzen durch:

IDictionary<string> dictionary = new RedBlackTree<string>();

Wenn dies RedBlackTreeimplementiert wird, IDictionarywird der gesamte Code der Anwendung erfolgreich kompiliert und Sie haben eine neuere Version Ihrer Anwendung, in der die Anwendung jetzt schneller und effizienter als die vorherige Version ausgeführt wird.

Ohne Schnittstellen wäre dieser Austausch schwieriger und würde von den Programmierern und Entwicklern erfordern, mehr Code zu ändern, der möglicherweise fehleranfällig ist.

user11336341
quelle
1

Ich habe festgestellt, dass es für lokale Variablen im Allgemeinen nicht wichtig ist, ob Sie die Schnittstelle oder die konkrete Klasse verwenden.

Im Gegensatz zu Klassenmitgliedern oder Methodensignaturen ist der Aufwand für das Refactoring sehr gering, wenn Sie sich entscheiden, Typen zu ändern, und die Variable ist auch außerhalb ihrer Verwendungssite nicht sichtbar. Wenn Sie varzum Deklarieren von Einheimischen verwenden, erhalten Sie nicht den Schnittstellentyp, sondern den Klassentyp (es sei denn, Sie wandeln ihn explizit in die Schnittstelle um).

Wenn Sie jedoch Methoden, Klassenmitglieder oder Schnittstellen deklarieren, ersparen Sie sich meiner Meinung nach einiges an Kopfschmerzen, wenn Sie den Schnittstellentyp im Voraus verwenden, anstatt die öffentliche API an einen bestimmten Klassentyp zu koppeln.

LBushkin
quelle
2
Dies war auch meine Meinung - für Methodendefinitionen ist es wichtiger, den allgemeinen Typ zu verwenden, da die Methode wiederverwendet werden kann. Für lokale Variablen sehe ich den Wert nicht.
Zügel
1
Persönlich denke ich, dass der Vorteil darin besteht, dass Sie mögliche Abhängigkeiten von Implementierungsklassen sehr leicht erkennen können. Zum Beispiel, wenn Sie Ihre lokale Variable an eine Dienstprogrammmethode oder etwas anderes übergeben.
NickDK
2
Bei lokalen Variablen habe ich folgende Argumente gesehen: 1) Sie werden diszipliniert mit Schnittstellen umgehen, und 2) Ihr Code reagiert weniger empfindlich auf die Rückgabetypen von Funktionsaufrufen, die Einheimischen zugewiesen werden - was bedeutet, dass die Funktion dies kann Ändern Sie den konkreten Typ, ohne Ihren Code zu beeinflussen (solange der Rückgabetyp natürlich noch an der Schnittstelle haftet).
LBushkin
1
@LBushkin, ich finde auch, dass sich der konkrete Typ bei der Entwicklung einer Methode und bei Refactorings wie der Extraktionsmethode und dergleichen tendenziell ausbreiten kann, obwohl die Schnittstelle hätte verwendet werden sollen.
Yishai
1

Die Verwendung von Schnittstellen bedeutet, dass "Wörterbuch" im folgenden Code eine beliebige Implementierung von IDictionary sein kann.

Dictionary1 dictionary = new Dictionary1();
dictionary.operation1(); // if operation1 is implemented only in Dictionary1() this will fail for every other implementation

Es ist am besten zu sehen, wenn Sie die Konstruktion des Objekts verbergen:

IDictionary dictionary = DictionaryFactory.getDictionary(...);
Manrico Corazzi
quelle
Ich würde zustimmen, dass dies eine gute Idee ist, wenn Sie eine Factory verwenden, um ein unspezifisches Wörterbuch zurückzugeben.
Zügel
1
Was ich damit meinte war, dass die Verwendung der Schnittstelle Sie dazu zwingt, "allgemein" zu denken und die Entkopplung der Klassen zu erzwingen (in meinem Beispiel muss die Methode mit Dictionary1 mit der inneren Struktur der Klasse vertraut sein, nicht mit der Schnittstelle)
Manrico Corazzi
1

Ich habe die gleiche Situation mit einem Java-Entwickler angetroffen. Er instanziiert Sammlungen UND Objekte auf die gleiche Weise an ihre Schnittstelle. Zum Beispiel,

IAccount account = new Account();

Eigenschaften werden immer als Schnittstellen abgerufen / festgelegt. Dies führt zu Problemen bei der Serialisierung, die sehr gut erklärt hier

Jim Schubert
quelle
1
Dies ist nur ein Problem mit der "magischen" Serialisierung, die .NET für Webdienstschnittstellen verwendet. Die Praxis, Schnittstellen anstelle konkreter Klassen zu verwenden, ist einwandfrei.
Daniel Pryden
Wir hatten das Problem speziell mit der ViewState-Serialisierung in ASP.NET 1.1
Jim Schubert