Summary - Design patterns
Build a cart system with payment method selection using Singleton, Factory and Strategy.
Summary - Cart system
In this summary we combine four patterns in a cart system. Singleton guarantees a single cart instance; Factory creates the payment method; Strategy decides how the payment runs; Builder assembles the items in the cart with fluent methods.
Each pattern carries its own responsibility, achieving separation of concerns. First you will build the cart with Builder, then process the payment with Singleton + Factory + Strategy.
The Builder pattern: fluent setter
The Builder pattern constructs a complex object step by step. Each builder method (for example addItem, setPayment) sets one part of the object and returns $this, so calls chain together.
The final build method returns the finished object. This keeps classes with many constructor parameters readable: add items, set payment, then call build for the result.
Exercise Build the cart fluently with Builder
Write a Builder class. The addItem(string $name, int $price) and setPayment(string $payment) methods return $this. The build method sums the item prices and returns ['total' => $total]. Build the cart with addItem('bread', 10), addItem('milk', 20), setPayment('card') and print the total value.
Show example solution
Write class Builder; addItem and setPayment return self (return $this;). build sums item prices and returns an array. Build with (new Builder())->addItem('bread', 10)->addItem('milk', 20)->setPayment('card')->build().
Example solution:
<?php
class Builder {
private array $items = [];
private string $payment = '';
public function addItem(string $name, int $price): self {
$this->items[] = ['name' => $name, 'price' => $price];
return $this;
}
public function setPayment(string $payment): self {
$this->payment = $payment;
return $this;
}
public function build(): array {
$total = 0;
foreach ($this->items as $i) { $total += $i['price']; }
return ['items' => $this->items, 'payment' => $this->payment, 'total' => $total];
}
}
$cart = (new Builder())
->addItem('bread', 10)
->addItem('milk', 20)
->setPayment('card')
->build();
echo 'total: ' . $cart['total'];
Exercise Payment with Singleton + Factory + Strategy
Write an Algorithm interface (with a pay(int $amount): string method). CardStrategy returns 'paid: ' . $amount. Write a Factory class whose createProduct('card') returns a CardStrategy instance. Write a Singleton class whose getInstance returns the single instance and whose checkout(int $amount, Algorithm $algo) method returns $algo->pay($amount). Get the cart, create the card strategy, pay with checkout(30, $pay) and print the result.
Show example solution
string; }. CardStrategy pay returns 'paid: ' . $amount. Factory::createProduct('card') returns CardStrategy. Singleton::getInstance() returns the single instance; checkout(30, $pay) returns $algo->pay($amount).
Example solution:
<?php
interface Algorithm {
public function pay(int $amount): string;
}
class CardStrategy implements Algorithm {
public function pay(int $amount): string { return 'paid: ' . $amount; }
}
class Factory {
public static function createProduct(string $type): Algorithm {
return match ($type) {
'card' => new CardStrategy(),
default => throw new \InvalidArgumentException('unknown'),
};
}
}
class Singleton {
private static ?Singleton $instance = null;
private function __construct() {}
public static function getInstance(): Singleton {
return self::$instance ??= new Singleton();
}
public function checkout(int $amount, Algorithm $algo): string {
return $algo->pay($amount);
}
}
$cart = Singleton::getInstance();
$pay = Factory::createProduct('card');
echo $cart->checkout(30, $pay);
Test yourself
-
Which pattern guarantees a single instance in the cart?
- Strategy
- Factory
- Builder
- Singleton
Show answer
Singleton — Correct. Singleton guarantees a single instance; it fits the cart.
-
Match the cart patterns
- Singleton
- single cart
- Factory
- creates payment method
- Strategy
- payment algorithm
- Builder
- fluent assembler