Home > Informatik > Begriffe und Konzepte > Prinzipien der OOP > Prinzip 4

4. Offen für Erweiterungen, geschlossen für Änderungen

Oft muss ein- und dasselbe Modul in verschiedenen Umgebungen eingesetzt werden, in verschiedenen Programmen oder im gleichen Programm unter verschiedenen Betriebssystemen.

Das naheliegende Verfahren ist es, dieses Modul zu kopieren und dann an die neue Umgebung anzupassen.

Dieses Verfahren birgt aber einige Risiken. Wenn sich die Anforderungen an dieses Modul ändern, müssen alle Kopien entsprechend verändert werden. Dabei kann es zu Inkonsistenzen und entsprechenden Fehlern kommen.

Ein Modul soll für Erweiterungen offen sein, aber nicht indem das Modul geändert wird, sondern indem es mit Erweiterungsmodulen gekoppelt wird, die entsprechend angepasst werden können.

"Das Modul soll also definierte Erweiterungspunkte bieten, an die sich die Erweiterungsmodule anknüpfen lassen." [1]

Open-Closed-Principle

Dieses Prinzip gehört zu den wichtigsten Regeln der objektorientierten Programmierung. Es besagt:

  • Offen für Erweiterung: Ein Modul soll sich erweitern lassen, z. B. durch zusätzliche Klassen.
  • Geschlossen für Änderung: Der bestehende Quelltext des Moduls soll dabei unverändert bleiben.

Ein häufiger Fehler besteht darin, eine Klasse zu kopieren und zu verändern, wenn man eine neue Variante braucht. Das ist riskant, denn spätere Änderungen müssen dann in allen Kopien vorgenommen werden. Auch das Prinzip 3 - Wiederholungen vermeiden - thematisiert ja dieses Problem.

Besser ist es, die ursprüngliche Klasse so zu entwerfen, dass sie nicht verändert werden muss – neue Varianten entstehen durch Unterklassen oder durch das Zusammenspiel mehrerer Klassen.

Ich habe mir dazu von ChatGPT ein einfaches Beispiel erstellen lassen, für Leute, die noch keine Erfahrung mit Java-Interfaces haben. Allzu schön ist dieses Beispiel nicht, aber etwas Besseres, das gleichzeitig leicht verständlich ist, habe ich nicht gefunden.

Beispiel: Begrüßung in verschiedenen Sprachen
public class Begruesser
{
   public void begruessen()
   {
      System.out.println("Hallo!");
   }
}

Nun erstellen wir zwei Varianten:

public class EnglischerBegruesser extends Begruesser
{
   public void begruessen()
   {
      System.out.println("Hello!");
   }
}

public class SpanischerBegruesser extends Begruesser
{
   public void begruessen()
   {
      System.out.println("¡Hola!");
   }
}

Verwendung im Programm:

Begruesser b1 = new Begruesser();
b1.begruessen();    // Ausgabe: Hallo!

Begruesser b2 = new EnglischerBegruesser();
b2.begruessen();    // Ausgabe: Hello!

Begruesser b3 = new SpanischerBegruesser();
b3.begruessen();    // Ausgabe: ¡Hola!
Was wurde erreicht?

Die ursprüngliche Klasse Begruesser musste nicht verändert werden. Neue Varianten entstehen durch Vererbung. Das entspricht dem Prinzip "offen für Erweiterung, geschlossen für Änderung".

Fazit:

Das Open-Closed-Principle hilft, Programme flexibler und besser wartbar zu machen. Wer auf Erweiterbarkeit achtet, vermeidet späteren Aufwand durch unnötige Änderungen.

Wie gesagt, ist das ein sehr einfaches Beispiel.

Beispiel: Versandkosten-Rechner

Von Google Gemini habe ich mir dann ein besseres (?), aber auch deutlich längeres Beispiel produzieren lassen. Hier zunächst die "schlechte" Version:

    // Berechnet die Kosten basierend auf dem Typ als String
    public double berechne(String typ) {
        if ("Standard".equals(typ)) {
            return 5.0;
        } else if ("Express".equals(typ)) {
            return 10.0;
        }
        return 0.0;
    }
}

public class Main {
    public static void main(String[] args) {
        VersandkostenRechner rechner = new VersandkostenRechner();
        System.out.println("Standardversand kostet: " + rechner.berechne("Standard") + "€");
        System.out.println("Expressversand kostet: " + rechner.berechne("Express") + "€");
    }
}

Warum ist diese Version schlecht? Wenn das Programm um eine neue Versandart ergänzt werden soll, muss die Klasse VersandkostenRechner um ein weiteres "else if" erweitert werden. Die Klasse ist daher nicht geschlossen für Veränderungen.

Google Gemini hat dann eine bessere Version generiert. Schauen wir uns diese mal an. Zunächst haben wir eine Klasse, die nur für die Versandart zuständig ist:

class Versandart {
    private String name;
    private double preis;

    public Versandart(String name, double preis) {
        this.name = name;
        this.preis = preis;
    }

    public double getPreis() {
        return preis;
    }

    public String getName() {
        return name;
    }
}

Das ist eine ganz einfache Klasse mit zwei Instanzvariablen für den Namen der Versandart und den Preis. Beide Größen werden dem Konstruktor übergeben. Mit zwei Getter-Methoden erhält man dann den Preis und die Versandart zurück.

Der Vorteil: Die Klasse Versandart kann nun mit allen möglichen Versandarten betrieben werden, sie muss dazu nicht erweitert werden.

Schauen wir uns nun die Klasse VersandkostenRechner an, die Gemini erzeugt hat:

class VersandkostenRechner {

    public double berechne(Versandart art) {
        return art.getPreis();
    }
}

public class Main {
    public static void main(String[] args) {
        VersandkostenRechner rechner = newVersandkostenRechner();

        Versandart standard = new Versandart("Standard", 5.0);
        Versandart express = new Versandart("Express", 10.0);
        Versandart international = new Versandart("International", 25.0); 

        System.out.println(standard.getName() + " kostet: " + rechner.berechne(standard) + "€");
        System.out.println(express.getName() + " kostet: " + rechner.berechne(express) + "€");
        System.out.println(international.getName() + " kostet: " + rechner.berechne(international) + "€");
    }
}

Na ja, so richtig glücklich bin ich mit diesem Beispiel auch noch nicht, aber es ist jedenfalls besser als das Beispiel von ChatGPT. Vielleicht finde ich in der Fachliteratur ja noch mal ein besseres Beispiel.

Quellen:

  1. Lahres et al.: Objektorientierte Programmierung, Rheinwerk Computing 2021.
  2. Barnes, Kölling: Java lernen mit BlueJ - Objects first. Pearson-Verlag 2019.
  3. Ullenboom: Java ist auch eine Insel, Rheinwerk Computing 2023.
  4. ChatGPT und Google Gemini