Interface Segregation Principle
No client should be forced to depend on methods it does not use.
Problem
Orderable bundles calculate(), shipping(), discount(), delivery(), and process() into ONE interface. A digital-goods order that has no physical shipping or delivery would still be forced to implement those methods — probably as empty stubs — just to satisfy the interface.
Solution
Split Orderable into five focused interfaces: Calculable, Shippable, Discountable, Deliverable, Processable. A physical order implements all five. A digital order implements only the three it actually needs.
Analogy
A universal remote control with 40 buttons, but your TV only uses 6 of them — you're still forced to hold (and maintain) the whole remote. Smaller, single-purpose remotes let each device depend on only what it needs.
Participants (After)
| Interface / Class | Role |
|---|---|
| Calculable / Shippable / Discountable / Deliverable / Processable | Five small, single-purpose interfaces |
| OrderManager | Physical order — implements all five |
| DigitalOrderManager | Digital order — implements only Calculable, Discountable, Processable |
Run the Demo
Activity Log
Source Code
▸ Before: Orderable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\Before;
/**
* ISP VIOLATION — one bloated interface forcing every implementer to
* support calculation, shipping, discounts, AND delivery — even a
* class that only needs one or two of these.
*/
interface Orderable
{
public function calculate();
public function shipping(int $shipping);
public function discount($discount);
public function delivery($company);
public function process();
}
▸ Before: OrderManager.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\Before;
/**
* ISP VIOLATION — OrderManager is forced to implement all five methods
* from Orderable, even in a scenario where (say) a digital-goods order
* has no physical shipping or delivery at all. It would still have to
* implement shipping()/delivery() with empty or nonsensical bodies
* just to satisfy the interface.
*/
class OrderManager implements Orderable
{
protected float $total = 0;
protected array $items;
protected string $deliveryMessage = '';
public function __construct(array $items = [])
{
$this->items = $items;
}
public function calculate()
{
$this->total = array_sum(array_column($this->items, 'price'));
return $this;
}
public function shipping(int $shipping)
{
$this->total += $shipping;
return $this;
}
public function discount($discount = 0.02)
{
$this->total -= $this->total * $discount;
return $this;
}
public function delivery($company)
{
$this->deliveryMessage = 'Delivery will be made by ' . $company;
return $this;
}
public function process()
{
return (object) [
'delivery' => $this->deliveryMessage,
'paid' => $this->total,
];
}
}
▸ After: Calculable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/** ISP FIX — a client that only totals items needs only this. */
interface Calculable
{
public function calculate();
}
▸ After: Shippable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/** ISP FIX — a client that adds shipping cost needs only this. */
interface Shippable
{
public function shipping(int $shipping);
}
▸ After: Discountable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/** ISP FIX — a client that applies a discount needs only this. */
interface Discountable
{
public function discount(float $discount);
}
▸ After: Deliverable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/** ISP FIX — only a physical-goods order needs delivery tracking. */
interface Deliverable
{
public function delivery(string $company);
}
▸ After: Processable.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/** ISP FIX — every order type still needs to finalise/process. */
interface Processable
{
public function process();
}
▸ After: OrderManager.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/**
* ISP FIX — a PHYSICAL order genuinely needs all five behaviours, so it
* implements every focused interface. This is fine — ISP doesn't ban
* implementing many interfaces, it bans forcing implementers who DON'T
* need a behaviour to implement it anyway.
*/
class OrderManager implements Calculable, Shippable, Discountable, Deliverable, Processable
{
protected float $total = 0;
protected array $items;
protected string $deliveryMessage = '';
public function __construct(array $items = [])
{
$this->items = $items;
}
public function calculate()
{
$this->total = array_sum(array_column($this->items, 'price'));
return $this;
}
public function shipping(int $shipping)
{
$this->total += $shipping;
return $this;
}
public function discount(float $discount = 0.02)
{
$this->total -= $this->total * $discount;
return $this;
}
public function delivery(string $company)
{
$this->deliveryMessage = 'Delivery will be made by ' . $company;
return $this;
}
public function process()
{
return (object) [
'delivery' => $this->deliveryMessage,
'paid' => $this->total,
];
}
}
▸ After: DigitalOrderManager.php
<?php
declare(strict_types=1);
namespace App\SolidPrinciples\Isp\After;
/**
* ISP FIX PROOF — a DIGITAL order (e.g. an e-book or software licence)
* has no shipping and no delivery. It implements ONLY the interfaces
* it actually needs: Calculable, Discountable, Processable. It is never
* forced to write empty/fake shipping() or delivery() methods.
*/
class DigitalOrderManager implements Calculable, Discountable, Processable
{
protected float $total = 0;
protected array $items;
public function __construct(array $items = [])
{
$this->items = $items;
}
public function calculate()
{
$this->total = array_sum(array_column($this->items, 'price'));
return $this;
}
public function discount(float $discount = 0.10)
{
$this->total -= $this->total * $discount;
return $this;
}
public function process()
{
return (object) [
'delivery' => 'N/A — digital delivery via download link',
'paid' => $this->total,
];
}
}