Zum Inhalt
wannacode

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);
Ausgabe
-500

Die 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:

Fehler
SyntaxError: Private field '#balance' must be declared in an enclosing class

Die 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, mit this.#check().
  • Achte auf den Namen. account.balance = -500 ohne # fasst das private Feld nicht an: Es legt auf dem Objekt ein neues, öffentliches Feld balance an, 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:

bank.js
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} €`);
Ausgabe
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:

Fehler
TypeError: Cannot set property balance of #<Account> which has only a getter

withdraw 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
  1. #balance ist privat: Das Glas bedeutet, dass von außen kein Weg zum Feld führt.
  2. Das Abheben geht durch die Prüfung in der Methode: 500 ist mehr als 100. Abgelehnt, und der Kontostand bleibt, wie er ist.
  3. Auch die Einzahlung wird geprüft: 50 ist ein positiver Betrag. Die Methode addiert ihn.
  4. 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);
Antwort

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);
Ausgabe
4

set 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}`);
Ausgabe
Kaffee x3

Die 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?

Der Kontostand soll nur in der Klasse sichtbar sein. Wie heißt das Feld?
class Account {
   = 0;
}

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);
Ausgabe
-500

Welche Zeile in der Klasse bricht den Schutz?

Wähle die Zeile mit dem Fehler.

Ü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().

Ausgabe
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.

Das Programm
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} €`);
Ausgabe
false
true
Kontostand: 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 #.

Das Programm
const cat = new Animal("Katze", 3);
cat.age = 4;
cat.age = -1;
console.log(`${cat.species}, ${cat.age} Jahre`);
Ausgabe
Ungültiges Alter: -1
Katze, 4 Jahre

Weitere Aufgaben

  • Note. Eine Klasse Grade mit 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 Product aus der Lektion eine Methode, die einen Rabatt in Prozent anwendet und nur 1 bis 90% annimmt. Der Preis hat keinen Setter.

Test

Teste dich

1. Das Feld heißt #balance. Von wo aus kann es geändert werden?
2. Warum hat das Konto keinen Setter für den Kontostand?
3. Was ist value in set age(value)?
4. Was bedeutet Kapselung?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·