← Tilbage til patterns

Singleton Pattern

Sikrer at en klasse kun har én instans og giver global adgang til den.

CreationalBegynder-venlig

Om Patterned

Singleton Pattern er et kreativt design pattern, der blev formaliseret i 'Design Patterns: Elements of Reusable Object-Oriented Software' af Gang of Four i 1994, selvom konceptet eksisterede tidligere. Patterned sikrer, at en klasse kun kan instantieres én gang gennem hele applikationens livscyklus og tilbyder et globalt adgangspunkt til denne instans. Dette er særligt vigtigt i scenarier hvor du har brug for præcis én instans af en klasse til at koordinere handlinger på tværs af systemet, såsom database forbindelser, loggere eller konfigurationsobjekter. Singleton Pattern er et af de mest kendte og samtidig mest debatterede patterns, da det kan føre til tight coupling og vanskeliggøre testing. I moderne PHP udvikling skal Singleton bruges sparsomt og ofte kan dependency injection være et bedre alternativ.

Key Points

  • Sikrer kun én instans af en klasse eksisterer
  • Giver globalt adgangspunkt til instansen
  • Lazy initialization - instansen oprettes kun når den er nødvendig
  • Constructor skal være private for at forhindre direkte instantiering
  • Clone og unserialize skal også forhindres
  • Thread-safety skal overvejes i concurrent environments
  • Kan gøre unit testing vanskeligt
  • Ofte betragtet som anti-pattern i moderne udvikling
  • Dependency Injection er ofte et bedre alternativ
  • Skal bruges sparsomt og kun når det er absolut nødvendigt
  • Kan føre til tight coupling mellem komponenter
  • Vanskelig at mock i tests

Kode Eksempel

<?php

declare(strict_types=1);

namespace App\Database;

use PDO;
use PDOException;

/**
 * Database Connection Singleton
 * Sikrer kun én database forbindelse gennem hele applikationen
 */
final class DatabaseConnection
{
    private static ?DatabaseConnection $instance = null;
    private PDO $connection;
    
    /**
     * Private constructor forhindrer direkte instantiering
     */
    private function __construct(
        private readonly string $host = 'localhost',
        private readonly string $database = 'myapp',
        private readonly string $username = 'root',
        private readonly string $password = ''
    ) {
        $this->connect();
    }
    
    /**
     * Forhindrer kloning af singleton instansen
     */
    private function __clone(): void
    {
        throw new \Exception('Cannot clone singleton instance');
    }
    
    /**
     * Forhindrer unserialization af singleton instansen
     */
    public function __wakeup(): void
    {
        throw new \Exception('Cannot unserialize singleton instance');
    }
    
    /**
     * Returnerer singleton instansen
     */
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    /**
     * Opretter database forbindelse
     */
    private function connect(): void
    {
        try {
            $dsn = "mysql:host={$this->host};dbname={$this->database};charset=utf8mb4";
            
            $this->connection = new PDO($dsn, $this->username, $this->password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ]);
        } catch (PDOException $e) {
            throw new \RuntimeException(
                'Database connection failed: ' . $e->getMessage()
            );
        }
    }
    
    /**
     * Returnerer PDO forbindelsen
     */
    public function getConnection(): PDO
    {
        return $this->connection;
    }
    
    /**
     * Udfører en query og returnerer resultatet
     */
    public function query(string $sql, array $params = []): array
    {
        $stmt = $this->connection->prepare($sql);
        $stmt->execute($params);
        
        return $stmt->fetchAll();
    }
}

// Brug af Singleton Pattern
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();

// $db1 og $db2 er samme instans
var_dump($db1 === $db2); // bool(true)

// Brug forbindelsen
$users = $db1->query('SELECT * FROM users WHERE active = ?', [1]);

// Samme forbindelse bruges overalt
$products = $db2->query('SELECT * FROM products LIMIT 10');

Fordele

  • +Kontrolleret adgang til én enkelt instans
  • +Reduceret hukommelsesforbrug ved at genbruge samme instans
  • +Lazy initialization - ressourcer allokeres kun når nødvendigt
  • +Global adgang til instansen fra hvor som helst i koden
  • +Kan udvides ved at subclasse (dog med forsigtighed)
  • +Sikrer konsistent tilstand gennem hele applikationen

Ulemper

  • Vanskeliggør unit testing da det introducerer global state
  • Bryder Single Responsibility Principle - klassen styrer både sin logik og sin instantiering
  • Skjuler dependencies - andre klasser' afhængigheder er ikke synlige
  • Kan føre til tight coupling mellem komponenter

Hvornår bruges det?

  • Database forbindelser - én connection pool gennem hele applikationen
  • Logger instanser - centraliseret logging til samme destination
  • Konfigurationsobjekter - global adgang til applikationsindstillinger
  • Cache managere - én cache instans delt på tværs af systemet
  • Session handlers - sikrer konsistent session håndtering
  • API rate limiters - tracker requests på tværs af applikationen

Best Practices

  • Brug final keyword på klassen for at forhindre arv
  • Gør constructor, __clone() og __wakeup() private/protected
  • Overvej Dependency Injection som alternativ
  • Brug kun når du virkelig behøver én enkelt instans
  • Dokumenter tydeligt hvorfor Singleton er nødvendig
  • Tænk på thread-safety i concurrent environments
  • Undgå at gemme mutable state i Singleton
  • Gør Singleton testbar ved at tilbyde en reset metode til tests

Quick Info

Kategori
Creational
Sværhedsgrad
Begynder-venlig
Relaterede Patterns
  • Factory Pattern - kan bruges til at oprette Singleton instanser
  • Dependency Injection - ofte et bedre alternativ til Singleton
  • Multiton Pattern - tillader kontrollerede multiple instanser