Composition

Composition Over Inheritance in Practice

Subclassing Order per shipping policy (FlatRateOrder, FreeOverFiftyOrder...) multiplies classes with every rule. Composition hands Order a rule object behind a small interface (Subsection 4.10.1), and a decorator such as FreeOver wraps one rule in another at run time.

Shipping rules composed into an order instead of inheritedPHP
<?php
interface ShippingRule { public function cost(int $subtotal): int; }
final class FlatRate implements ShippingRule {
  public function __construct(private int $cents) {}
  public function cost(int $subtotal): int { return $this->cents; }
}
final class FreeOver implements ShippingRule {            // decorator: wraps another rule
  public function __construct(private int $min, private ShippingRule $next) {}
  public function cost(int $s): int { return $s >= $this->min ? 0 : $this->next->cost($s); }
}
final class Order {                                        // has-a rule, not is-a subclass
  public function __construct(private ShippingRule $rule, private array $lines = []) {}
  public function total(): int {
    $s = array_sum($this->lines);
    return $s + $this->rule->cost($s);
  }
}
$rule = new FreeOver(5000, new FlatRate(499));
echo new Order($rule, [1999])->total(), ' ', new Order($rule, [1999, 3500])->total(), "\n";
Output
2498 5499

Each rule is testable alone, and Order never changes when one is added. Inherit when the child truly is a specialized parent; compose when combining behaviors (traits: Subsection 4.10.5).