Dateien:

Wir nehmen die Modellierung mehrer Kontoarten als Beispiel für die Vorteile von Abstrakten Klassen. Zunächst schreiben wir unsere AbstraktKonto Klasse, welche Operationen implementiert, die alle Kontoarten gemeinsam haben.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public abstract class AbstraktKonto 
    {
        protected int _saldo;

        public final void einzahlen(int betrag) 
        {
            _saldo += betrag;
        }

        abstract public void abheben(int betrag);

        public int gibSaldo() 
        {
            return _saldo;
        }
    }

Erklärungen zu den Zeilen:

  • Z.3 protected bewirkt hier, dass das Feld auch im Subtyp ganz normal wie eine private Exemplarvariable verwendet werden kann
  • Z.5 final bewirkt, dass ein Subtyp diese Methode nicht überschreiben darf
  • Z.10 In abstrakten Klassen dürfen auch abstrakte Methoden deklariert werden. Konkrete Klassen, deren Supertyp eine solche Klasse ist, müssen dann die abstrakten Methoden implementieren, ähnlich wie bei einem Interface.

Nun, nachdem wir mit unseren abstrakten Klasse das allgemeine Verhalten eines Kontos implementiert haben, widmen wir uns jetzt unserer ersten konkreten Klasse, dem Sparkonto:

1
2
3
4
5
6
7
8
9
10
11
public class Sparkonto extends AbstraktKonto 
{
	@Override
	public void abheben(int betrag) 
	{
		if(super._saldo >= betrag)
		{
			super._saldo -= betrag;
		}
	}
}

Es fällt auf, das wir hier nur die Implementation für die abheben Operation schreiben müssen. Dies ist eines der angesprochenen Vorteile: es wird die Implementation einer abstrakten Klasse vererbt. Damit der Vorteil etwas deutlicher wird, implementieren wir jetzt noch ein Girokonto mit dispo.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Girokonto extends AbstraktKonto 
{
	private int _dispo;
	
	@Override
	public void abheben(int betrag) 
	{
		if(super._saldo + _dispo >= betrag)
		{
			super._saldo -= betrag;
		}
	}
}

Durch die Implementationsvererbung konnten wir uns in diesem Fall also sparen, einzahlen und gibSaldo mehrfach zu implementieren. Um das Beispiel weiter auszubauen, könnte man vereinfacht davon ausgehen, dass ein Tagesgeldkonto in etwa dem Sparkonto entspräche und eine Kreditkarte einem Girokonto. Wenn wir dies mithilfe eines UML-Klassendiagramms modellieren, sähe das folgendermaßen aus:
UML Klassendiagramm Konten

Ein weiterer Vorteil unserer Implementationsvererbung ist: wenn wir jetzt merken, dass unsere einzahlen Methode einen Fehler hat, müssen wir sie nicht mehrmals ändern. Dazu nehmen wir einfach mal an, dass einzahlen mit negativen Zahlen aufgerufen worden ist. Dies würde einen Abheben entsprechen, was wir aber nicht wollen. Um diesen Fehler zu beheben müssen wir nur AbstraktKonto abändern:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class AbstraktKonto 
{
	protected int _saldo;

	public final void einzahlen(int betrag) 
	{
		if(betrag > 0)
		{
			_saldo += betrag;

		}
	}

Dank der Vererbung haben wir nun den Fehler in allen 4 Klassen behoben.

Ich hoffe, es wurde deutlich, warum Implementationsvererbung mächtig und zugleich in der Softwareentwicklung sinnvoll ist. Bis zum nächsten Mal!