「共通処理をまとめたい → 基底クラス作ろう」という思考、あるある。でもそれ、だいたい2年後に技術的負債になる。私も何度もやりかけては痛い目を見てきた。
継承は「is-a」関係。動物クラスを継承した犬クラス、猫クラス。一見きれい。でも犬は吠えるけど猫は吠えない——さてどこに bark() を置く?こういう小さな歪みが積み重なって、やがて継承ツリー全体が崩壊する。
Compositionの考え方
「has-a」関係。振る舞いを小さなクラスに切り出して、必要に応じて組み合わせる。
class Logger {
public function log(string $msg): void {
echo "[" . date("Y-m-d H:i:s") . "] $msg\n";
}
}
class UserService {
public function __construct(
private Logger $logger,
private Mailer $mailer
) {}
public function register(User $user): void {
$this->logger->log("Registering: {$user->email}");
$this->mailer->sendWelcome($user);
}
}
Traitは銀の弾丸か
PHPのTraitは一見compositionに見えるけど、実態はコンパイル時にコピペされるマクロ。多重Traitの衝突、可視性の問題、IDE追従の困難さ。乱用すると継承以上にカオスになる。
まとめ
「なんでも継承」から「小さなクラスを組み合わせる」への発想の転換が、長く生き残るコードを書く第一歩。PHP8.1のreadonlyプロパティやenumも、composition指向の設計と相性がいい。