Skip to main content

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

warning

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

// 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());

Structural Patterns

// 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);

Behavioral Patterns

// 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);

Real-World Example

// 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);
}

Summary

These standards establish our team's approach to implementing design patterns. Following these guidelines will ensure consistency across our codebase:

  1. Always follow standard implementations from the TutorialsPoint Design Patterns Guide
  2. Preserve interface definitions exactly as specified in the standard pattern
  3. Avoid creative implementations or "improvements" to standard patterns
  4. Save mental energy for solving business problems rather than reinventing patterns
info

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