12 PHP Software Design Patterns - With Examples
Software design patterns are reusable solutions to commonly occurring software architecture problems. Here are the 12 most common PHP patterns, and all you need to know to select the right one for your project.
In this article:
- What is Software Design Patterns
- Web Development Design Patterns vs Traditional Software Design Patterns
- 12 of the most common PHP design patterns
- Can I mix software design patterns?
- How to pick the right software design pattern for your project
What is Software Design Patterns
Software design patterns are reusable solutions to commonly occurring software design problems. They are general, proven solutions to design problems that can be adapted and applied to a wide range of situations.
Design patterns can be thought of as templates or blueprints for software development, helping developers to create flexible, reusable, and maintainable software systems. They provide a common language and framework for discussing and describing software design and architecture, making it easier for developers to communicate and collaborate on projects.
Design patterns are typically organized into categories based on the type of problem they solve, such as creational patterns, structural patterns, and behavioral patterns. Examples of design patterns include the Singleton pattern, the Factory pattern, the Observer pattern, and the Strategy pattern, among others.
By using design patterns, software developers can improve the quality and maintainability of their code, as well as reduce the time and effort required for development. However, it is important to use design patterns judiciously and not to overuse them, as this can lead to unnecessary complexity and decreased maintainability.
Web Development Design Patterns vs Traditional Software Design Patterns
Design patterns in web development are similar to traditional software development design patterns, but there are some differences due to the unique nature of web development.
In web development, the focus is on building user interfaces that are accessible and responsive across a wide range of devices and browsers. This requires a different set of design patterns than traditional software development, which typically focuses on creating backend systems or desktop applications.
Some of the design patterns commonly used in web development include the Model-View-Controller (MVC) pattern, which separates the application into three components: the model (data and logic), the view (user interface), and the controller (handles user input and manages communication between the model and the view). Another commonly used pattern is the Front Controller pattern, which provides a central point of control for handling requests and dispatching them to the appropriate handler.
Other patterns used in web development include the Template Method pattern, which defines a skeleton algorithm in a superclass and lets subclasses override specific steps of the algorithm, and the Decorator pattern, which allows for the dynamic addition of functionality to an object. These patterns help developers create modular and flexible code that can be easily adapted to changing requirements.
Overall, while there are some differences in the design patterns used in web development compared to traditional software development, the principles of modularity, flexibility, and maintainability remain the same.
12 of the most common PHP design patterns
There are several ways to structure your code, however, 12 of the most common software design patterns in PHP are:
- Singleton pattern
Ensures that only one instance of a class is created, and provides a global point of access to it. - Factory pattern
Provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. - Observer pattern
Defines a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically. - Strategy pattern
Defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime, allowing the algorithm to vary independently from clients that use it. - Decorator pattern
Adds behavior to an individual object, either statically or dynamically, without affecting other objects. - Adapter pattern
Converts the interface of a class into another interface that clients expect. - Template method pattern
Defines the skeleton of an algorithm in a superclass, but lets subclasses override specific steps of the algorithm without changing its structure. - Facade pattern
Provides a simplified interface to a complex system of classes, making it easier to use. - Iterator pattern
Provides a way to access the elements of an object sequentially without exposing its underlying representation. - Proxy pattern
Provides a surrogate or placeholder object to control access to another object. - Command pattern
Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. - Builder pattern
Separates the construction of a complex object from its representation, allowing different representations to be created using the same construction process.
The Singleton Software Design Pattern
The Singleton pattern is a creational design pattern that restricts the instantiation of a class to a single object. This is useful when we need to ensure that only one instance of a class exists and that this instance is accessible from all parts of the code.
In the Singleton pattern, a class has a private constructor and a static method that returns the same instance of the class every time it is called. If the instance does not yet exist, the static method creates it; otherwise, it returns the existing instance.
Here is an example of implementing the Singleton pattern in PHP:
class Singleton {
private static $instance;
private function __construct() {
// Private constructor to prevent instantiation from outside the class
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function doSomething() {
// Method to perform some action
}
}
In this example, the Singleton class has a private constructor, so it cannot be instantiated from outside the class. Instead, we create a static method getInstance() that checks if an instance of the class already exists. If it does not, we create a new instance of the class; otherwise, we return the existing instance. The doSomething() method is an example of a method that can be called on the singleton instance.
We can use the Singleton class as follows:
$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();
var_dump($singleton1 === $singleton2); // Output: true
In this example, we create two variables $singleton1 and $singleton2, both of which are assigned the same instance of the Singleton class using the getInstance() method. We then use the var_dump() function to compare these variables, and we see that they are the same instance of the class, confirming that the Singleton pattern has been implemented correctly.
The Factory Software Design Pattern
The Factory pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. The Factory pattern is useful when we need to create multiple objects that share the same interface or superclass, but differ in their implementation details.
In the Factory pattern, we create a factory class that defines a method for creating objects. This method takes some input parameters and returns an instance of an object that implements a common interface or extends a common superclass. Subclasses can then extend the factory class and override the factory method to create different types of objects.
Here is an example of implementing the Factory pattern in PHP:
interface Car {
public function drive();
}
class Sedan implements Car {
public function drive() {
echo "Driving a sedan\n";
}
}
class Suv implements Car {
public function drive() {
echo "Driving an SUV\n";
}
}
class CarFactory {
public function createCar($type) {
switch ($type) {
case 'sedan':
return new Sedan();
case 'suv':
return new Suv();
default:
throw new InvalidArgumentException('Invalid car type');
}
}
}
In this example, we define an interface Car and two classes that implement it: Sedan and Suv. We then define a CarFactory class with a createCar() method that takes a $type parameter and returns an instance of the appropriate class. The createCar() method uses a switch statement to determine which type of car to create.
We can use the CarFactory class as follows:
$carFactory = new CarFactory();
$sedan = $carFactory->createCar('sedan');
$suv = $carFactory->createCar('suv');
$sedan->drive(); // Output: Driving a sedan
$suv->drive(); // Output: Driving an SUV
In this example, we create a CarFactory object and use it to create a $sedan and $suv object using the createCar() method. We then call the drive() method on each object to demonstrate that they have different implementations.
By using the Factory pattern, we can create flexible and extensible code that is easy to modify and maintain. We can also hide the implementation details of object creation behind a simple interface, making it easier to use and understand.
The Observer Software Design Pattern
The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects, where when one object changes its state, all of its dependents are notified and updated automatically. The Observer pattern is useful when we have an object that needs to notify other objects about changes in its state.
In the Observer pattern, we have two main entities: the subject and the observer. The subject is the object that is being observed, and the observer is the object that is notified when the subject's state changes. The subject maintains a list of its observers and notifies them when its state changes.
Example of implementing the Observer pattern in PHP:
interface Subject {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
interface Observer {
public function update(Subject $subject);
}
class User implements Subject {
private $name;
private $email;
private $observers = [];
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
public function setName($name) {
$this->name = $name;
$this->notify();
}
public function setEmail($email) {
$this->email = $email;
$this->notify();
}
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function detach(Observer $observer) {
$index = array_search($observer, $this->observers);
if ($index !== false) {
array_splice($this->observers, $index, 1);
}
}
public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}
class UserLogger implements Observer {
public function update(Subject $subject) {
echo "Logging user: {$subject->getName()} ({$subject->getEmail()})\n";
}
}
class UserNotifier implements Observer {
public function update(Subject $subject) {
echo "Sending email to user: {$subject->getName()} ({$subject->getEmail()})\n";
}
}
In this example, we define two interfaces Subject and Observer, and two classes UserLogger and UserNotifier that implement the Observer interface. We also define a User class that implements the Subject interface and maintains a list of its observers.
The User class has setName() and setEmail() methods that update the user's name and email and then notify all of its observers by calling the notify() method. The attach() and detach() methods are used to add and remove observers from the list of observers.
The UserLogger and UserNotifier classes implement the Observer interface and define their own update() methods that are called when the User object's state changes. The UserLogger class logs information about the user, and the UserNotifier class sends an email to the user.
We can use the User class as follows:
$user = new User("John", "john@example.com");
$user->attach(new UserLogger());
$user->attach(new UserNotifier());
$user->setName("Jane");
$user->setEmail("jane@example.com");
// Output: Logging user: Jane (jane@example.com)
// Sending email to user: Jane (jane@example.com)
In this example, we create a new User object with the name "John" and email "john@example.com". We then attach a UserLogger and a UserNotifier observer to the user object using the attach() method.
We then call the setName() and setEmail() methods on the user object, which updates the user's state and notifies all of its observers using the notify() method. The observers then receive the notification and perform their respective actions.
By using the Observer pattern, we can create loosely coupled and extensible systems that can be easily modified and maintained. We can also decouple the subject and observer objects, making them easier to test and reuse.
The Strategy Software Design Pattern
The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one of them and makes them interchangeable at runtime. This pattern allows the algorithm to vary independently of the client that uses it. The Strategy pattern is useful when we need to dynamically switch between different algorithms or behaviors based on changing requirements or conditions.
In the Strategy pattern, we define a common interface for a family of related algorithms. We then define concrete implementations of this interface for each algorithm. We can then dynamically switch between different implementations of the interface, depending on the situation.
Example of implementing the Strategy pattern in PHP:
interface SortingStrategy {
public function sort(array $data): array;
}
class BubbleSort implements SortingStrategy {
public function sort(array $data): array {
$n = count($data);
for ($i = 0; $i < $n; $i++) {
for ($j = $i + 1; $j < $n; $j++) {
if ($data[$i] > $data[$j]) {
$temp = $data[$i];
$data[$i] = $data[$j];
$data[$j] = $temp;
}
}
}
return $data;
}
}
class QuickSort implements SortingStrategy {
public function sort(array $data): array {
if (count($data) <= 1) {
return $data;
}
$pivot = $data[0];
$left = $right = array();
for ($i = 1; $i < count($data); $i++) {
if ($data[$i] < $pivot) {
$left[] = $data[$i];
} else {
$right[] = $data[$i];
}
}
return array_merge($this->sort($left), array($pivot), $this->sort($right));
}
}
class Sorter {
private $strategy;
public function __construct(SortingStrategy $strategy) {
$this->strategy = $strategy;
}
public function setStrategy(SortingStrategy $strategy) {
$this->strategy = $strategy;
}
public function sort(array $data): array {
return $this->strategy->sort($data);
}
}
In this example, we define an interface SortingStrategy that has a sort() method for sorting an array of data. We then define two concrete classes BubbleSort and QuickSort that implement this interface.
We then define a Sorter class that takes an instance of a SortingStrategy object in its constructor. The Sorter class has a sort() method that calls the sort() method on the current strategy object. The setStrategy() method is used to change the sorting strategy dynamically.
We can use the Sorter class as follows:
$data = array(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
$sorter = new Sorter(new BubbleSort());
$sortedData = $sorter->sort($data);
echo implode(", ", $sortedData); // Output: 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9
$sorter->setStrategy(new QuickSort());
$sortedData = $sorter->sort($data);
echo implode(", ", $sortedData); // Output: 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9
In this example, we create an array $data containing some unsorted data. We then create a new Sorter object with a BubbleSort strategy and call its sort() method to sort the data. We then print the sorted data using implode() function.
We then change the strategy of the Sorter object to QuickSort using the setStrategy() method and call its sort() method again to sort the data using the new strategy. We then print the sorted data using implode() function.
By using the Strategy pattern, we can create flexible and extensible code that can adapt to changing requirements or conditions. We can also decouple the algorithm implementation from the client that uses it, making it easier to test and maintain.
The Decorator Software Design Pattern
The Decorator pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The Decorator pattern is useful when we need to add new features or functionality to an object at runtime, but don't want to modify the object's original code.
In the Decorator pattern, we define a base class or interface that represents the object we want to decorate. We then define concrete decorators that add new behavior to the base class or interface. Each decorator implements the same interface as the base class, and may add additional functionality or modify the behavior of the base class.
Example of implementing the Decorator pattern in PHP:
interface Pizza {
public function getDescription(): string;
public function getPrice(): float;
}
class Margherita implements Pizza {
public function getDescription(): string {
return "Margherita Pizza";
}
public function getPrice(): float {
return 5.99;
}
}
abstract class PizzaDecorator implements Pizza {
protected $pizza;
public function __construct(Pizza $pizza) {
$this->pizza = $pizza;
}
public function getDescription(): string {
return $this->pizza->getDescription();
}
public function getPrice(): float {
return $this->pizza->getPrice();
}
}
class Cheese extends PizzaDecorator {
public function getDescription(): string {
return $this->pizza->getDescription() . ", extra cheese";
}
public function getPrice(): float {
return $this->pizza->getPrice() + 1.00;
}
}
class Mushrooms extends PizzaDecorator {
public function getDescription(): string {
return $this->pizza->getDescription() . ", mushrooms";
}
public function getPrice(): float {
return $this->pizza->getPrice() + 0.50;
}
}
In this example, we define an interface Pizza that represents a pizza object, with methods getDescription() and getPrice(). We also define a concrete implementation of Pizza, Margherita.
We then define an abstract class PizzaDecorator that implements the Pizza interface, with a $pizza property that represents the pizza object we want to decorate. The PizzaDecorator class also implements the getDescription() and getPrice() methods, which simply call the corresponding methods on the $pizza object.
We then define two concrete decorators, Cheese and Mushrooms, that extend the PizzaDecorator class. Each decorator adds a new behavior to the base Pizza object by implementing its own versions of the getDescription() and getPrice() methods.
We can use the Pizza classes as follows:
$pizza = new Margherita();
echo $pizza->getDescription() . ": $" . $pizza->getPrice() . "\n"; // Output: Margherita Pizza: $5.99
$pizza = new Cheese(new Margherita());
echo $pizza->getDescription() . ": $" . $pizza->getPrice() . "\n"; // Output: Margherita Pizza, extra cheese: $6.99
$pizza = new Mushrooms(new Cheese(new Margherita()));
echo $pizza->getDescription() . ": $" . $pizza->getPrice() . "\n"; // Output: Margherita Pizza, extra cheese, mushrooms: $7.49
In this example, we create a new Margherita pizza object and call its getDescription() and getPrice() methods to get its description and price.
We then create a new pizza object, Cheese, by wrapping the Margherita object with a new Cheese decorator. We call the getDescription() and getPrice() methods on the Cheese object to get its description and price.
We then create a new pizza object, Mushrooms, by wrapping the Cheese object with a new Mushrooms decorator. We call the getDescription() and getPrice() methods on the Mushrooms object to get its description and price.
By using the Decorator pattern, we can add new functionality to an object at runtime without modifying its original code. We can also combine multiple decorators to create complex behavior, and easily remove decorators when we no longer need them.
The Adapter Software Design Pattern
The Adapter pattern is a structural design pattern that allows incompatible objects to work together. The Adapter pattern is useful when we need to use an existing class that does not have the interface we need, or when we want to create a reusable class that can work with multiple incompatible classes.
In the Adapter pattern, we define a new class, called an adapter, that translates the interface of one class into the interface expected by the client. The adapter has the same interface as the class we want to adapt, and it contains an instance of that class. The adapter then implements the interface expected by the client, and translates the calls to the interface of the adaptee.
Example of implementing the Adapter pattern in PHP:
interface MediaPlayer {
public function play(string $mediaType, string $fileName): void;
}
interface AdvancedMediaPlayer {
public function playVlc(string $fileName): void;
public function playMp4(string $fileName): void;
}
class VlcPlayer implements AdvancedMediaPlayer {
public function playVlc(string $fileName): void {
echo "Playing vlc file. Name: " . $fileName . "\n";
}
public function playMp4(string $fileName): void {
// do nothing
}
}
class Mp4Player implements AdvancedMediaPlayer {
public function playVlc(string $fileName): void {
// do nothing
}
public function playMp4(string $fileName): void {
echo "Playing mp4 file. Name: " . $fileName . "\n";
}
}
class MediaAdapter implements MediaPlayer {
private $advancedMediaPlayer;
public function __construct(string $audioType) {
if ($audioType == "vlc") {
$this->advancedMediaPlayer = new VlcPlayer();
} else if ($audioType == "mp4") {
$this->advancedMediaPlayer = new Mp4Player();
}
}
public function play(string $mediaType, string $fileName): void {
if ($mediaType == "vlc") {
$this->advancedMediaPlayer->playVlc($fileName);
} else if ($mediaType == "mp4") {
$this->advancedMediaPlayer->playMp4($fileName);
}
}
}
class AudioPlayer implements MediaPlayer {
private $mediaAdapter;
public function play(string $mediaType, string $fileName): void {
if ($mediaType == "mp3") {
echo "Playing mp3 file. Name: " . $fileName . "\n";
} else if ($mediaType == "vlc" || $mediaType == "mp4") {
$this->mediaAdapter = new MediaAdapter($mediaType);
$this->mediaAdapter->play($mediaType, $fileName);
} else {
echo "Invalid media type. Only mp3, vlc and mp4 are supported.\n";
}
}
}
In this example, we define two interfaces: MediaPlayer and AdvancedMediaPlayer. The MediaPlayer interface has a play() method, which is used to play media files of different types. The AdvancedMediaPlayer interface has two methods, playVlc() and playMp4(), which are used to play Vlc and Mp4 files respectively.
We then define two classes that implement the AdvancedMediaPlayer interface: VlcPlayer and Mp4Player. These classes provide implementations of the playVlc() and playMp4() methods.
We then define a class MediaAdapter that implements the MediaPlayer interface. This class wraps an instance of an AdvancedMediaPlayer object and provides an implementation of the play() method that translates the media type and file name to the corresponding AdvancedMediaPlayer method.
We then define a class AudioPlayer that implements the `MediaPlayer interface`. This class has a$mediaAdapterproperty that stores an instance of theMediaAdapterclass. Theplay()method of theAudioPlayerclass checks the media type of the file to be played, and calls the appropriate method of either theAudioPlayerorMediaAdapter` object.
We can use the MediaPlayer classes as follows:
$audioPlayer = new AudioPlayer();
$audioPlayer->play("mp3", "song.mp3"); // Output: Playing mp3 file. Name: song.mp3
$audioPlayer->play("vlc", "movie.vlc"); // Output: Playing vlc file. Name: movie.vlc
$audioPlayer->play("mp4", "video.mp4"); // Output: Playing mp4 file. Name: video.mp4
$audioPlayer->play("avi", "movie.avi"); // Output: Invalid media type. Only mp3, vlc and mp4 are supported.
In this example, we create a new AudioPlayer object and call its play() method with different media types and file names. When we call play() with an mp3 file, the AudioPlayer object directly plays the file. When we call play() with a vlc or mp4 file, the AudioPlayer object creates a new MediaAdapter object and calls its play() method to play the file.
By using the Adapter pattern, we can integrate new or existing code with other code that has a different interface, without modifying the original code. We can also create adapters for different classes or interfaces, making it easier to reuse existing code in new contexts.
The Template Method Software Design Pattern
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass, but allows subclasses to override specific steps of the algorithm without changing its structure. The Template Method pattern is useful when we have a series of steps that need to be executed in a specific order, but allow some of the steps to be customized by subclasses.
In the Template Method pattern, we define a base class that contains a series of steps to be executed in a specific order. Some of the steps may be implemented in the base class, while others are left as abstract methods to be implemented by subclasses. The base class also contains a template method that defines the overall algorithm and calls the steps in the correct order.
Example of implementing the Template Method pattern in PHP:
abstract class Game {
protected abstract function initialize(): void;
protected abstract function startPlay(): void;
protected abstract function endPlay(): void;
public final function play(): void {
$this->initialize();
$this->startPlay();
$this->endPlay();
}
}
class Cricket extends Game {
protected function initialize(): void {
echo "Cricket Game Initialized! Start playing.\n";
}
protected function startPlay(): void {
echo "Cricket Game Started. Enjoy the game!\n";
}
protected function endPlay(): void {
echo "Cricket Game Finished!\n";
}
}
class Football extends Game {
protected function initialize(): void {
echo "Football Game Initialized! Start playing.\n";
}
protected function startPlay(): void {
echo "Football Game Started. Enjoy the game!\n";
}
protected function endPlay(): void {
echo "Football Game Finished!\n";
}
}
In this example, we define an abstract class Game that represents a game object. The Game class contains three abstract methods: initialize(), startPlay(), and endPlay(). These methods represent the steps that need to be executed in a specific order when playing a game. The Game class also contains a final play() method that calls these steps in the correct order.
We then define two concrete subclasses of the Game class: Cricket and Football. Each subclass implements the abstract methods to define the specific behavior of the game. The Cricket class implements the initialize(), startPlay(), and endPlay() methods for a game of cricket, while the Football class implements these methods for a game of football.
We can use the Game classes as follows:
$game = new Cricket();
$game->play();
/*
Output:
Cricket Game Initialized! Start playing.
Cricket Game Started. Enjoy the game!
Cricket Game Finished!
*/
$game = new Football();
$game->play();
/*
Output:
Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!
*/
In this example, we create a new Cricket object and call its play() method. The play() method calls the initialize(), startPlay(), and endPlay() methods in the correct order to play a game of cricket. We then create a new Football object and call its play() method, which calls the initialize(), startPlay(), and endPlay() methods in the correct order to play a game of football.
By using the Template Method pattern, we can define a series of steps to be executed in a specific order, but allow some of the steps to be customized by subclasses. We can also prevent subclasses from changing the overall structure of the algorithm, ensuring that the steps are always executed in the correct order. This makes it easier to reuse and maintain code, since the overall structure of the algorithm is defined in the base class and can be reused by multiple subclasses.
The Facade Software Design Pattern
The Facade pattern is a structural design pattern that provides a simplified interface to a complex system of classes, interfaces, and subsystems. The Facade pattern is useful when we want to provide a simple interface to a complex system, and hide its implementation details from clients.
In the Facade pattern, we define a new class or interface that provides a simplified interface to the underlying system. This new class or interface then wraps and interacts with the subsystems of the underlying system to perform the required functionality.
Example of implementing the Facade pattern in PHP:
class ShapeMaker {
private $circle;
private $rectangle;
private $square;
public function __construct() {
$this->circle = new Circle();
$this->rectangle = new Rectangle();
$this->square = new Square();
}
public function drawCircle(): void {
$this->circle->draw();
}
public function drawRectangle(): void {
$this->rectangle->draw();
}
public function drawSquare(): void {
$this->square->draw();
}
}
interface Shape {
public function draw(): void;
}
class Circle implements Shape {
public function draw(): void {
echo "Circle::draw()\n";
}
}
class Rectangle implements Shape {
public function draw(): void {
echo "Rectangle::draw()\n";
}
}
class Square implements Shape {
public function draw(): void {
echo "Square::draw()\n";
}
}
In this example, we define an interface Shape that represents a shape object, with a method draw() that is used to draw the shape. We also define three concrete implementations of Shape: Circle, Rectangle, and Square.
We then define a new class ShapeMaker that provides a simplified interface to these shape objects. The ShapeMaker class wraps instances of the Circle, Rectangle, and Square classes and provides three methods, drawCircle(), drawRectangle(), and drawSquare(), that call the corresponding draw() methods on the wrapped objects.
We can use the ShapeMaker class as follows:
$shapeMaker = new ShapeMaker();
$shapeMaker->drawCircle(); // Output: Circle::draw()
$shapeMaker->drawRectangle(); // Output: Rectangle::draw()
$shapeMaker->drawSquare(); // Output: Square::draw()
In this example, we create a new ShapeMaker object and call its drawCircle(), drawRectangle(), and drawSquare() methods to draw the corresponding shapes. The ShapeMaker object wraps instances of the Circle, Rectangle, and Square classes and provides a simplified interface to draw these shapes.
By using the Facade pattern, we can provide a simple interface to a complex system, and hide its implementation details from clients. This makes it easier to use and maintain the system, since clients only need to interact with the Facade object and don't need to know about the underlying subsystems.
The Iterator Software Design Pattern
The Iterator pattern is a behavioral design pattern that provides a way to sequentially access the elements of an aggregate object, without exposing its underlying representation. The Iterator pattern is useful when we want to traverse the elements of a collection in a specific order, without exposing the collection's internal representation or implementation.
In the Iterator pattern, we define two interfaces: Iterator and Aggregate. The Iterator interface defines methods for sequentially accessing the elements of an aggregate object, while the Aggregate interface defines a method for creating an iterator object.
Example of implementing the Iterator pattern in PHP:
interface Iterator {
public function hasNext(): bool;
public function next(): object;
}
interface Aggregate {
public function createIterator(): Iterator;
}
class NameRepository implements Aggregate {
private $names = array("Robert", "John", "Julie", "Lora");
public function createIterator(): Iterator {
return new NameIterator($this->names);
}
}
class NameIterator implements Iterator {
private $names;
private $index = 0;
public function __construct(array $names) {
$this->names = $names;
}
public function hasNext(): bool {
if ($this->index < count($this->names)) {
return true;
} else {
return false;
}
}
public function next(): object {
if ($this->hasNext()) {
return $this->names[$this->index++];
} else {
return null;
}
}
}
In this example, we define an interface Iterator that represents an iterator object, with two methods: hasNext() and next(). The hasNext() method is used to check whether there are more elements in the collection, while the next() method is used to return the next element in the collection.
We also define an interface Aggregate that represents an aggregate object, with a method createIterator() that is used to create a new iterator object.
We then define a concrete class NameRepository that implements the Aggregate interface. The NameRepository class has an array of names and a createIterator() method that returns a new NameIterator object.
We also define a concrete class NameIterator that implements the Iterator interface. The NameIterator class has an array of names and an index that keeps track of the current position in the array. The hasNext() method checks whether there are more elements in the array, while the next() method returns the next element in the array and updates the index.
We can use the NameRepository and NameIterator classes as follows:
$nameRepository = new NameRepository();
$iterator = $nameRepository->createIterator();
while ($iterator->hasNext()) {
echo $iterator->next() . "\n";
}
In this example, we create a new NameRepository object and call its createIterator() method to create a new NameIterator object. We then use a while loop to iterate over the elements in the NameIterator object and print each name to the console.
By using the Iterator pattern, we can traverse the elements of a collection in a specific order, without exposing the collection's internal representation or implementation. This makes it easier to use and maintain collections, since clients only need to interact with the Iterator object and don't need to know about the underlying collection.
The Proxy Software Design Pattern
The Proxy pattern is a structural design pattern that provides a surrogate or placeholder object for another object to control access to it. The Proxy pattern is useful when we want to add additional functionality to an existing object, such as caching or security checks, without modifying the original object.
In the Proxy pattern, we define a new class or interface that has the same interface as the original object. This new class or interface then wraps the original object and controls access to it by intercepting requests and performing additional operations before or after forwarding them to the original object.
Here is an example of implementing the Proxy pattern in PHP:
interface Image {
public function display(): void;
}
class RealImage implements Image {
private $filename;
public function __construct(string $filename) {
$this->filename = $filename;
$this->loadFromDisk();
}
public function display(): void {
echo "Displaying image " . $this->filename . "\n";
}
private function loadFromDisk(): void {
echo "Loading " . $this->filename . " from disk.\n";
}
}
class ProxyImage implements Image {
private $filename;
private $realImage;
public function __construct(string $filename) {
$this->filename = $filename;
}
public function display(): void {
if ($this->realImage == null) {
$this->realImage = new RealImage($this->filename);
}
$this->realImage->display();
}
}
In this example, we define an interface Image that represents an image object, with a method display() that is used to display the image. We also define a concrete class RealImage that implements the Image interface. The RealImage class represents a real image object that is loaded from disk when it is created.
We then define a new class ProxyImage that also implements the Image interface. The ProxyImage class wraps a RealImage object and controls access to it by intercepting calls to its display() method. If a RealImage object has not yet been created, the ProxyImage object creates one and then calls its display() method.
We can use the RealImage and ProxyImage classes as follows:
$image1 = new ProxyImage("image1.png");
$image2 = new ProxyImage("image2.png");
// The following lines will only create RealImage objects if necessary
$image1->display(); // Output: Loading image1.png from disk. Displaying image image1.png
$image1->display(); // Output: Displaying image image1.png
$image2->display(); // Output: Loading image2.png from disk. Displaying image image2.png
$image2->display(); // Output: Displaying image image2.png
In this example, we create two ProxyImage objects and call their display() methods. The first call to display() for each object creates a RealImage object and loads the image from disk. The subsequent calls to display() for each object use the existing RealImage object and display the image.
By using the Proxy pattern, we can add additional functionality to an existing object, such as caching or security checks, without modifying the original object. We can also control access to the original object by intercepting requests and performing additional operations before or after forwarding them to the original object.
The Command Software Design Pattern
The Command pattern is a behavioral design pattern that encapsulates a request as an object, thereby allowing it to be parameterized with different requests, queued, or logged with undo/redo capabilities. The Command pattern is useful when we want to decouple the requester of an action from the object that performs the action, and provide a way to log, undo, or redo actions.
In the Command pattern, we define a new class or interface that encapsulates a request as an object. This new class or interface then defines a method or methods that the client can call to execute the request.
Example of implementing the Command pattern in PHP:
interface Command {
public function execute(): void;
}
class LightOnCommand implements Command {
private $light;
public function __construct(Light $light) {
$this->light = $light;
}
public function execute(): void {
$this->light->turnOn();
}
}
class LightOffCommand implements Command {
private $light;
public function __construct(Light $light) {
$this->light = $light;
}
public function execute(): void {
$this->light->turnOff();
}
}
class Light {
public function turnOn(): void {
echo "Light is turned on\n";
}
public function turnOff(): void {
echo "Light is turned off\n";
}
}
class RemoteControl {
private $onCommands = array();
private $offCommands = array();
public function setCommand(int $slot, Command $onCommand, Command $offCommand): void {
$this->onCommands[$slot] = $onCommand;
$this->offCommands[$slot] = $offCommand;
}
public function pressOnButton(int $slot): void {
$this->onCommands[$slot]->execute();
}
public function pressOffButton(int $slot): void {
$this->offCommands[$slot]->execute();
}
}
In this example, we define an interface Command that represents a command object, with a method execute() that is used to execute the command. We also define two concrete classes LightOnCommand and LightOffCommand that implement the Command interface. These classes represent commands to turn a light on or off.
We then define a class Light that represents a light object, with methods to turn the light on and off.
We also define a class RemoteControl that represents a remote control object. The RemoteControl class has two arrays, $onCommands and $offCommands, that are used to store the Command objects for turning a light on and off. The RemoteControl class has methods setCommand(), pressOnButton(), and pressOffButton() that are used to set the commands for each button and execute the corresponding command when a button is pressed.
We can use the RemoteControl class as follows:
$remoteControl = new RemoteControl();
$light = new Light();
$lightOnCommand = new LightOnCommand($light);
$lightOffCommand = new LightOffCommand($light);
$remoteControl->setCommand(0, $lightOnCommand, $lightOffCommand);
$remoteControl->pressOnButton(0); // Output: Light is turned on
$remoteControl->pressOffButton(0); // Output: Light is turned off
In this example, we create a new RemoteControl object and a new Light object. We then create LightOnCommand and LightOffCommand objects that are used to turn the light on and off. We set the commands for button 0 on the remote control using the setCommand() method. Finally, we press button 0 twice to turn the light on and off.
By using the Command pattern, we can decouple the requester of an action from the object that performs the action, and provide a way to log, undo, or redo actions. We can also parameterize commands with different requests, queue commands, or log commands with undo/redo capabilities. This makes it easier to design flexible and extensible systems that can be easily modified or extended.
The Builder Software Design Pattern
The Builder pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing the same construction process to create different representations. The Builder pattern is useful when we want to create complex objects that have multiple parts, and when we want to vary the way the object is constructed without changing its underlying representation.
In the Builder pattern, we define a new class or interface that represents a builder object. This builder object has methods that are used to construct the complex object, such as adding parts or setting properties. We also define a director object that is responsible for invoking the builder object's methods in a specific order to construct the complex object.
Example of implementing the Builder pattern in PHP:
class Car {
private $engine;
private $wheels;
private $seats;
public function setEngine(string $engine): void {
$this->engine = $engine;
}
public function setWheels(int $wheels): void {
$this->wheels = $wheels;
}
public function setSeats(int $seats): void {
$this->seats = $seats;
}
public function getInfo(): string {
return "This car has a " . $this->engine . " engine, " . $this->wheels . " wheels, and " . $this->seats . " seats.\n";
}
}
interface CarBuilder {
public function setEngine(): void;
public function setWheels(): void;
public function setSeats(): void;
public function getResult(): Car;
}
class CheapCarBuilder implements CarBuilder {
private $car;
public function __construct() {
$this->car = new Car();
}
public function setEngine(): void {
$this->car->setEngine("cheap");
}
public function setWheels(): void {
$this->car->setWheels(4);
}
public function setSeats(): void {
$this->car->setSeats(4);
}
public function getResult(): Car {
return $this->car;
}
}
class ExpensiveCarBuilder implements CarBuilder {
private $car;
public function __construct() {
$this->car = new Car();
}
public function setEngine(): void {
$this->car->setEngine("expensive");
}
public function setWheels(): void {
$this->car->setWheels(4);
}
public function setSeats(): void {
$this->car->setSeats(2);
}
public function getResult(): Car {
return $this->car;
}
}
class CarDirector {
private $builder;
public function setBuilder(CarBuilder $builder): void {
$this->builder = $builder;
}
public function buildCar(): void {
$this->builder->setEngine();
$this->builder->setWheels();
$this->builder->setSeats();
}
public function getCar(): Car {
return $this->builder->getResult();
}
}
In this example, we define a class Car that represents a car object, with methods to set the engine, wheels, and seats, and a method getInfo() that returns a string describing the car's characteristics.
We also define an interface CarBuilder that represents a builder object, with methods to set the engine, wheels, and seats, and a method getResult() that returns the constructed Car object.
We then define two concrete classes CheapCarBuilder and ExpensiveCarBuilderthat implement theCarBuilder` interface. These classes represent different ways of constructing a car object, with different engines, numbers of wheels, and numbers of seats.
We also define a class CarDirector that represents a director object, with methods to set the builder object, invoke the builder object's methods in a specific order to construct the car object, and return the constructed car object.
We can use the CarDirector, CheapCarBuilder, and ExpensiveCarBuilder classes as follows:
$director = new CarDirector();
$cheapCarBuilder = new CheapCarBuilder();
$expensiveCarBuilder = new ExpensiveCarBuilder();
$director->setBuilder($cheapCarBuilder);
$director->buildCar();
$cheapCar = $director->getCar();
echo $cheapCar->getInfo(); // Output: This car has a cheap engine, 4 wheels, and 4 seats.
$director->setBuilder($expensiveCarBuilder);
$director->buildCar();
$expensiveCar = $director->getCar();
echo $expensiveCar->getInfo(); // Output: This car has an expensive engine, 4 wheels, and 2 seats.
In this example, we create a new CarDirector object and two new CarBuilder objects, CheapCarBuilder and ExpensiveCarBuilder. We then set the CarDirector object's builder to the CheapCarBuilder object and use it to construct a Car object. We then set the CarDirector object's builder to the ExpensiveCarBuilder object and use it to construct a different Car object. Finally, we print out information about the two cars using their getInfo() method.
By using the Builder pattern, we can separate the construction of a complex object from its representation, allowing the same construction process to create different representations. We can also vary the way the object is constructed without changing its underlying representation. This makes it easier to design flexible and extensible systems that can be easily modified or extended.
Can I mix software design patterns?
Yes, you can mix software design patterns in your software design. In fact, it's common for software systems to use multiple design patterns together to achieve specific goals.
For example, you might use the Factory pattern to create objects and the Singleton pattern to ensure that only one instance of a specific object is created. Or, you might use the Adapter pattern to convert the interface of a legacy system to a modern interface and the Decorator pattern to add new functionality to the legacy system without changing its core functionality.
When using multiple design patterns together, it's important to ensure that they work well together and don't create unnecessary complexity. It's also important to understand the strengths and weaknesses of each pattern and how they contribute to the overall design of the system.
In general, it's a good idea to use design patterns as building blocks to construct well-designed and maintainable software systems. By combining different patterns together, you can create more flexible and extensible systems that are easier to modify or extend over time.
How to pick the right software design pattern for your project
Choosing the right software design pattern for your project depends on several factors, including the requirements of your project, the architecture of your system, and the constraints and limitations you face.
Here are some steps you can follow to help you pick the right software design pattern for your project:
- Understand the problem
Before choosing a design pattern, it's important to have a clear understanding of the problem you're trying to solve. What are the requirements of the system? What are the constraints and limitations? What are the goals and objectives of the system? - Identify the design patterns
Once you understand the problem, you can start to identify the design patterns that might be useful for your project. There are many design patterns to choose from, so it's important to have a good understanding of the strengths and weaknesses of each pattern. - Evaluate the design patterns
After identifying the design patterns, you should evaluate each pattern to determine which one is the best fit for your project. Consider factors such as the complexity of the pattern, the ease of implementation, and the impact on the overall architecture of the system. - Consider the trade-offs
Every design pattern has trade-offs, so it's important to consider the pros and cons of each pattern before making a decision. For example, some patterns might provide more flexibility but also increase complexity, while others might be simpler but less flexible. - Choose the right pattern
Based on your evaluation and consideration of the trade-offs, choose the design pattern that is the best fit for your project. Keep in mind that you might need to combine multiple patterns to achieve your goals, and that it's important to use patterns in a way that supports the overall architecture of your system.
In general, the key to picking the right software design pattern for your project is to have a good understanding of the problem you're trying to solve and the strengths and weaknesses of each pattern. By carefully evaluating the available patterns and considering the trade-offs, you can choose the pattern that will help you achieve your goals while maintaining the maintainability and extensibility of your system.