Kapselung
Kurz gesagt: Kapselung bedeutet, dass die Daten eines Objekts nur über seine Methoden geändert werden: in JavaScript mit privaten #-Feldern und mit Gettern und Settern über get und set.
Problem
Minus 500 €
In der letzten Lektion haben wir die Prüfung beim Abheben in eine Methode gesteckt. Das Feld ist aber offen, und jeder kann direkt hineinschreiben:
const account = new Account("Maria", 100);account.withdraw(500);account.balance = -500;console.log(account.balance);-500Die Methode hat das Abheben von 500 € richtig abgelehnt. Die nächste Zeile hat den Kontostand einfach von Hand gesetzt. Die Prüfung ist da, aber man kann sie umgehen. Wir brauchen eine Möglichkeit zu sagen: „Nur das Konto fasst den Kontostand an.“
Die Idee
#balance: nur die Klasse fasst es an
Schreib # vor den Namen des Feldes: #balance. Das Feld muss in der Klasse deklariert sein, und innen schreibst du this.#balance:
class Account { #owner; #balance;
constructor(owner, balance) { this.#owner = owner; this.#balance = balance; }
// ...}Ein Feld mit # ist privat: Nur der Code in der Klasse Account kommt daran. Steht außerhalb eine Zeile wie
account.#balance = -500;startet das Programm gar nicht erst:
SyntaxError: Private field '#balance' must be declared in an enclosing classDie Klasse versteckt ihre Daten und zeigt sie nur über ihre eigenen Methoden. Das nennt man Kapselung. Java und C# haben dafür Schlüsselwörter, die Zugriffsmodifizierer (private, public). In JavaScript ist privat #, und alles ohne # ist öffentlich.
Genauer, für Neugierige
- Auch Methoden können privat sein:
#check() { … }wird nur aus der Klasse aufgerufen, mitthis.#check(). - Achte auf den Namen.
account.balance = -500ohne#fasst das private Feld nicht an: Es legt auf dem Objekt ein neues, öffentliches Feldbalancean, ohne Fehler. - Bevor es
#gab (bis 2022), benutzte JavaScript die Abmachung_balance, wie Python. Du triffst sie in älterem Code.
Die Idee
Lesen, ohne anzufassen
Von außen können wir den Kontostand jetzt nicht einmal lesen. Deshalb gibt die Klasse eine Methode, die ihn nur zurückgibt:
class Account { #owner; #balance;
constructor(owner, balance) { this.#owner = owner; this.#balance = balance; }
get owner() { return this.#owner; }
get balance() { return this.#balance; }
deposit(amount) { if (amount > 0) { this.#balance += amount; } }
withdraw(amount) { if (amount > 0 && amount <= this.#balance) { this.#balance -= amount; return true; } return false; }}
const account = new Account("Maria", 100);account.withdraw(30);account.deposit(50);console.log(`${account.owner}: ${account.balance} €`);Maria: 120 €get balance() ist ein Getter: eine Methode, die du von außen wie ein Feld liest, account.balance, ohne Klammern. Sie gibt den Wert des privaten #balance zurück. Der Kontostand ändert sich nur über deposit und withdraw, und die prüfen.
Es gibt keinen Setter, also ändert die Zeile account.balance = -500; nichts: JavaScript ignoriert den Schreibversuch, ohne Fehler. Im strikten Modus (die Zeile "use strict"; ganz oben in der Datei) hält dieselbe Zeile das Programm an:
TypeError: Cannot set property balance of #<Account> which has only a getterwithdraw gibt jetzt true oder false zurück: ob das Abheben geklappt hat. So weiß jeder, der abhebt, ob es funktioniert hat.
const account = new Account("Maria", 100);account.withdraw(500);account.deposit(50);console.log(account.balance);Schritt für Schritt, in Worten
- #balance ist privat: Das Glas bedeutet, dass von außen kein Weg zum Feld führt.
- Das Abheben geht durch die Prüfung in der Methode: 500 ist mehr als 100. Abgelehnt, und der Kontostand bleibt, wie er ist.
- Auch die Einzahlung wird geprüft: 50 ist ein positiver Betrag. Die Methode addiert ihn.
- Der Getter balance gibt eine Kopie des Kontostands heraus. Das Feld #balance selbst bleibt versteckt.
Probier es aus
Abgelehnt
Was gibt das Programm aus?
const account = new Account("Maria", 100);console.log(account.withdraw(150));account.deposit(-20);console.log(account.balance);150 € sind mehr als der Kontostand, also gibt withdraw false zurück und lässt den Kontostand in Ruhe. Die Einzahlung von -20 € wird ebenfalls abgelehnt, weil der Betrag positiv sein muss. Der Kontostand bleibt 100.
Die Idee
Setter: eine Änderung mit Prüfung
Manchmal muss Code von außen ein Feld ändern, aber nicht auf jeden beliebigen Wert. Das Alter eines Tieres kann sich ändern, aber es kann nicht negativ sein:
class Animal { #age = 0;
get age() { return this.#age; }
set age(value) { if (value >= 0) { this.#age = value; } }}
const cat = new Animal();cat.age = 4;cat.age = -3;console.log(cat.age);4set age(value) ist ein Setter: die Methode, die bei cat.age = -3 aufgerufen wird. value ist der Wert rechts von =, hier -3. Die Prüfung schlägt fehl, und das Alter bleibt 4. Von außen sieht das Schreiben aus wie das Schreiben in ein einfaches Feld, aber es geht durch die Prüfung.
Die Idee
Welches Feld bekommt einen Setter?
Nicht jedes Feld braucht einen Setter, und manche brauchen nicht einmal einen Getter. Überleg, was Code von außen mit dem Produkt tun können soll:
- Name und Preis werden beim Erzeugen des Produkts gesetzt und danach nur gelesen: nur ein Getter;
- die Stückzahl im Warenkorb ändert sich, ist aber mindestens 1: ein Getter und ein Setter mit Prüfung.
class Product { #name; #price; #quantity = 1;
constructor(name, price) { this.#name = name; this.#price = price; }
get name() { return this.#name; }
get price() { return this.#price; }
get quantity() { return this.#quantity; }
set quantity(value) { if (value >= 1) { this.#quantity = value; } }}
const coffee = new Product("Kaffee", 7.50);coffee.quantity = 3;coffee.quantity = 0;console.log(`${coffee.name} x${coffee.quantity}`);Kaffee x3Die Regel lautet: Fang mit # für alles an und öffne nur, was du brauchst. Ein Setter, der ohne Prüfung alles speichert, bringt das Problem aus der ersten Karte zurück.
Probier es aus
Was fehlt?
class Account {
= 0;
}#balance: Mit # ist das Feld nur für den Code in Account sichtbar.Probier es aus
Finde den Fehler
Das Feld ist #balance, und trotzdem wird der Kontostand -500:
const account = new Account();account.balance = 100;account.withdraw(30);account.balance = -500;console.log(account.balance);-500Welche Zeile in der Klasse bricht den Schutz?
Wähle die Zeile mit dem Fehler.
set balance(value) speichert jeden Wert ohne Prüfung. Das Feld ist privat, aber der Setter öffnet es wieder. Entferne ihn und setz den Startbetrag im Konstruktor. Danach ändert er sich nur über deposit und withdraw.
Übung
Versteckte Daten
Löse die Aufgaben in deinem Editor. Alle Felder sind privat, mit #.
1. Person. Person mit Name und Alter, ein Konstruktor, die Getter name und age und introduce().
Maria ist 34 Jahre alt.Hallo, ich bin Maria und bin 34 Jahre alt.2. Ein Konto ohne Minus. Account mit Inhaber und Kontostand. Eine Einzahlung nimmt nur einen positiven Betrag an. Eine Abhebung gibt zurück, ob sie geklappt hat, und lässt den Kontostand nie unter 0 fallen. Code von außen kann den Kontostand lesen, aber nicht schreiben.
const account = new Account("Peter", 50);account.deposit(25);account.deposit(-10);console.log(account.withdraw(100));console.log(account.withdraw(40));console.log(`Kontostand: ${account.balance} €`);falsetrueKontostand: 35 €3. Tier. Animal mit Art und Alter. Die Art wird im Konstruktor gesetzt und danach nur gelesen. Das Alter hat einen Setter (set age), der eine negative Zahl ablehnt und eine Meldung ausgibt. Auch der Konstruktor geht durch dieselbe Prüfung: Schreib im Konstruktor this.age = age;, ohne #.
const cat = new Animal("Katze", 3);cat.age = 4;cat.age = -1;console.log(`${cat.species}, ${cat.age} Jahre`);Ungültiges Alter: -1Katze, 4 JahreWeitere Aufgaben
- Note. Eine Klasse
Grademit dem Namen eines Fachs und einer Note von 2 bis 6. Der Setter lehnt alles außerhalb dieser Grenzen ab. Probier es mit 5, 7 und 1. - Produkt mit Rabatt. Gib dem
Productaus der Lektion eine Methode, die einen Rabatt in Prozent anwendet und nur 1 bis 90% annimmt. Der Preis hat keinen Setter.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Parameter und return · Klassen und Objekte · Konstruktoren und this · Instanzmethoden
- Geht weiter in: static: was alle Objekte teilen · Vererbung · Polymorphie · Abstrakte Klassen ohne abstract
- Aufgaben zum Thema: Klassen und Objekte