Design Patterns
This document outlines the team standards and best practices for implementing design patterns in our projects.
Introduction
Design patterns are proven solutions to common problems in software design. They represent best practices evolved over time by experienced software developers. This guide establishes our team's standards for implementing design patterns.
Core Principles
1. Follow Standard Implementations
When implementing any design pattern, strictly follow the standard implementation as described in the official reference:
TutorialsPoint Design Patterns Guide
Even if you have implemented a particular design pattern many times before, always refer back to the standard reference. This ensures consistency across the codebase and prevents drift from established patterns.
2. Preserve Interface Definitions
One of the most common mistakes when implementing design patterns is not adhering to standard interface definitions. Interfaces are not optional extras but essential components of the pattern structure.
// ❌ Incorrect: Non-standard interface
interface ProductCreator {
public function makeProduct($type);
}
// ✅ Correct: Standard Factory Method interface
interface Creator {
public function factoryMethod(): Product;
}
interface Product {
public function operation(): string;
}
3. Avoid Creative Implementations
When implementing design patterns, avoid creativity or "improvements" to the standard pattern. The patterns have been refined over decades and are optimized for their specific use cases.
// ❌ Avoid: Creative implementation of Singleton
class ImprovedSingleton {
private static $instances = [];
private function __construct() {}
public static function getInstance(string $key = 'default'): self {
if (!isset(self::$instances[$key])) {
self::$instances[$key] = new self();
}
return self::$instances[$key];
}
}
// ✅ Correct: Standard Singleton implementation
class Singleton {
private static $instance = null;
private function __construct() {}
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __clone() {}
private function __wakeup() {}
}
4. Conserve Mental Energy
Save your creativity and mental energy for solving business problems rather than reinventing design patterns. The standard implementations are well-tested and understood by other developers.
Common Design Patterns
Below are examples of standard implementations for common design patterns. Always refer to the official reference for complete details.
Creational Patterns
- Factory Method
- Singleton
- Builder
// Standard Factory Method Pattern
// Product interface
interface Product {
public function operation(): string;
}
// Concrete products
class ConcreteProductA implements Product {
public function operation(): string {
return "Result of ConcreteProductA";
}
}
class ConcreteProductB implements Product {
public function operation(): string {
return "Result of ConcreteProductB";
}
}
// Creator interface
interface Creator {
public function factoryMethod(): Product;
public function someOperation(): string;
}
// Concrete creators
class ConcreteCreatorA implements Creator {
public function factoryMethod(): Product {
return new ConcreteProductA();
}
public function someOperation(): string {
$product = $this->factoryMethod();
return "Creator A: " . $product->operation();
}
}
class ConcreteCreatorB implements Creator {
public function factoryMethod(): Product {
return new ConcreteProductB();
}
public function someOperation(): string {
$product = $this->factoryMethod();
return "Creator B: " . $product->operation();
}
}
// Client code
function clientCode(Creator $creator) {
echo $creator->someOperation();
}
// Usage
clientCode(new ConcreteCreatorA());
clientCode(new ConcreteCreatorB());
// Standard Singleton Pattern
class Singleton {
private static $instance = null;
// Private constructor to prevent direct instantiation
private function __construct() {}
// The static method that controls access to the singleton instance
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// Prevent cloning of the instance
private function __clone() {}
// Prevent unserializing of the instance
private function __wakeup() {}
// Example business method
public function businessLogic() {
// ...
}
}
// Usage
$singleton = Singleton::getInstance();
$singleton->businessLogic();
// Standard Builder Pattern
// Product
class Product {
private $parts = [];
public function add(string $part): void {
$this->parts[] = $part;
}
public function listParts(): string {
return "Product parts: " . implode(', ', $this->parts);
}
}
// Builder interface
interface Builder {
public function reset(): void;
public function buildPartA(): void;
public function buildPartB(): void;
public function buildPartC(): void;
}
// Concrete builder
class ConcreteBuilder implements Builder {
private $product;
public function __construct() {
$this->reset();
}
public function reset(): void {
$this->product = new Product();
}
public function buildPartA(): void {
$this->product->add("PartA");
}
public function buildPartB(): void {
$this->product->add("PartB");
}
public function buildPartC(): void {
$this->product->add("PartC");
}
public function getProduct(): Product {
$result = $this->product;
$this->reset();
return $result;
}
}
// Director
class Director {
private $builder;
public function setBuilder(Builder $builder): void {
$this->builder = $builder;
}
public function buildMinimalViableProduct(): void {
$this->builder->buildPartA();
}
public function buildFullFeaturedProduct(): void {
$this->builder->buildPartA();
$this->builder->buildPartB();
$this->builder->buildPartC();
}
}
// Usage
$director = new Director();
$builder = new ConcreteBuilder();
$director->setBuilder($builder);
$director->buildMinimalViableProduct();
$product = $builder->getProduct();
echo $product->listParts();
$director->buildFullFeaturedProduct();
$product = $builder->getProduct();
echo $product->listParts();
Structural Patterns
- Adapter
- Decorator
- Proxy
// Standard Adapter Pattern
// Target interface
interface Target {
public function request(): string;
}
// The existing class with incompatible interface
class Adaptee {
public function specificRequest(): string {
return "Specific request from Adaptee";
}
}
// Adapter makes Adaptee compatible with Target
class Adapter implements Target {
private $adaptee;
public function __construct(Adaptee $adaptee) {
$this->adaptee = $adaptee;
}
public function request(): string {
return "Adapter: (TRANSLATED) " . $this->adaptee->specificRequest();
}
}
// Client code
function clientCode(Target $target) {
echo $target->request();
}
// Usage
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
clientCode($adapter);
// Standard Decorator Pattern
// Component interface
interface Component {
public function operation(): string;
}
// Concrete component
class ConcreteComponent implements Component {
public function operation(): string {
return "ConcreteComponent";
}
}
// Base decorator
abstract class Decorator implements Component {
protected $component;
public function __construct(Component $component) {
$this->component = $component;
}
public function operation(): string {
return $this->component->operation();
}
}
// Concrete decorators
class ConcreteDecoratorA extends Decorator {
public function operation(): string {
return "ConcreteDecoratorA(" . parent::operation() . ")";
}
}
class ConcreteDecoratorB extends Decorator {
public function operation(): string {
return "ConcreteDecoratorB(" . parent::operation() . ")";
}
}
// Usage
$simple = new ConcreteComponent();
$decorator1 = new ConcreteDecoratorA($simple);
$decorator2 = new ConcreteDecoratorB($decorator1);
echo $decorator2->operation();
// Output: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
// Standard Proxy Pattern
// Subject interface
interface Subject {
public function request(): void;
}
// Real subject
class RealSubject implements Subject {
public function request(): void {
echo "RealSubject: Handling request.\n";
}
}
// Proxy
class Proxy implements Subject {
private $realSubject;
public function __construct(RealSubject $realSubject) {
$this->realSubject = $realSubject;
}
public function request(): void {
if ($this->checkAccess()) {
$this->realSubject->request();
$this->logAccess();
}
}
private function checkAccess(): bool {
echo "Proxy: Checking access prior to firing a real request.\n";
return true;
}
private function logAccess(): void {
echo "Proxy: Logging the time of request.\n";
}
}
// Client code
function clientCode(Subject $subject) {
$subject->request();
}
// Usage
$realSubject = new RealSubject();
$proxy = new Proxy($realSubject);
clientCode($proxy);
Behavioral Patterns
- Observer
- Strategy
- Template Method
// Standard Observer Pattern
// Subject interface
interface Subject {
public function attach(Observer $observer): void;
public function detach(Observer $observer): void;
public function notify(): void;
}
// Observer interface
interface Observer {
public function update(Subject $subject): void;
}
// Concrete subject
class ConcreteSubject implements Subject {
private $state;
private $observers = [];
public function attach(Observer $observer): void {
$this->observers[] = $observer;
}
public function detach(Observer $observer): void {
$key = array_search($observer, $this->observers, true);
if ($key !== false) {
unset($this->observers[$key]);
}
}
public function notify(): void {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setState($state): void {
$this->state = $state;
$this->notify();
}
public function getState() {
return $this->state;
}
}
// Concrete observers
class ConcreteObserverA implements Observer {
public function update(Subject $subject): void {
if ($subject->getState() < 3) {
echo "ConcreteObserverA: Reacted to the event.\n";
}
}
}
class ConcreteObserverB implements Observer {
public function update(Subject $subject): void {
if ($subject->getState() >= 3) {
echo "ConcreteObserverB: Reacted to the event.\n";
}
}
}
// Usage
$subject = new ConcreteSubject();
$observerA = new ConcreteObserverA();
$subject->attach($observerA);
$observerB = new ConcreteObserverB();
$subject->attach($observerB);
$subject->setState(2);
$subject->setState(5);
$subject->detach($observerB);
$subject->setState(1);
// Standard Strategy Pattern
// Strategy interface
interface Strategy {
public function doAlgorithm(array $data): array;
}
// Concrete strategies
class ConcreteStrategyA implements Strategy {
public function doAlgorithm(array $data): array {
sort($data);
return $data;
}
}
class ConcreteStrategyB implements Strategy {
public function doAlgorithm(array $data): array {
rsort($data);
return $data;
}
}
// Context
class Context {
private $strategy;
public function __construct(Strategy $strategy) {
$this->strategy = $strategy;
}
public function setStrategy(Strategy $strategy): void {
$this->strategy = $strategy;
}
public function doSomeBusinessLogic(): void {
$data = [1, 5, 2, 4, 3];
$result = $this->strategy->doAlgorithm($data);
echo "Context: Sorted data: " . implode(', ', $result) . "\n";
}
}
// Usage
$context = new Context(new ConcreteStrategyA());
$context->doSomeBusinessLogic();
$context->setStrategy(new ConcreteStrategyB());
$context->doSomeBusinessLogic();
// Standard Template Method Pattern
// Abstract class
abstract class AbstractClass {
// Template method
final public function templateMethod(): void {
$this->baseOperation1();
$this->requiredOperation1();
$this->baseOperation2();
$this->hook1();
$this->requiredOperation2();
$this->baseOperation3();
$this->hook2();
}
// These operations already have implementations
protected function baseOperation1(): void {
echo "AbstractClass: I am doing the bulk of the work\n";
}
protected function baseOperation2(): void {
echo "AbstractClass: But I let subclasses override some operations\n";
}
protected function baseOperation3(): void {
echo "AbstractClass: But I am doing the bulk of the work anyway\n";
}
// These operations must be implemented by subclasses
abstract protected function requiredOperation1(): void;
abstract protected function requiredOperation2(): void;
// Hooks can be overridden but it's not mandatory
protected function hook1(): void {}
protected function hook2(): void {}
}
// Concrete classes
class ConcreteClass1 extends AbstractClass {
protected function requiredOperation1(): void {
echo "ConcreteClass1: Implemented Operation1\n";
}
protected function requiredOperation2(): void {
echo "ConcreteClass1: Implemented Operation2\n";
}
protected function hook1(): void {
echo "ConcreteClass1: Overridden Hook1\n";
}
}
class ConcreteClass2 extends AbstractClass {
protected function requiredOperation1(): void {
echo "ConcreteClass2: Implemented Operation1\n";
}
protected function requiredOperation2(): void {
echo "ConcreteClass2: Implemented Operation2\n";
}
protected function hook2(): void {
echo "ConcreteClass2: Overridden Hook2\n";
}
}
// Client code
function clientCode(AbstractClass $class) {
$class->templateMethod();
}
// Usage
clientCode(new ConcreteClass1());
clientCode(new ConcreteClass2());
Real-World Example
- Repository Pattern
- Factory Method in Real Application
// Standard Repository Pattern Implementation
// Entity
class User {
private $id;
private $name;
private $email;
public function __construct(int $id, string $name, string $email) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
}
public function getId(): int {
return $this->id;
}
public function getName(): string {
return $this->name;
}
public function getEmail(): string {
return $this->email;
}
public function setName(string $name): void {
$this->name = $name;
}
public function setEmail(string $email): void {
$this->email = $email;
}
}
// Repository Interface
interface UserRepositoryInterface {
public function findById(int $id): ?User;
public function findAll(): array;
public function save(User $user): void;
public function delete(User $user): void;
}
// Concrete Repository Implementation
class MySqlUserRepository implements UserRepositoryInterface {
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
public function findById(int $id): ?User {
$statement = $this->connection->prepare(
"SELECT id, name, email FROM users WHERE id = :id"
);
$statement->execute(['id' => $id]);
$userData = $statement->fetch(PDO::FETCH_ASSOC);
if (!$userData) {
return null;
}
return new User(
$userData['id'],
$userData['name'],
$userData['email']
);
}
public function findAll(): array {
$statement = $this->connection->query(
"SELECT id, name, email FROM users"
);
$users = [];
while ($userData = $statement->fetch(PDO::FETCH_ASSOC)) {
$users[] = new User(
$userData['id'],
$userData['name'],
$userData['email']
);
}
return $users;
}
public function save(User $user): void {
if ($this->findById($user->getId())) {
// Update existing user
$statement = $this->connection->prepare(
"UPDATE users SET name = :name, email = :email WHERE id = :id"
);
$statement->execute([
'id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail()
]);
} else {
// Insert new user
$statement = $this->connection->prepare(
"INSERT INTO users (id, name, email) VALUES (:id, :name, :email)"
);
$statement->execute([
'id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail()
]);
}
}
public function delete(User $user): void {
$statement = $this->connection->prepare(
"DELETE FROM users WHERE id = :id"
);
$statement->execute(['id' => $user->getId()]);
}
}
// Service using the repository
class UserService {
private $userRepository;
public function __construct(UserRepositoryInterface $userRepository) {
$this->userRepository = $userRepository;
}
public function getUserById(int $id): ?User {
return $this->userRepository->findById($id);
}
public function getAllUsers(): array {
return $this->userRepository->findAll();
}
public function createUser(int $id, string $name, string $email): User {
$user = new User($id, $name, $email);
$this->userRepository->save($user);
return $user;
}
public function updateUser(User $user): void {
$this->userRepository->save($user);
}
public function deleteUser(User $user): void {
$this->userRepository->delete($user);
}
}
// Usage
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');
$userRepository = new MySqlUserRepository($pdo);
$userService = new UserService($userRepository);
$user = $userService->getUserById(1);
if ($user) {
$user->setName('New Name');
$userService->updateUser($user);
}
// Real-world Factory Method Pattern for Payment Processing
// Product Interface
interface PaymentProcessor {
public function processPayment(float $amount): PaymentResult;
}
// Concrete Products
class CreditCardProcessor implements PaymentProcessor {
public function processPayment(float $amount): PaymentResult {
// Credit card processing logic
return new PaymentResult(true, "CC-" . uniqid(), "Credit card payment processed");
}
}
class PayPalProcessor implements PaymentProcessor {
public function processPayment(float $amount): PaymentResult {
// PayPal processing logic
return new PaymentResult(true, "PP-" . uniqid(), "PayPal payment processed");
}
}
class BankTransferProcessor implements PaymentProcessor {
public function processPayment(float $amount): PaymentResult {
// Bank transfer processing logic
return new PaymentResult(true, "BT-" . uniqid(), "Bank transfer initiated");
}
}
// Result DTO
class PaymentResult {
public readonly bool $success;
public readonly string $transactionId;
public readonly string $message;
public function __construct(bool $success, string $transactionId, string $message) {
$this->success = $success;
$this->transactionId = $transactionId;
$this->message = $message;
}
}
// Creator Interface
interface PaymentProcessorFactory {
public function createPaymentProcessor(): PaymentProcessor;
public function processPayment(float $amount): PaymentResult;
}
// Concrete Creators
class CreditCardProcessorFactory implements PaymentProcessorFactory {
public function createPaymentProcessor(): PaymentProcessor {
return new CreditCardProcessor();
}
public function processPayment(float $amount): PaymentResult {
$processor = $this->createPaymentProcessor();
return $processor->processPayment($amount);
}
}
class PayPalProcessorFactory implements PaymentProcessorFactory {
public function createPaymentProcessor(): PaymentProcessor {
return new PayPalProcessor();
}
public function processPayment(float $amount): PaymentResult {
$processor = $this->createPaymentProcessor();
return $processor->processPayment($amount);
}
}
class BankTransferProcessorFactory implements PaymentProcessorFactory {
public function createPaymentProcessor(): PaymentProcessor {
return new BankTransferProcessor();
}
public function processPayment(float $amount): PaymentResult {
$processor = $this->createPaymentProcessor();
return $processor->processPayment($amount);
}
}
// Payment Service
class PaymentService {
public function processPayment(string $method, float $amount): PaymentResult {
$factory = $this->getProcessorFactory($method);
return $factory->processPayment($amount);
}
private function getProcessorFactory(string $method): PaymentProcessorFactory {
switch ($method) {
case 'credit_card':
return new CreditCardProcessorFactory();
case 'paypal':
return new PayPalProcessorFactory();
case 'bank_transfer':
return new BankTransferProcessorFactory();
default:
throw new InvalidArgumentException("Unsupported payment method: {$method}");
}
}
}
// Usage
$paymentService = new PaymentService();
$result = $paymentService->processPayment('credit_card', 99.99);
echo "Payment processed: {$result->message} (Transaction ID: {$result->transactionId})";
Summary
These standards establish our team's approach to implementing design patterns. Following these guidelines will ensure consistency across our codebase:
- Always follow standard implementations from the TutorialsPoint Design Patterns Guide
- Preserve interface definitions exactly as specified in the standard pattern
- Avoid creative implementations or "improvements" to standard patterns
- Save mental energy for solving business problems rather than reinventing patterns
Team CTO Guidance:
"Design patterns are standardized solutions to common problems. By following established implementations, we ensure our code is maintainable and understandable by all team members. Remember that interfaces are not optional extras but essential components of the pattern structure."
Actionable Rule:
- Always refer to the standard reference when implementing a design pattern
- Review design pattern implementations during code reviews to ensure they follow the standards
Further Reading: