SOLID-Prinzipien für Anfänger: Ein leicht verständlicher Einstieg in die Softwareentwicklung
- Zahida Seo
- Mar 3
- 5 min read

Die Entwicklung von Software ist eine anspruchsvolle Aufgabe, die klare Strukturen und Prinzipien erfordert. Ein wichtiger Satz von Prinzipien, der besonders für Anfänger in der Softwareentwicklung von großem Nutzen sein kann, sind die SOLID-Prinzipien. Diese fünf Prinzipien helfen dabei, sauberen, wartbaren und flexiblen Code zu schreiben. Sie sind besonders wertvoll, wenn du gerade erst mit der objektorientierten Programmierung (OOP) beginnst.
In diesem Artikel werden wir die SOLID-Prinzipien auf eine einfache und verständliche Weise erklären, damit du als Anfänger ein solides Fundament in der Softwareentwicklung aufbauen kannst.
Was sind die SOLID-Prinzipien?
SOLID ist ein Akronym, das für fünf grundlegende Prinzipien der objektorientierten Programmierung steht:
S – Single Responsibility Principle (SRP): Eine Klasse sollte nur eine Verantwortung haben.
O – Open/Closed Principle (OCP): Software-Entitäten sollten offen für Erweiterungen, aber geschlossen für Änderungen sein.
L – Liskov Substitution Principle (LSP): Objekte einer abgeleiteten Klasse sollten ohne Probleme durch Objekte der Basisklasse ersetzt werden können.
I – Interface Segregation Principle (ISP): Clients sollten nicht gezwungen sein, Schnittstellen zu implementieren, die sie nicht benötigen.
D – Dependency Inversion Principle (DIP): Hochrangige Module sollten nicht von niederrangigen Modulen abhängen, sondern von Abstraktionen.
Wenn du diese Prinzipien in deinem Code umsetzt, hilft es, klare Strukturen zu schaffen, die den Code lesbarer, wartbarer und weniger fehleranfällig machen. Jetzt wollen wir uns jedes dieser Prinzipien einzeln anschauen.
1. Single Responsibility Principle (SRP): Eine Klasse sollte nur eine Verantwortung haben
Das Single Responsibility Principle (SRP) besagt, dass eine Klasse nur für eine einzige Aufgabe verantwortlich sein sollte. Das bedeutet, dass jede Klasse nur für einen bestimmten Bereich oder eine Funktion zuständig ist und Änderungen in einem Bereich nicht Auswirkungen auf andere Bereiche haben sollten.
Warum ist SRP wichtig?
Wenn du eine Klasse für mehrere Aufgaben verwendest, wird sie schnell unübersichtlich und schwer zu warten. Eine Klasse, die zu viele Aufgaben übernimmt, ist anfälliger für Fehler, da Änderungen in einer Funktionalität auch andere Teile der Klasse beeinflussen können.
Beispiel:
Angenommen, du hast eine Order-Klasse, die sowohl Bestellungen verarbeitet als auch Bestätigungs-E-Mails verschickt:
class Order {
public void processOrder() {
// Bestellung verarbeiten
}
public void sendConfirmationEmail() {
// Bestätigungsemail senden
}
}
Hier hat die Order-Klasse zwei Verantwortlichkeiten. Es wäre besser, diese Verantwortlichkeiten zu trennen:
class OrderProcessor {
public void processOrder(Order order) {
// Bestellung verarbeiten
}
}
class EmailService {
public void sendConfirmationEmail(Order order) {
// Bestätigungsemail senden
}
}
Vorteil: Der Code ist jetzt klarer und einfacher zu pflegen. Wenn du die Art und Weise ändern möchtest, wie E-Mails versendet werden, musst du nur die EmailService-Klasse ändern, ohne die OrderProcessor-Klasse zu beeinflussen. Auf der Seite andocs.biz findest du nützliche Tipps zur praktischen Anwendung der Solid Prinzipien.
2. Open/Closed Principle (OCP): Offen für Erweiterung, geschlossen für Änderungen
Das Open/Closed Principle (OCP) besagt, dass Software-Entitäten (z. B. Klassen, Methoden, Module) offen für Erweiterungen, aber geschlossen für Änderungen sein sollten. Das bedeutet, dass du den Code erweitern kannst, ohne bestehende Codezeilen zu ändern.
Warum ist OCP wichtig?
Stell dir vor, du hast eine Software, die im Laufe der Zeit neue Funktionen benötigt. Wenn du den Code immer wieder änderst, kann er fehleranfällig werden und die Stabilität des Programms gefährden. Stattdessen solltest du Erweiterungen ermöglichen, ohne den bestehenden Code zu modifizieren.
Beispiel:
Angenommen, du hast eine Klasse, die verschiedene Arten von Zahlungen verarbeitet:
class PaymentProcessor {
public void processCreditCardPayment(CreditCard card) {
// Kreditkarten-Zahlung verarbeiten
}
public void processPayPalPayment(PayPal payPal) {
// PayPal-Zahlung verarbeiten
}
}
Wenn du jetzt eine neue Zahlungsmethode hinzufügen möchtest, musst du die PaymentProcessor-Klasse ändern. Das verstößt gegen das OCP.
Stattdessen solltest du das Design so anpassen, dass die Klasse erweiterbar wird:
interface PaymentMethod {
void processPayment();
}
class CreditCardPayment implements PaymentMethod {
public void processPayment() {
// Kreditkarten-Zahlung verarbeiten
}
}
class PayPalPayment implements PaymentMethod {
public void processPayment() {
// PayPal-Zahlung verarbeiten
}
}
class PaymentProcessor {
public void processPayment(PaymentMethod paymentMethod) {
paymentMethod.processPayment();
}
}
Jetzt kannst du jederzeit neue Zahlungsmethoden hinzufügen, ohne den bestehenden Code zu ändern.
3. Liskov Substitution Principle (LSP): Substitution von abgeleiteten Klassen
Das Liskov Substitution Principle (LSP) besagt, dass Objekte einer abgeleiteten Klasse ohne Probleme durch Objekte der Basisklasse ersetzt werden können sollten, ohne dass das Verhalten des Programms beeinträchtigt wird.
Warum ist LSP wichtig?
Wenn eine abgeleitete Klasse die Erwartungen der Basisklasse nicht erfüllt, kann es zu Fehlern führen, wenn du ein Objekt der Basisklasse durch ein Objekt der abgeleiteten Klasse ersetzt. Das LSP hilft, sicherzustellen, dass die Vererbung korrekt eingesetzt wird.
Beispiel:
Stell dir vor, du hast eine Bird-Klasse und eine abgeleitete Ostrich-Klasse:
class Bird {
public void fly() {
// Vogel fliegt
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Strauße können nicht fliegen");
}
}
Hier verletzt der Ostrich die Erwartungen der Bird-Klasse, da Ostrich nicht fliegen kann. Das LSP fordert, dass Ostrich die gleiche Erwartung wie Bird erfüllt.
Eine bessere Lösung wäre es, die fly-Methode nur auf Vögel anzuwenden, die tatsächlich fliegen können:
class Bird {
public void fly() {
// Vogel fliegt
}
}
class Sparrow extends Bird {
@Override
public void fly() {
// Spatz fliegt
}
}
class Ostrich {
public void run() {
// Strauß läuft
}
}
Vorteil: Der Ostrich ist jetzt keine Unterklasse von Bird mehr und verletzt nicht die Erwartungen der Basisklasse.
4. Interface Segregation Principle (ISP): Kleine Schnittstellen sind besser
Das Interface Segregation Principle (ISP) besagt, dass Clients nicht gezwungen sein sollten, Schnittstellen zu implementieren, die sie nicht benötigen. Ein großes Interface sollte in kleinere, spezialisierte Schnittstellen unterteilt werden.
Warum ist ISP wichtig?
Ein großes Interface, das viele unnötige Methoden enthält, zwingt eine Klasse dazu, Methoden zu implementieren, die sie gar nicht benötigt. Dies führt zu unnötigem Code und erhöht die Komplexität.
Beispiel:
Angenommen, du hast ein MultiFunctionDevice-Interface, das viele Methoden umfasst:
interface MultiFunctionDevice {
void print();
void scan();
void fax();
}
Wenn du nur einen Drucker implementierst, aber keine Faxfunktion benötigst, ist das nicht ideal. Stattdessen kannst du kleinere, spezialisierte Schnittstellen verwenden:
interface Printer {
void print();
}
interface Scanner {
void scan();
}
interface Fax {
void fax();
}
class AllInOnePrinter implements Printer, Scanner, Fax {
public void print() { /* druckt */ }
public void scan() { /* scannt */ }
public void fax() { /* faxen */ }
}
class SimplePrinter implements Printer {
public void print() { /* druckt */ }
}
Vorteil: Der Code ist flexibler und einfacher zu erweitern.
5. Dependency Inversion Principle (DIP): Abstraktionen statt konkreter Implementierungen
Das Dependency Inversion Principle (DIP) besagt, dass hochrangige Module nicht von niederrangigen Modulen abhängen sollten, sondern beide von Abstraktionen abhängen sollten. Niederrangige Module sollten von den Abstraktionen abhängen, nicht von den konkreten Implementierungen.
Warum ist DIP wichtig?
Das DIP hilft, den Code flexibel und modular zu gestalten. Wenn hochrangige Module direkt von niederrangigen Modulen abhängen, wird der Code schwer erweiterbar und änderbar.
Beispiel:
class DatabaseConnection {
public void connect() {
// Verbindung zur Datenbank herstellen
}
}
class UserService {
private DatabaseConnection databaseConnection;
public UserService(DatabaseConnection databaseConnection) {
this.databaseConnection = databaseConnection;
}
public void saveUser(User user) {
databaseConnection.connect();
// Benutzer speichern
}
}
Im obigen Beispiel hängt UserService direkt von DatabaseConnection ab. Eine bessere Lösung wäre es, Abstraktionen zu verwenden:
interface Database {
void connect();
}
class DatabaseConnection implements Database {
public void connect() {
// Verbindung zur Datenbank herstellen
}
}
class UserService {
private Database database;
public UserService(Database database) {
this.database = database;
}
public void saveUser(User user) {
database.connect();
// Benutzer speichern
}
}
Vorteil: Der UserService hängt jetzt nur von der Database-Abstraktion ab, nicht von einer konkreten Implementierung. Das macht den Code flexibler und leichter testbar.
Die SOLID-Prinzipien sind eine ausgezeichnete Grundlage für die Entwicklung von wartbarem, erweiterbarem und fehlerfreiem Code, besonders für Anfänger in der Softwareentwicklung. Wenn du diese Prinzipien befolgst, wirst du feststellen, dass dein Code übersichtlicher wird, leichter zu testen und zu pflegen ist. Wenn du die SOLID-Prinzipien beherrschst, wirst du als Entwickler eine solide Grundlage haben, auf der du komplexe Softwarelösungen aufbauen kannst.
Comments