I — SOLID Design Principle

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 / ProcessableFive small, single-purpose interfaces
OrderManagerPhysical order — implements all five
DigitalOrderManagerDigital order — implements only Calculable, Discountable, Processable

Run the Demo

Activity Log

Press a button above to see the pattern in action.

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,
        ];
    }
}