Facade & Dependency Injection (DI)

object-oriented programming (OOP) facade
object-oriented programming (OOP) facade
object-oriented programming (OOP) facade

Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.

This design pattern uses a number of different design principles, to provide ease of access to complex classes, what we call subsystem. This is done by encapsulating the subsystem classes into a Facade class, and then hiding them from the client classes so that the clients do not know about the details of the subsystem.

When use it: Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.

<?php
// --- Subsystems --- //

class InventoryService {
    public function checkStock(string $productId): bool {
        echo "Checking stock for product: $productId\n";
        // Simulated logic
        return true;
    }

    public function reserveItem(string $productId): void {
        echo "Reserving item: $productId\n";
    }
}

class PaymentService {
    public function processPayment(float $amount, string $method): bool {
        echo "Processing payment of $$amount via $method\n";
        // Simulated logic
        return true;
    }
}

class ShippingService {
    public function ship(string $productId, string $address): void {
        echo "Shipping product '$productId' to $address\n";
    }
}

class NotificationService {
    public function sendConfirmation(string $email, string $message): void {
        echo "Sending confirmation email to $email: $message\n";
    }
}

// --- Facade --- //

class OrderFacade {
    private InventoryService $inventory;
    private PaymentService $payment;
    private ShippingService $shipping;
    private NotificationService $notification;

    public function __construct() {
        $this->inventory = new InventoryService();
        $this->payment = new PaymentService();
        $this->shipping = new ShippingService();
        $this->notification = new NotificationService();
    }

    /**
     * Simplified order process using the Facade
     */
    public function placeOrder(string $productId, float $amount, string $paymentMethod, string $address, string $customerEmail): void {
        echo "Starting order process...\n";

        if (!$this->inventory->checkStock($productId)) {
            echo "Product not available in stock.\n";
            return;
        }

        $this->inventory->reserveItem($productId);

        if (!$this->payment->processPayment($amount, $paymentMethod)) {
            echo "Payment failed. Order canceled.\n";
            return;
        }

        $this->shipping->ship($productId, $address);
        $this->notification->sendConfirmation($customerEmail, "Your order for '$productId' has been successfully processed!");

        echo "Order completed successfully.\n";
    }
}

// --- Client Code --- //
$orderFacade = new OrderFacade();
$orderFacade->placeOrder(
    productId: 'SKU12345',
    amount: 199.99,
    paymentMethod: 'Credit Card',
    address: '123 Business Street, Cityville',
    customerEmail: 'customer@example.com'
);

As you can see, a facade class can be used to wrap all the interfaces and classes for a subsystem. It is your decision as to what you want to wrap.

Now let’s improve this code by using Dependency Injection (DI) in it.

Dependency Injection means giving an object its dependencies from something outside, instead of the object creating them by itself.

<?php
// --- Interfaces --- //

interface InventoryInterface {
    public function checkStock(string $productId): bool;
    public function reserveItem(string $productId): void;
}

interface PaymentInterface {
    public function processPayment(float $amount, string $method): bool;
}

interface ShippingInterface {
    public function ship(string $productId, string $address): void;
}

interface NotificationInterface {
    public function sendConfirmation(string $email, string $message): void;
}

// --- Concrete Implementations --- //

class InventoryService implements InventoryInterface {
    public function checkStock(string $productId): bool {
        echo "Checking stock for product: $productId\n";
        return true;
    }

    public function reserveItem(string $productId): void {
        echo "Reserving item: $productId\n";
    }
}

/**
 * Stripe payment service
 */
class StripePaymentService implements PaymentInterface {
    public function processPayment(float $amount, string $method): bool {
        echo "Processing payment of $$amount via $method\n";
        return true;
    }
}

/**
 * PayPal payment service
 */
class PaypalPaymentService implements PaymentInterface {
    public function processPayment(float $amount, string $method): bool {
        echo "Processing PayPal payment of $$amount using $method\n";
        return true;
    }
}

class ShippingService implements ShippingInterface {
    public function ship(string $productId, string $address): void {
        echo "Shipping product '$productId' to $address\n";
    }
}

class NotificationService implements NotificationInterface {
    public function sendConfirmation(string $email, string $message): void {
        echo "Sending confirmation email to $email: $message\n";
    }
}

// --- Facade --- //

class OrderFacade {
    public function __construct(
        private InventoryInterface $inventory,
        private PaymentInterface $payment,
        private ShippingInterface $shipping,
        private NotificationInterface $notification
    ) {}

    /**
     * High-level method hiding all subsystem complexity
     */
    public function placeOrder(
        string $productId,
        float $amount,
        string $paymentMethod,
        string $address,
        string $customerEmail
    ): void {
        echo "Starting order process...\n";

        if (!$this->inventory->checkStock($productId)) {
            echo "Product not available in stock.\n";
            return;
        }

        $this->inventory->reserveItem($productId);

        if (!$this->payment->processPayment($amount, $paymentMethod)) {
            echo "Payment failed. Order canceled.\n";
            return;
        }

        $this->shipping->ship($productId, $address);
        $this->notification->sendConfirmation(
            $customerEmail,
            "Your order for '$productId' has been successfully processed!"
        );

        echo "Order completed successfully.\n";
    }
}

// --- Client Code --- //
$inventory = new InventoryService();
$payment = new PaypalPaymentService();
$shipping = new ShippingService();
$notification = new NotificationService();

$orderFacade = new OrderFacade($inventory, $payment, $shipping, $notification);

$orderFacade->placeOrder(
    productId: 'SKU12345',
    amount: 199.99,
    paymentMethod: 'PayPal',
    address: '123 Business Street, Cityville',
    customerEmail: 'customer@example.com'
);

What changed?

  • Interfaces: define contracts for each subsystem. You can swap implementations easily (e.g., replace PayPalPaymentService with StripePaymentService).
  • Dependency Injection: the Facade doesn’t create its dependencies — they’re injected externally in client code.
  • Loose coupling: classes depend on abstractions, not concrete classes.
  • Testable: in unit tests, you can inject mock objects.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *