SOLID原則によるコーディング

「SOLID原則に沿ってプログラミングをする」とは、オブジェクト指向設計の基本原則を守って、保守性・拡張性・再利用性の高いコードを書く ということです。

SOLID は 5つの設計原則の頭文字を並べたものです。それぞれ詳しく見ていきましょう。


🔹 SOLID原則とは

1. SRP(Single Responsibility Principle:単一責任の原則)

  • 意味:クラスやモジュールは「1つの責任」だけを持つべき。
  • 狙い:変更理由が複数になるのを防ぐ。
  • 例(悪い例)class User { public function saveToDatabase() { ... } public function sendWelcomeMail() { ... } } → このクラスは「データ保存」と「メール送信」という2つの責任を持ってしまっている。
  • 改善例class UserRepository { public function save(User $user) { ... } } class MailService { public function sendWelcomeMail(User $user) { ... } } → 責任を分けることで変更に強い設計になる。

2. OCP(Open/Closed Principle:開放/閉鎖の原則)

  • 意味:機能の追加には「拡張」によって対応できるようにし、「既存のコード修正」はなるべく不要にする。
  • 狙い:既存コードを壊さずに新しい要件を組み込める。
  • 例(悪い例)class Payment { public function pay($type) { if ($type === 'credit') { ... } else if ($type === 'paypal') { ... } } } → 新しい決済手段を追加するたびに pay の中を修正しなければならない。
  • 改善例(OCPを満たす)interface PaymentMethod { public function pay(); } class CreditCardPayment implements PaymentMethod { public function pay() { ... } } class PaypalPayment implements PaymentMethod { public function pay() { ... } } class PaymentProcessor { public function process(PaymentMethod $method) { $method->pay(); } } → 新しい決済方法を追加する場合はクラスを新規追加すればよく、既存の PaymentProcessor は修正不要。

3. LSP(Liskov Substitution Principle:リスコフの置換原則)

  • 意味:親クラスと子クラスは置き換え可能であるべき。サブクラスはスーパークラスの契約を破ってはいけない。
  • 狙い:継承関係を「is-a」の関係で正しく保つ。
  • 例(悪い例)class Bird { public function fly() { ... } } class Penguin extends Bird { public function fly() { throw new Exception("ペンギンは飛べません!"); } }PenguinBird の代わりに使えないので LSP違反。
  • 改善例interface Bird { } interface FlyableBird extends Bird { public function fly(); } class Sparrow implements FlyableBird { public function fly() { ... } } class Penguin implements Bird { // flyを持たない } → 「飛べる鳥」と「飛べない鳥」を分けることで置換可能性を保つ。

4. ISP(Interface Segregation Principle:インターフェース分離の原則)

  • 意味:1つの大きなインターフェースではなく、利用者ごとに特化した小さいインターフェースを用意する。
  • 狙い:不要な依存を避ける。
  • 例(悪い例)interface Worker { public function work(); public function eat(); } class Robot implements Worker { public function work() { ... } public function eat() { throw new Exception("食べません"); } } → ロボットに eat() を強制するのはおかしい。
  • 改善例interface Workable { public function work(); } interface Eatable { public function eat(); } class Human implements Workable, Eatable { public function work() { ... } public function eat() { ... } } class Robot implements Workable { public function work() { ... } } → 必要なインターフェースだけ実装する。

5. DIP(Dependency Inversion Principle:依存関係逆転の原則)

  • 意味:高水準モジュール(ビジネスロジック)は低水準モジュール(DB操作やメール送信など)に依存すべきでなく、抽象に依存すべき。
  • 狙い:依存方向を逆転させることでテストや変更に強くする。
  • 例(悪い例)class UserService { private $db; public function __construct(MySQLDatabase $db) { $this->db = $db; } }MySQLDatabase に直接依存しているので、DBをPostgreSQLに変えたいときに修正が大きい。
  • 改善例interface Database { public function save($data); } class MySQLDatabase implements Database { public function save($data) { ... } } class PostgreSQLDatabase implements Database { public function save($data) { ... } } class UserService { private $db; public function __construct(Database $db) { $this->db = $db; } }UserService は具体実装ではなく「Databaseインターフェース」に依存。
    → DIコンテナやサービスプロバイダで依存を注入すれば切り替えが容易。

🔹 まとめ

  • SRP:クラスは「1つの責任」だけ持つ
  • OCP:拡張に開かれ、修正に閉じる
  • LSP:親と子は置換可能
  • ISP:使わない機能を強制する大きなインターフェースは避ける
  • DIP:具象に依存せず抽象に依存する

つまり SOLID原則に沿ってプログラミングをする とは、
「将来の仕様変更や機能追加に柔軟に対応できるように、依存や責任を適切に整理して設計・実装する」ことです。


👉 実務的には「テストしやすい」「修正しやすい」「新しい機能を追加しやすい」コードを目指すことになります。