SOLID Prinzipien Erklaert
SOLID Prinzipien für bessere Software
SOLID ist ein Akronym für fünf Designprinzipien objektorientierter Programmierung. Sie führen zu wartbarem, flexiblem und testbarem Code.
Überblick
| Buchstabe | Prinzip | Kurzform |
|---|---|---|
| S | Single Responsibility | Eine Klasse, eine Aufgabe |
| O | Open/Closed | Offen für Erweiterung, geschlossen für Änderung |
| L | Liskov Substitution | Subtypen müssen ersetzbar sein |
| I | Interface Segregation | Kleine, spezifische Interfaces |
| D | Dependency Inversion | Abhängig von Abstraktionen, nicht Konkretionen |
S - Single Responsibility Principle
// "Eine Klasse sollte nur einen Grund haben, sich zu ändern."
// ❌ Verletzt SRP - Klasse macht zu viel
class User {
public function __construct(
public string $name,
public string $email
) {}
public function save() {
// Datenbank-Logik
$db->insert('users', $this->toArray());
}
public function sendWelcomeEmail() {
// Email-Logik
$mailer->send($this->email, 'Welcome!', ...);
}
public function generateReport() {
// Report-Logik
return new PDF($this->toArray());
}
}
// ✅ SRP eingehalten - Jede Klasse hat eine Verantwortung
class User {
public function __construct(
public string $name,
public string $email
) {}
}
class UserRepository {
public function save(User $user): void {
$this->db->insert('users', $user->toArray());
}
public function findById(int $id): ?User {
// ...
}
}
class UserMailer {
public function sendWelcomeEmail(User $user): void {
$this->mailer->send($user->email, 'Welcome!', ...);
}
}
class UserReportGenerator {
public function generate(User $user): PDF {
return new PDF($user->toArray());
}
}
O - Open/Closed Principle
// "Software-Entitäten sollten offen für Erweiterung,
// aber geschlossen für Modifikation sein."
// ❌ Verletzt OCP - Muss bei jedem neuen Typ geändert werden
class DiscountCalculator {
public function calculate(Order $order): float {
if ($order->customerType === 'regular') {
return $order->total * 0.1;
} elseif ($order->customerType === 'premium') {
return $order->total * 0.2;
} elseif ($order->customerType === 'vip') {
return $order->total * 0.3;
}
// Neue Typen erfordern Änderung dieser Klasse!
return 0;
}
}
// ✅ OCP eingehalten - Erweiterbar ohne Änderung
interface DiscountStrategy {
public function calculate(Order $order): float;
}
class RegularDiscount implements DiscountStrategy {
public function calculate(Order $order): float {
return $order->total * 0.1;
}
}
class PremiumDiscount implements DiscountStrategy {
public function calculate(Order $order): float {
return $order->total * 0.2;
}
}
class VIPDiscount implements DiscountStrategy {
public function calculate(Order $order): float {
return $order->total * 0.3;
}
}
// Neue Typen: Einfach neue Klasse erstellen!
class EmployeeDiscount implements DiscountStrategy {
public function calculate(Order $order): float {
return $order->total * 0.5;
}
}
class DiscountCalculator {
public function __construct(
private DiscountStrategy $strategy
) {}
public function calculate(Order $order): float {
return $this->strategy->calculate($order);
}
}
L - Liskov Substitution Principle
// "Objekte einer Superklasse sollten durch Objekte einer
// Subklasse ersetzbar sein, ohne das Programm zu brechen."
// ❌ Verletzt LSP - Square verhält sich anders als erwartet
class Rectangle {
protected int $width;
protected int $height;
public function setWidth(int $width): void {
$this->width = $width;
}
public function setHeight(int $height): void {
$this->height = $height;
}
public function getArea(): int {
return $this->width * $this->height;
}
}
class Square extends Rectangle {
// Quadrat muss beide Seiten gleich halten
public function setWidth(int $width): void {
$this->width = $width;
$this->height = $width; // Unerwartetes Verhalten!
}
public function setHeight(int $height): void {
$this->width = $height; // Unerwartetes Verhalten!
$this->height = $height;
}
}
// Problem:
$rect = new Square();
$rect->setWidth(5);
$rect->setHeight(10);
echo $rect->getArea(); // Erwartet: 50, Tatsächlich: 100!
// ✅ LSP eingehalten - Separate Hierarchie
interface Shape {
public function getArea(): int;
}
class Rectangle implements Shape {
public function __construct(
private int $width,
private int $height
) {}
public function getArea(): int {
return $this->width * $this->height;
}
}
class Square implements Shape {
public function __construct(
private int $side
) {}
public function getArea(): int {
return $this->side * $this->side;
}
}
I - Interface Segregation Principle
// "Clients sollten nicht gezwungen werden, von Interfaces
// abzuhängen, die sie nicht nutzen."
// ❌ Verletzt ISP - Zu großes Interface
interface Worker {
public function work(): void;
public function eat(): void;
public function sleep(): void;
public function code(): void;
public function attendMeeting(): void;
}
class Developer implements Worker {
public function work(): void { /* ... */ }
public function eat(): void { /* ... */ }
public function sleep(): void { /* ... */ }
public function code(): void { /* ... */ }
public function attendMeeting(): void { /* ... */ }
}
class Robot implements Worker {
public function work(): void { /* ... */ }
public function eat(): void {
// Roboter essen nicht!
throw new Exception('Robots dont eat');
}
public function sleep(): void {
throw new Exception('Robots dont sleep');
}
// ...
}
// ✅ ISP eingehalten - Kleine, fokussierte Interfaces
interface Workable {
public function work(): void;
}
interface Feedable {
public function eat(): void;
}
interface Sleepable {
public function sleep(): void;
}
interface Programmer {
public function code(): void;
}
class Developer implements Workable, Feedable, Sleepable, Programmer {
public function work(): void { /* ... */ }
public function eat(): void { /* ... */ }
public function sleep(): void { /* ... */ }
public function code(): void { /* ... */ }
}
class Robot implements Workable {
public function work(): void { /* ... */ }
// Keine unnötigen Methoden!
}
D - Dependency Inversion Principle
// "High-Level-Module sollten nicht von Low-Level-Modulen abhängen.
// Beide sollten von Abstraktionen abhängen."
// ❌ Verletzt DIP - Direkte Abhängigkeit
class OrderService {
private MySQLDatabase $database;
private SMTPMailer $mailer;
public function __construct() {
$this->database = new MySQLDatabase(); // Konkrete Klasse!
$this->mailer = new SMTPMailer(); // Konkrete Klasse!
}
public function createOrder(Order $order): void {
$this->database->insert('orders', $order);
$this->mailer->send($order->customerEmail, 'Order confirmed');
}
}
// ✅ DIP eingehalten - Abhängig von Abstraktionen
interface DatabaseInterface {
public function insert(string $table, array $data): void;
}
interface MailerInterface {
public function send(string $to, string $message): void;
}
class OrderService {
public function __construct(
private DatabaseInterface $database,
private MailerInterface $mailer
) {}
public function createOrder(Order $order): void {
$this->database->insert('orders', $order->toArray());
$this->mailer->send($order->customerEmail, 'Order confirmed');
}
}
// Konkrete Implementierungen
class MySQLDatabase implements DatabaseInterface {
public function insert(string $table, array $data): void { /* ... */ }
}
class PostgreSQLDatabase implements DatabaseInterface {
public function insert(string $table, array $data): void { /* ... */ }
}
// Einfach austauschbar!
$service = new OrderService(
new PostgreSQLDatabase(), // Leicht wechselbar
new SendGridMailer() // Leicht wechselbar
);
// Einfach testbar!
$service = new OrderService(
new InMemoryDatabase(), // Test-Double
new FakeMailer() // Test-Double
);
Zusammenspiel der Prinzipien
// Alle SOLID Prinzipien in einem Beispiel
// Interfaces (I, D)
interface PaymentProcessor {
public function process(Payment $payment): PaymentResult;
}
interface PaymentLogger {
public function log(Payment $payment, PaymentResult $result): void;
}
// Konkrete Implementierungen (O, L)
class StripeProcessor implements PaymentProcessor {
public function process(Payment $payment): PaymentResult {
// Stripe-spezifische Logik
}
}
class PayPalProcessor implements PaymentProcessor {
public function process(Payment $payment): PaymentResult {
// PayPal-spezifische Logik
}
}
// Single Responsibility (S)
class PaymentService {
public function __construct(
private PaymentProcessor $processor, // DIP
private PaymentLogger $logger
) {}
public function pay(Payment $payment): PaymentResult {
$result = $this->processor->process($payment);
$this->logger->log($payment, $result);
return $result;
}
}
// Nutzung
$service = new PaymentService(
new StripeProcessor(),
new DatabaseLogger()
);
💡 Praktischer Tipp:
SOLID-Prinzipien sind Richtlinien, keine Gesetze. Wenden Sie sie an, wo sie Sinn ergeben. Übertriebene Anwendung führt zu Over-Engineering. Beginnen Sie einfach und refaktorisieren Sie, wenn Komplexität wächst.