Singleton Pattern: Richtig einsetzen | Enjyn Gruppe
Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

241 Dokumentationen verfügbar

Wissensdatenbank

Singleton Pattern Verwendung

Zuletzt aktualisiert: 20.01.2026 um 11:26 Uhr

Singleton Pattern: Richtig einsetzen

Das Singleton Pattern stellt sicher, dass eine Klasse nur eine Instanz hat. Lernen Sie wann es sinnvoll ist und welche Alternativen es gibt.

Das Pattern

class Singleton {
    private static ?self $instance = null;

    // Private Constructor verhindert new Singleton()
    private function __construct() {}

    // Verhindert Klonen
    private function __clone() {}

    // Verhindert Unserialisierung
    public function __wakeup() {
        throw new Exception("Cannot unserialize singleton");
    }

    // Einziger Zugriffspunkt
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

// Verwendung
$instance1 = Singleton::getInstance();
$instance2 = Singleton::getInstance();

var_dump($instance1 === $instance2); // true - Gleiche Instanz

Praktisches Beispiel: Logger

class Logger {
    private static ?self $instance = null;
    private array $logs = [];
    private $fileHandle;

    private function __construct() {
        $this->fileHandle = fopen('app.log', 'a');
    }

    public function __destruct() {
        if ($this->fileHandle) {
            fclose($this->fileHandle);
        }
    }

    public static function getInstance(): self {
        return self::$instance ??= new self();
    }

    public function log(string $level, string $message): void {
        $entry = sprintf(
            "[%s] %s: %s\n",
            date('Y-m-d H:i:s'),
            strtoupper($level),
            $message
        );

        $this->logs[] = $entry;
        fwrite($this->fileHandle, $entry);
    }

    public function info(string $message): void {
        $this->log('info', $message);
    }

    public function error(string $message): void {
        $this->log('error', $message);
    }

    public function getLogs(): array {
        return $this->logs;
    }
}

// Überall in der Anwendung
Logger::getInstance()->info('User logged in');
Logger::getInstance()->error('Database connection failed');

JavaScript Singleton

// ES6 Module sind bereits Singletons!
// logger.js
class Logger {
    constructor() {
        this.logs = [];
    }

    log(level, message) {
        const entry = `[${new Date().toISOString()}] ${level}: ${message}`;
        this.logs.push(entry);
        console.log(entry);
    }

    info(message) { this.log('INFO', message); }
    error(message) { this.log('ERROR', message); }
}

// Eine einzige Instanz exportieren
export const logger = new Logger();

// Verwendung überall gleich
import { logger } from './logger.js';
logger.info('Application started');
// Klassischer Singleton in JavaScript
const Singleton = (function() {
    let instance;

    function createInstance() {
        return {
            timestamp: Date.now(),
            data: {},
            setData(key, value) {
                this.data[key] = value;
            },
            getData(key) {
                return this.data[key];
            }
        };
    }

    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

// Verwendung
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true

Probleme mit Singleton

// ❌ Problem 1: Versteckte Abhängigkeiten
class UserService {
    public function createUser(string $name): void {
        // Woher kommt der Logger? Nicht ersichtlich!
        Logger::getInstance()->info("Creating user: {$name}");

        // Woher kommt die DB? Versteckt!
        Database::getInstance()->insert('users', ['name' => $name]);
    }
}

// ❌ Problem 2: Schwer testbar
class UserServiceTest extends TestCase {
    public function testCreateUser(): void {
        $service = new UserService();
        $service->createUser('Test');
        // Wie mocken wir Logger und Database?
        // Singleton macht das sehr schwierig!
    }
}

// ❌ Problem 3: Globaler State
// Änderungen wirken sich überall aus
Logger::getInstance()->setLevel('debug');
// Jetzt loggt ALLES in der Anwendung auf Debug-Level

// ❌ Problem 4: Thread-Safety (in multithreaded Umgebungen)
// Race Condition möglich bei getInstance()

Bessere Alternative: Dependency Injection

// ✅ Klare Abhängigkeiten
class UserService {
    public function __construct(
        private LoggerInterface $logger,
        private DatabaseInterface $db
    ) {}

    public function createUser(string $name): void {
        $this->logger->info("Creating user: {$name}");
        $this->db->insert('users', ['name' => $name]);
    }
}

// Einfach testbar
class UserServiceTest extends TestCase {
    public function testCreateUser(): void {
        $mockLogger = $this->createMock(LoggerInterface::class);
        $mockDb = $this->createMock(DatabaseInterface::class);

        $mockLogger->expects($this->once())
            ->method('info')
            ->with($this->stringContains('Test'));

        $mockDb->expects($this->once())
            ->method('insert');

        $service = new UserService($mockLogger, $mockDb);
        $service->createUser('Test');
    }
}

// DI Container kümmert sich um Instanzen
// Container kann intern Singletons verwalten
$container->singleton(LoggerInterface::class, fn() => new FileLogger());

Wann Singleton SINNVOLL ist

// ✅ 1. Ressourcen-Manager (Datenbankverbindungen)
class ConnectionPool {
    private static ?self $instance = null;
    private array $connections = [];

    public static function getInstance(): self {
        return self::$instance ??= new self();
    }

    public function getConnection(): PDO {
        // Verbindungen wiederverwenden statt neu erstellen
    }
}

// ✅ 2. Konfiguration (Read-only)
class Config {
    private static ?self $instance = null;
    private array $config;

    private function __construct() {
        $this->config = require 'config.php';
    }

    public static function getInstance(): self {
        return self::$instance ??= new self();
    }

    public function get(string $key, mixed $default = null): mixed {
        return $this->config[$key] ?? $default;
    }
}

// ✅ 3. Caching
class Cache {
    private static ?self $instance = null;
    private array $store = [];

    public static function getInstance(): self {
        return self::$instance ??= new self();
    }

    public function get(string $key): mixed {
        return $this->store[$key] ?? null;
    }

    public function set(string $key, mixed $value): void {
        $this->store[$key] = $value;
    }
}

Singleton + Dependency Injection

// Moderne Lösung: Container verwaltet Singletons

// Container-Konfiguration
$container = new Container();

// Als Singleton registrieren
$container->singleton(LoggerInterface::class, function($c) {
    return new FileLogger('/var/log/app.log');
});

// Normal (neue Instanz jedes Mal)
$container->bind(UserService::class, function($c) {
    return new UserService(
        $c->get(LoggerInterface::class),  // Immer gleiche Instanz
        $c->get(DatabaseInterface::class)
    );
});

// Verwendung
$logger1 = $container->get(LoggerInterface::class);
$logger2 = $container->get(LoggerInterface::class);
// $logger1 === $logger2 (Singleton durch Container)

// Aber: Klassen haben keine statischen Aufrufe
// Abhängigkeiten sind klar sichtbar
// Einfach testbar

Thread-Safe Singleton (PHP 8.1+)

// Mit Enums (PHP 8.1+) - automatisch Singleton
enum AppLogger {
    case Instance;

    private array $logs;

    public function log(string $message): void {
        // Enum-Cases sind Singletons
    }
}

// Verwendung
AppLogger::Instance->log('Message');
⚠️ Empfehlung: Vermeiden Sie klassische Singletons mit statischen Methoden. Nutzen Sie stattdessen Dependency Injection Container, die Instanzen als Singletons verwalten können. So behalten Sie Testbarkeit und klare Abhängigkeiten.

Weitere Informationen

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
120