Warum sollte ein Java-Objekt nicht über die Implementierung (HashMap), sondern über die Schnittstelle (z. B. Map) definiert werden?

17

In den meisten Java-Codes werden Java-Objekte folgendermaßen deklariert:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

Anstatt von:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

Warum wird das Java-Objekt vorzugsweise über die Schnittstelle definiert, anstatt über die Implementierung, die tatsächlich verwendet wird?

Suman
quelle

Antworten:

26

Der Grund dafür ist, dass die Implementierung dieser Schnittstellen in der Regel nicht relevant ist, wenn Sie den Anrufer dazu verpflichten, eine zu übergeben HashMap an eine Methode zu übergeben, müssen Sie im Wesentlichen festlegen, welche Implementierung verwendet werden soll. In der Regel sollten Sie sich also eher um die Benutzeroberfläche als um die eigentliche Implementierung kümmern und den Schmerz und das Leid vermeiden, die dazu führen können, dass Sie alle Methodensignaturen ändern müssen, HashMapwenn Sie entscheiden, dass Sie sie LinkedHashMapstattdessen verwenden möchten.

Es sollte gesagt werden, dass es Ausnahmen gibt, wenn die Implementierung relevant ist. Wenn Sie eine Karte benötigen, wenn die Reihenfolge wichtig ist, können Sie verlangen, dass ein TreeMapoder ein LinkedHashMapübergeben wird, oder noch besser SortedMap, dass keine bestimmte Implementierung angegeben wird. Dies verpflichtet den Aufrufer, zwangsläufig eine bestimmte Art der Implementierung von Map zu bestehen und weist nachdrücklich auf diese Reihenfolge hin ist wichtig. Das heißt, könnten Sie SortedMapeine unsortierte überschreiben und übergeben? Ja, natürlich, aber erwarten Sie, dass daraus schlimme Dinge werden.

Die beste Praxis schreibt jedoch vor, dass Sie keine spezifischen Implementierungen verwenden sollten, wenn dies nicht wichtig ist. Dies gilt im Allgemeinen. Wenn Sie es zu tun Dogund Catwelche ergeben sich aus Animal, um einen optimalen Einsatz der Vererbung zu machen, sollten Sie in der Regel vermeiden Methoden spezifisch mit Dogoder Cat. Eher alle Methoden in Dogoder Catsollten Methoden in überschreiben Animalund es erspart Ihnen auf lange Sicht Ärger.

Neil
quelle
Wenn Sie eine Karte benötigen, die sortiert ist, sollte der Parametertyp SortedMapnicht so sein TreeMap.
Kopffüßer
@Arian SortedMapist eine von mehreren Implementierungen, die sich mit der Bestellung befassen. Darum geht es nicht. TreeMapbestellt auch Elemente entsprechend der Implementierung des Schlüssels Comparableoder einer gegebenen ComparatorSchnittstelle.
Neil
Nein, SortedMap ist keine Implementierung, das ist genau der Punkt. Es ist die Schnittstelle für Karten, die nach Schlüsseln sortiert sind.
Kopffüßer
1
@Arian Ah, ich verstehe, was du meinst. Richtig, besser SortedMap, da dies keine Implementierung erzwingt. Ich werde die richtigen Anpassungen vornehmen.
Neil
Eigentlich LinkedHashMapimplementiert a nicht SortedMap. Die einzigen Unterklassen von SortedMapsind ConcurrentSkipListMapund TreeMap.
bcorso
10

In Laymans Worten:

Aus dem gleichen Grund bauten die Hersteller von Elektrogeräten ihre Produkte mit elektrischen Steckern, anstatt einfach die Kabel abzuziehen, und die Häuser sind mit Wandsteckdosen ausgestattet, anstatt Kabel, die aus der Wand herausragen, abzuziehen.

Indem Sie stattdessen Standardstecker verwenden, können Sie die gleichen Geräte an jeden kompatiblen Stecker im ganzen Haus anschließen.

Aus Sicht der Steckdose spielt es keine Rolle, ob Sie ein Fernsehgerät oder eine Stereoanlage anschließen.

Das macht sowohl das Gerät als auch die Steckdose nützlicher.

Nehmen Sie zum Beispiel eine Methode, die eine Map als Argument akzeptiert.

Die Methode funktioniert unabhängig davon, ob Sie eine HashMap oder eine LinkedHashMap übergeben, sofern es sich um eine Unterklasse von Map handelt.

Das ist das Liskov-Substitutionsprinzip .

In dem von Ihnen angegebenen Beispielcode bedeutet dies, dass Sie später aus irgendeinem Grund die konkrete Implementierung von Hash ändern können und den Rest des Codes nicht mehr ändern müssen.

Das Problem mit der Software ist, dass die Leute, da es relativ einfach ist, Dinge später ohne Verschwendung von Ziegeln oder Mörtel zu ändern, davon ausgehen, dass sich diese Art des Vorausdenkens nicht lohnt. Die Realität hat uns jedoch gezeigt, dass die Softwarewartung sehr teuer ist.

Tulains Córdova
quelle
4

Es muss dem Prinzip der Schnittstellentrennung (das 'I' in SOLID) folgen ). Dadurch wird verhindert, dass Code, der diese Objekte verwendet, von den Methoden der Objekte abhängt, die er nicht benötigt, wodurch der Code weniger gekoppelt und daher einfacher zu ändern ist.

Wenn Sie zum Beispiel später herausfinden, dass Sie wirklich eine benötigen LinkedHashMap, können Sie diese Änderung sicher vornehmen, ohne dass sich dies auf einen anderen Code auswirkt.

Es gibt jedoch einen Kompromiss, da Sie den Code, der Ihr Objekt als Parameter verwenden kann, künstlich einschränken. Angenommen, es gibt eine Funktion, für die aus irgendeinem Grund eine erforderlich istHashMap . Wenn Sie a zurückgeben Map, können Sie Ihr Objekt nicht an diese Funktion übergeben. Sie müssen die Wahrscheinlichkeit abwägen, dass Sie irgendwann in der Zukunft die zusätzliche Funktionalität benötigen, die in der konkreteren Klasse liegt, mit dem Wunsch, die Kopplung zu begrenzen und Ihre öffentliche Schnittstelle so klein wie möglich zu halten.

Karl Bielefeldt
quelle
3

Wenn die Variable auf eine Schnittstelle beschränkt ist, wird sichergestellt, dass keine der Verwendungen dieser Variablen HashMapbestimmte Funktionen verwendet, die möglicherweise nicht auf der Schnittstelle vorhanden sind. Daher kann die Instanz später ohne Bedenken in eine andere Implementierung geändert werden, solange die neue Instanz auch die implementiert Schnittstelle.

Aus diesem Grund ist es immer empfehlenswert, Ihre Variablen als Schnittstelle zu deklarieren, wenn Sie eine Objektschnittstelle verwenden möchten, und nicht die jeweilige Implementierung. Dies gilt für alle Arten von Objekten, die Sie möglicherweise verwenden und die über eine Schnittstelle verfügen. Der Grund, warum Sie es oft sehen, ist, dass viele Menschen dies als Gewohnheit eingebaut haben.

Das heißt, es ist nicht schädlich, manchmal keine Schnittstellen mehr zu verwenden, und die meisten von uns befolgen diese Regel nicht immer ohne wirklichen Schaden. Es ist nur eine gute Praxis, sich an die Regeln zu halten, wenn Sie das Gefühl haben, dass der Code möglicherweise geändert wird und in Zukunft Wartung / Wachstum benötigt. Es ist weniger bedenklich, wenn Sie Code hacken, von dem Sie nicht vermuten, dass er eine lange Lebensdauer hat oder von großer Bedeutung ist. Ein Verstoß gegen diese Regel hat in der Regel eine kleine Konsequenz: Wenn Sie die Implementierung auf eine andere umstellen, müssen Sie sie möglicherweise etwas umgestalten. Wenn Sie sich also nicht immer daran halten, werden Sie sich nicht sehr verletzen .

Jimmy Hoffa
quelle