Zum Inhalt
wannacode

Kapselung

Kurz gesagt: Kapselung bedeutet, dass eine Klasse ihre Daten mit private versteckt und sie nur über Properties und Methoden zeigt, die die Werte prüfen.

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:

Account account = new Account("Maria", 100);
account.Withdraw(500);
account.Balance = -500;
Console.WriteLine(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

private: nur die Klasse fasst es an

Schreib private vor das Feld statt public:

class Account
{
private string owner;
private double balance;
// ...
}

Jetzt kommt nur noch der Code in der Klasse Account an das Feld. Von außen lässt sich die Zeile

account.balance = -500;

nicht mehr kompilieren:

Fehler beim Kompilieren
error CS0122: 'Account.balance' is inaccessible due to its protection level

private und public heißen Zugriffsmodifizierer. Sie sagen, wer ein Feld, eine Methode oder einen Konstruktor sehen darf. Die Klasse versteckt ihre Daten und zeigt sie nur über ihre eigenen Methoden. Das nennt man Kapselung.

In C# schreibt man versteckte Felder mit kleinem Anfangsbuchstaben: balance, owner. Deshalb steht im Konstruktor this.balance = balance;: Feld und Parameter heißen jetzt gleich.

Genauer, für Neugierige
  • Ohne Modifizierer ist ein Feld oder eine Methode in C# private. Deshalb haben wir bis zu dieser Lektion überall public geschrieben.
  • Eine Klasse ohne Modifizierer ist internal: im ganzen Projekt (der Assembly) sichtbar, aber nicht aus anderen Projekten. Deshalb funktioniert class Product überall in unserem Programm.
  • Es gibt auch protected: sichtbar in der Klasse und in ihren Unterklassen. Du triffst es im Modul „Vererbung und Polymorphie“.

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:

Program.cs
class Account
{
private string owner;
private double balance;
public Account(string owner, double balance)
{
this.owner = owner;
this.balance = balance;
}
public string Owner
{
get { return owner; }
}
public double Balance
{
get { return balance; }
}
public void Deposit(double amount)
{
if (amount > 0)
{
balance += amount;
}
}
public bool Withdraw(double amount)
{
if (amount > 0 && amount <= balance)
{
balance -= amount;
return true;
}
return false;
}
}
class Program
{
static void Main()
{
Account account = new Account("Maria", 100);
account.Withdraw(30);
account.Deposit(50);
Console.WriteLine(account.Owner + ": " + account.Balance + " €");
}
}
Ausgabe
Maria: 120 €

Balance ist eine Property. Von außen benutzt du sie wie ein Feld: account.Balance. Innen ist sie eine Methode: get gibt den Wert des versteckten Feldes balance zurück. Das ist der Getter in C#. Der Kontostand ändert sich nur über Deposit und Withdraw, und die prüfen.

Withdraw gibt jetzt bool zurück: ob das Abheben geklappt hat. So weiß jeder, der abhebt, ob es funktioniert hat.

Account account = new Account("Maria", 100);
account.Withdraw(500);
account.Deposit(50);
Console.WriteLine(account.Balance);
Schritt für Schritt, in Worten
  1. balance ist private: 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. Die Property Balance gibt eine Kopie des Kontostands heraus. Das Feld selbst bleibt versteckt.

Probier es aus

Abgelehnt

Was gibt das Programm aus?

Account account = new Account("Maria", 100);
Console.WriteLine(account.Withdraw(150));
account.Deposit(-20);
Console.WriteLine(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
{
private int age;
public int Age
{
get { return age; }
set
{
if (value >= 0)
{
age = value;
}
}
}
}
Animal cat = new Animal();
cat.Age = 4;
cat.Age = -3;
Console.WriteLine(cat.Age);
Ausgabe
4

Die Property hat jetzt auch ein set: Das ist der Setter. Bei cat.Age = -3 läuft der Code in set, und value ist der Wert rechts von =, hier -3. Die Prüfung schlägt fehl, und das Alter bleibt 4.

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
{
private string name;
private double price;
private int quantity;
public Product(string name, double price)
{
this.name = name;
this.price = price;
this.quantity = 1;
}
public string Name
{
get { return name; }
}
public double Price
{
get { return price; }
}
public int Quantity
{
get { return quantity; }
set
{
if (value >= 1)
{
quantity = value;
}
}
}
}
Product coffee = new Product("Kaffee", 7.50);
coffee.Quantity = 3;
coffee.Quantity = 0;
Console.WriteLine(coffee.Name + " x" + coffee.Quantity);
Ausgabe
Kaffee x3

Die Regel lautet: Fang mit private 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.

Die Idee

Properties in kurz

Eine Property, die ein Feld ohne Prüfung nur liest und schreibt, kannst du in einer Zeile schreiben. C# legt das versteckte Feld selbst an:

class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; } = 1;
}
Product coffee = new Product { Name = "Kaffee", Price = 7.50 };
Console.WriteLine(coffee.Name + " x" + coffee.Quantity + ": " + coffee.Price + " €");
Ausgabe
Kaffee x1: 7.5 €

{ get; set; } ist eine automatisch implementierte Property. Mit = 1 hat sie einen Standardwert. new Product { Name = "Kaffee", Price = 7.50 } ist ein Objektinitialisierer: Er erzeugt das Objekt und setzt seine Properties in einem Paar geschweifter Klammern.

Du kannst auch nur das Schreiben verstecken. { get; private set; } kann von überall gelesen, aber nur in der Klasse geändert werden. { get; } wird nur im Konstruktor gesetzt:

class Account
{
public string Owner { get; }
public double Balance { get; private set; }
public Account(string owner, double balance)
{
Owner = owner;
Balance = balance;
}
public void Deposit(double amount)
{
if (amount > 0)
{
Balance += amount;
}
}
}
Account account = new Account("Maria", 100);
account.Deposit(20);
Console.WriteLine(account.Owner + ": " + account.Balance + " €");
Ausgabe
Maria: 120 €

Die Zeile account.Balance = -500; in Main lässt sich nicht kompilieren:

Fehler beim Kompilieren
error CS0272: The property or indexer 'Account.Balance' cannot be used in this context because the set accessor is inaccessible

Probier es aus

Was fehlt?

Der Kontostand soll nur in der Klasse sichtbar sein. Was steht in der Lücke?
class Account
{
     double balance;
}

Probier es aus

Finde den Fehler

Das Feld ist private, und trotzdem wird der Kontostand -500:

Account account = new Account();
account.Balance = 100;
account.Withdraw(30);
account.Balance = -500;
Console.WriteLine(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 private.

1. Person. Person mit Name und Alter, ein Konstruktor, die schreibgeschützten Properties 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.

Main
Account account = new Account("Peter", 50);
account.Deposit(25);
account.Deposit(-10);
Console.WriteLine(account.Withdraw(100));
Console.WriteLine(account.Withdraw(40));
Console.WriteLine("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 ein set, der eine negative Zahl ablehnt und eine Meldung ausgibt. Auch der Konstruktor geht durch dieselbe Prüfung.

Main
Animal cat = new Animal("Katze", 3);
cat.Age = 4;
cat.Age = -1;
Console.WriteLine(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 balance ist private. Von wo aus kann es geändert werden?
2. Warum hat das Konto keinen Setter für den Kontostand?
3. Was ist value im set einer Property?
4. Was bedeutet Kapselung?

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