DEV Community

Cover image for Top 5 Design Patterns Every Software Engineer Should Know
Mhammed Talhaouy
Mhammed Talhaouy

Posted on

Top 5 Design Patterns Every Software Engineer Should Know

PHP, especially with its modern features like attributes, enums, and readonly properties in PHP, is well-suited for implementing design patterns. Here are five essential patterns every software engineer should know.


1. Singleton Pattern

Ensures a class has only one instance.

final class Config 
{
    private static ?Config $instance = null;

    private function __construct(public readonly array $settings) {}

    public static function getInstance(): Config 
    {
        return self::$instance ??= new Config(['env' => 'production']);
    }
}

// Usage
$config = Config::getInstance();
echo $config->settings['env']; // Output: production
Enter fullscreen mode Exit fullscreen mode

2. Factory Pattern

Centralizes object creation logic.

class DatabaseFactory 
{
    public static function create(string $type): Database 
    {
        return match ($type) {
            'mysql' => new MySQLDatabase(),
            'postgres' => new PostgresDatabase(),
            default => throw new InvalidArgumentException("Unknown database type"),
        };
    }
}

interface Database { public function connect(): void; }
class MySQLDatabase implements Database { public function connect() { echo "MySQL connected"; } }
class PostgresDatabase implements Database { public function connect() { echo "Postgres connected"; } }

// Usage
$db = DatabaseFactory::create('mysql');
$db->connect(); // Output: MySQL connected
Enter fullscreen mode Exit fullscreen mode

3. Observer Pattern

Notifies multiple objects about state changes.

class Event 
{
    private array $listeners = [];

    public function attach(callable $listener): void { $this->listeners[] = $listener; }
    public function trigger(string $data): void { foreach ($this->listeners as $listener) $listener($data); }
}

// Usage
$event = new Event();
$event->attach(fn($data) => print "Listener 1: $data\n");
$event->attach(fn($data) => print "Listener 2: $data\n");

$event->trigger("Event triggered"); 
// Output:
// Listener 1: Event triggered
// Listener 2: Event triggered
Enter fullscreen mode Exit fullscreen mode

4. Decorator Pattern

Dynamically adds behavior to objects.

interface Text { public function render(): string; }

class PlainText implements Text 
{
    public function __construct(private string $text) {}
    public function render(): string { return $this->text; }
}

class BoldText implements Text 
{
    public function __construct(private Text $text) {}
    public function render(): string { return "<b>" . $this->text->render() . "</b>"; }
}

// Usage
$text = new BoldText(new PlainText("Hello, World!"));
echo $text->render(); // Output: <b>Hello, World!</b>
Enter fullscreen mode Exit fullscreen mode

5. Strategy Pattern

Switches between algorithms at runtime.

interface PaymentStrategy { public function pay(float $amount): void; }

class CreditCardPayment implements PaymentStrategy 
{
    public function pay(float $amount): void { echo "Paid $amount with Credit Card\n"; }
}

class PayPalPayment implements PaymentStrategy 
{
    public function pay(float $amount): void { echo "Paid $amount via PayPal\n"; }
}

class PaymentProcessor 
{
    public function __construct(private PaymentStrategy $strategy) {}

    public function execute(float $amount): void 
    {
        $this->strategy->pay($amount);
    }
}

// Usage
$processor = new PaymentProcessor(new PayPalPayment());
$processor->execute(100.00); // Output: Paid 100 via PayPal
Enter fullscreen mode Exit fullscreen mode

These patterns solve real-world problems and are fundamental to writing maintainable and scalable applications.

Which pattern resonates with your current project? Let’s discuss in the comments! 🚀

Top comments (0)