Datenkapselung: Unterschied zwischen den Versionen

Aus Programmieren-Wiki
(New page for getters/setters for lists)
 
KKeine Bearbeitungszusammenfassung
 
(5 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Bewertungsrichtlinie
{{CategoryBlock
|baustelle=Ja
|Baustelle=Ja
|blatt=2
|Java Grundlagen=Nein
|beschreibung=Es sollten nie komplexe veränderliche Objekte, Arrays oder Collections (z.B. ArrayList, HashMap) in direkter Form gesetzt oder ausgegeben werden, um einen Geheimnisverrat und eine Manipulation der Objekte zu verhindern. Betrachten wir hierzu ein kleines Beispiel:
|Organisation=Nein
 
|Programmierstil=Nein
<syntaxhighlight lang="Java">
|Bewertungsrichtlinie=Ja
// Innerhalb unserer Klasse
|blattAnnotation=2
|blattAbzug=3
}}
{{Inhaltsblock
|vorher==== Beschreibung ===
Es sollten nie komplexe veränderliche Objekte, Arrays oder Collections (z.B. ArrayList, HashMap) in direkter Form gesetzt oder ausgegeben werden, um einen Geheimnisverrat und eine Manipulation der Objekte zu verhindern. Betrachten wir hierzu ein kleines Beispiel:
|Beispiel=// Innerhalb unserer Klasse
private int[] array;
private int[] array;
   
   
Zeile 16: Zeile 22:
setSomeArray(somewhere);
setSomeArray(somewhere);
somewhere[2] = 666; // Manipulation durch böse Geister!
somewhere[2] = 666; // Manipulation durch böse Geister!
</syntaxhighlight>
|beispielname=DatenkapselungEx1
|nachher=Nach dem Setzen des Arrays somewhere wird der Klasse in einer Ausgabe Methode etwaiges zu sehen sein: {0, 42, 1337. 4711}. In der letzten Zeile also insbesondere nach Festlegung des privaten und internen Arrays, verändern wir vermeintlich nur das außerhalb liegende Array somewhere und müssen leider feststellen durch unsere nach wie vor gültige Objektrefenzierung erhalten wir nun auch in der Klasse Ausgabe des Arrays array: {0, 666, 1337, 4711}. Ähnliche Beispiele können wir konstruieren nach Ausgabe eines internen Arrays.
 
Erstes Ziel sollte demnach sein, die Schnittstellen der Klasse so zu gestalten, dass überhaupt keine Arrays oder Collections nach außen gegeben werden müssen, bspw. durch anbieten von Methoden zum Hinzufügen oder Entfernen einzelner Elemente anhand bestimmter Eigenschaften statt der Gesamtmenge.


Nach dem Setzen des Arrays somewhere wird der Klasse in einer Ausgabe Methode etwaiges zu sehen sein: {0, 42, 1337. 4711}. In der letzten Zeile also insbesondere nach Festlegung des privaten und internen Arrays, verändern wir vermeintlich nur das außerhalb liegende Array somewhere und müssen leider feststellen durch unsere nach wie vor gültige Objektrefenzierung erhalten wir nun auch in der Klasse Ausgabe des Arrays array: {0, 666, 1337, 4711}. Ähnliche Beispiele können wir konstruieren nach Ausgabe eines internen Arrays.
Eine einfache Variante, dieses Problem dennoch zu umgehen, ist, eine [https://en.wikipedia.org/wiki/Object_copying#Deep_copy echte Kopie] der jeweiligen übergebenen oder auszugebenden Objekte zu erstellen. Für auszugebende Objekte reicht oftmals auch eine unveränderliche Ansicht des Objekts, das heißt ein Wrapper-Objekt welches Daten vom ursprünglichen Objekt liest, aber keine verändernden Operationen zulässt.


Erstes Ziel sollte demnach sein, die Schnittstellen der Klasse so zu gestalten, dass überhaupt keine Arrays oder Listen nach außen gegeben werden müssen, bspw. durch anbieten von Methoden zum Hinzufügen oder entfernen einzelner Elemente anhand bestimmter Eigenschaften statt der Gesamtmenge.
==== Methoden der Standardbibliothek ====
; Collections
: Kopien von Collections können mit [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/List.html#copyOf(java.util.Collection) List.copyOf] bzw. den äquivalenten Methoden in [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#copyOf(java.util.Collection) Set] und [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#copyOf(java.util.Map) Map] erzeugt werden. Die Kopie ist hierbei immer unveränderbar; ist explizit eine veränderbare Collection gefragt kann direkt der Copy-Konstruktur der gewünschten Datenstruktur verwendet werden (z.B. [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ArrayList.html#%3Cinit%3E(java.util.Collection) ArrayList(Collection)]).
: Unveränderbare Ansichten erstellt [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#unmodifiableCollection(java.util.Collection) Collections.unmodifiableCollection] bzw. die spezifischen Methoden für [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#unmodifiableList(java.util.List) List], [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#unmodifiableSet(java.util.Set) Set] und weitere Interfaces.
: ''Anmerkung zu Records:'' Da diese immer unveränderbar sein sollten, empfiehlt es sich die <code>List/Set/Map.copyOf</code> Methoden im Konstruktor zu verwenden. Damit ist gewährleistet, dass spätere Änderungen an der übergebenen Collection keine Auswirkung auf das Objekt haben, und Änderungen an der im Record gespeicherten Collection nicht möglich sind.
; Arrays
: [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html#copyOf(T%5B%5D,int) Arrays.copyOf] kopiert ein Array. Unveränderbare Ansichten sind nur mit Einsatz von Collections möglich, da Arrays selber immer veränderbar sind: <code>Collections.unmodifiableList(Arrays.asList(array))</code>. Außerdem kann mit [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/List.html#of(E...) List.of] eine Kopie des Arrays als unveränderbare Liste erstellt werden.


Eine einfache Variante, dieses Problem dennoch zu umgehen, ist, eine [https://en.wikipedia.org/wiki/Object_copying#Deep_copy echte Kopie] der jeweiligen übergebenen oder auszugebenden Objekte zu erstellen. Im Falle von Arrays durch Nutzung von [https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#arraycopy(java.lang.Object,int,java.lang.Object,int,int)) System.arraycopy(…)] oder bei Collections und eigenen komplexen veränderliche Objekten durch eine "deep-copy", sprich: Ein neues Objekt vom gleichen Typ, mit gleichem Zustand erstellen und eintragen, das aber nicht das Original ist. Alternativ kann versucht werden, eigene komplexe veränderliche Objekte [https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html nicht veränderlich] zu gestalten, bspw. Koordinaten oder anderweitige Tupel.
'''Wichtig:''' alle genannten Methoden Kopieren nur die Datenstruktur selber, die enthaltenen Objekte werden nicht kopiert. Sind auch die Objekte veränderbar, muss manuell eine sogenannte Deep-Copy erstellt werden, bei der rekursiv alle veränderbaren Objekte kopiert werden. Oft ist es aber sinnvoller, veränderbare Strukturen zu vermeiden und eigene komplexe Datentypen [https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html nicht veränderlich] zu gestalten, bspw. Koordinaten oder anderweitige Tupel.
|schweregrad=leicht
}}
|negativ=<syntaxhighlight lang="Java">
{{Inhaltsblock
public void setSomeArray(int[] array) {
|color=red
|vorher=Negativbeispiel:
|Beispiel=public void setSomeArray(int[] array) {
     this.array = array;
     this.array = array;
}
}
Zeile 32: Zeile 49:
     return this.array;
     return this.array;
}
}
</syntaxhighlight>
|beispielname=DatenkapselungBad
|positiv=<syntaxhighlight lang="Java">
}}
public void setSomeArray(int[] array) {
{{Inhaltsblock
|color=green
|vorher=Positivbeispiel:
|Beispiel=public void setSomeArray(int[] array) {
     this.array = new int[array.length];
     this.array = new int[array.length];
     System.arraycopy(array, 0, this.array, 0, array.length);
     System.arraycopy(array, 0, this.array, 0, array.length);
Zeile 44: Zeile 64:
     return copy;
     return copy;
}
}
</syntaxhighlight>
|beispielname=DatenkapselungGood
|weiterlesen=Nein
}}
}}
{{DISPLAYTITLE:Datenkapselung}}
{{DEFAULTSORT:Datenkapselung}}

Aktuelle Version vom 14. Oktober 2025, 23:21 Uhr

🚧 Diese Seite befindet sich in Bearbeitung 🚧
🤓 Diese Seite ist eine Bewertungsrichtlinie, die ab Blatt 2 annotiert und ab Blatt 3 abgezogen wird. 🤓

Beschreibung

Es sollten nie komplexe veränderliche Objekte, Arrays oder Collections (z.B. ArrayList, HashMap) in direkter Form gesetzt oder ausgegeben werden, um einen Geheimnisverrat und eine Manipulation der Objekte zu verhindern. Betrachten wir hierzu ein kleines Beispiel:

// Innerhalb unserer Klasse
private int[] array;
 
public void setSomeArray(int[] array) {
    this.array = array;
}
 
// Und an einer anderen Stelle
int[] somewhere = {0, 42, 1337, 4711};
setSomeArray(somewhere);
somewhere[2] = 666; // Manipulation durch böse Geister!

Nach dem Setzen des Arrays somewhere wird der Klasse in einer Ausgabe Methode etwaiges zu sehen sein: {0, 42, 1337. 4711}. In der letzten Zeile also insbesondere nach Festlegung des privaten und internen Arrays, verändern wir vermeintlich nur das außerhalb liegende Array somewhere und müssen leider feststellen durch unsere nach wie vor gültige Objektrefenzierung erhalten wir nun auch in der Klasse Ausgabe des Arrays array: {0, 666, 1337, 4711}. Ähnliche Beispiele können wir konstruieren nach Ausgabe eines internen Arrays.

Erstes Ziel sollte demnach sein, die Schnittstellen der Klasse so zu gestalten, dass überhaupt keine Arrays oder Collections nach außen gegeben werden müssen, bspw. durch anbieten von Methoden zum Hinzufügen oder Entfernen einzelner Elemente anhand bestimmter Eigenschaften statt der Gesamtmenge.

Eine einfache Variante, dieses Problem dennoch zu umgehen, ist, eine echte Kopie der jeweiligen übergebenen oder auszugebenden Objekte zu erstellen. Für auszugebende Objekte reicht oftmals auch eine unveränderliche Ansicht des Objekts, das heißt ein Wrapper-Objekt welches Daten vom ursprünglichen Objekt liest, aber keine verändernden Operationen zulässt.

Methoden der Standardbibliothek

Collections
Kopien von Collections können mit List.copyOf bzw. den äquivalenten Methoden in Set und Map erzeugt werden. Die Kopie ist hierbei immer unveränderbar; ist explizit eine veränderbare Collection gefragt kann direkt der Copy-Konstruktur der gewünschten Datenstruktur verwendet werden (z.B. ArrayList(Collection)).
Unveränderbare Ansichten erstellt Collections.unmodifiableCollection bzw. die spezifischen Methoden für List, Set und weitere Interfaces.
Anmerkung zu Records: Da diese immer unveränderbar sein sollten, empfiehlt es sich die List/Set/Map.copyOf Methoden im Konstruktor zu verwenden. Damit ist gewährleistet, dass spätere Änderungen an der übergebenen Collection keine Auswirkung auf das Objekt haben, und Änderungen an der im Record gespeicherten Collection nicht möglich sind.
Arrays
Arrays.copyOf kopiert ein Array. Unveränderbare Ansichten sind nur mit Einsatz von Collections möglich, da Arrays selber immer veränderbar sind: Collections.unmodifiableList(Arrays.asList(array)). Außerdem kann mit List.of eine Kopie des Arrays als unveränderbare Liste erstellt werden.
Wichtig: alle genannten Methoden Kopieren nur die Datenstruktur selber, die enthaltenen Objekte werden nicht kopiert. Sind auch die Objekte veränderbar, muss manuell eine sogenannte Deep-Copy erstellt werden, bei der rekursiv alle veränderbaren Objekte kopiert werden. Oft ist es aber sinnvoller, veränderbare Strukturen zu vermeiden und eigene komplexe Datentypen nicht veränderlich zu gestalten, bspw. Koordinaten oder anderweitige Tupel.

Negativbeispiel:

public void setSomeArray(int[] array) {
    this.array = array;
}
 
public int[] getSomeArray() {
    return this.array;
}

Positivbeispiel:

public void setSomeArray(int[] array) {
    this.array = new int[array.length];
    System.arraycopy(array, 0, this.array, 0, array.length);
}
 
public int[] getSomeArray() {
    int[] copy = new int[this.array.length];
    System.arraycopy(this.array, 0, copy, 0, copy.length);
    return copy;
}