ソリッド原則とは
SOLID は、オブジェクト指向設計の 5 原則の頭文字です。保守性・拡張性・テスタビリティを高め、変更に強い設計を目指します。
- Single Responsibility Principle(単一責任)
- Open/Closed Principle(開放/閉鎖)
- Liskov Substitution Principle(リスコフの置換)
- Interface Segregation Principle(インターフェース分離)
- Dependency Inversion Principle(依存関係逆転)
別名: SOLID Principles / 出典に Robert C. Martin(Uncle Bob)らの提唱があります。
概要
ねらい
- 変更容易性(リグレッションを減らす)
- 再利用性(差し替え・拡張がしやすい)
- 可読性(責務が明確)
対象
- 業務アプリのドメイン/ユースケース層
- ライブラリ/SDK/プラグイン設計
- APIクライアント・ストラテジ実装
注意
- 過剰設計のリスク(YAGNI/KISS とバランス)
- 小粒化しすぎによる複雑化
- チームの理解・命名規約との整合
基本
| 原則 | 一言で | 要点 |
|---|---|---|
| SRP | クラスは一つの理由でのみ変更される | 関心の分離・凝集度↑ 結合度↓ |
| OCP | 拡張に開き、変更に閉じる | 抽象による拡張ポイント/差し替え |
| LSP | 派生は基底を置換可能 | 契約(事前/事後条件・不変条件) |
| ISP | クライアント特化の小さなインターフェース | "太い"IFを分割、無駄な実装を排除 |
| DIP | 詳細ではなく抽象に依存 | 上位ポリシーが下位詳細に依存しない |
基礎(ミニチートシート)
良い設計の兆候
- 依存は抽象(インターフェース)に向く
- コンストラクタDI / ファクトリで生成責務分離
- ユニットテスト容易(テストダブル差替OK)
避けたい匂い
- 巨大クラス(God Object)
- 条件分岐の乱立(型による分岐→ポリモーフィズム)
- 下位モジュールへの強い結合(newの直書き乱用)
最小チェックリスト
- 責務は 1 文で説明できるか(SRP)
- 機能追加は既存改変よりも拡張で対応できるか(OCP)
- サブタイプの振る舞いは基底契約を破らないか(LSP)
- 利用側に不要なメソッドを強要していないか(ISP)
- 上位ポリシーは実装詳細から独立しているか(DIP)
応用(PHP中心の例)
SRP + OCP + DIP(ストラテジ差替)
<button class="copy" data-copy>Copy</button><?php
interface Notifier { public function send(string $to, string $message): void; }
class MailNotifier implements Notifier { public function send(string $to, string $message): void {/* mail() 等 */} }
class SlackNotifier implements Notifier { public function send(string $to, string $message): void {/* Webhook */} }
class UserRegistrationService { // SRP: 登録のユースケースに責務を限定
public function __construct(private Notifier $notifier) {}
public function register(string $email): void {
// 1) ユーザー作成(省略)
// 2) 通知(OCP: 実装を増やしても拡張で対応)
$this->notifier->send($email, 'ようこそ!'); // DIP: 抽象に依存
}
}
$service = new UserRegistrationService(new MailNotifier());
$service->register('user@example.com');
?>
通知手段追加は Notifier 実装を増やすだけ。既存クラスは変更不要(OCP)。
ISP(太いIFの分割)
<button class="copy" data-copy>Copy</button><?php
// 悪い例: 1つの巨大リポジトリIF
interface Repository { public function find(); public function save(); public function exportCsv(); }
// 良い例: クライアントごとに細分化
interface Reader { public function find(); }
interface Writer { public function save(); }
interface Exportable { public function exportCsv(); }
class UserReader implements Reader {/* ... */}
class UserWriter implements Writer {/* ... */}
?>
発展(テストとアーキテクチャ)
テスト容易性
- DIによりテストダブル(スタブ/モック)注入
- ユースケースは副作用を外部に委譲し純粋に
- 境界(I/O)はアダプタ層に隔離(Ports & Adapters)
レイヤ分離
- ドメイン/アプリ/インフラ層(Clean/Hexagonal)
- 上位層は下位詳細に依存しない(DIP)
- 依存の向きは常に内側へ
アンチパターン回避
- new の蔓延(Factory/DIに集約)
- 静的呼び出し濫用(テスト差替困難)
- 過剰な継承(合成/委譲を優先)
最新情報
- ニュース準備中です。(
/data/news.jsonを配置すると表示されます)
目的
- 変更に強いシステムを育てる
- レビュー/引き継ぎ/オンボーディングの容易化
- 品質と速度の両立(技術的負債の抑制)
仕事の現場(ベストプラクティス)
DO
- ユースケースごとに小さいサービスへ分割(SRP)
- 外部IFはポートで抽象化(DIP)
- コンストラクタDI + Factoryで生成責務分離
- 契約テストでLSPを担保
DONT
- 下位詳細を上位から new 直書き
- 継承のための継承(共通化は合成で)
- 何でもかんでも単一メガIF(ISP違反)
そのほかの原則や規約と比較
| 指針 | 要旨 | 関係性 |
|---|---|---|
| KISS | シンプルに保つ | SOLIDの過剰化を抑制 |
| DRY | 重複を避ける | SRP/抽象化と相性良い |
| YAGNI | 今いらないものは作らない | OCPとのバランスが肝 |
| PSR(PHP-FIG) | コーディング規約/標準IF | 実装互換で差替やすい |
知っておくといい
知識
- 抽象・合成・委譲と継承の使い分け
- 契約による設計(Design by Contract)
- ポート&アダプタ / クリーンアーキテクチャ
技能
- 責務分割・命名・境界づけ
- テストダブル設計(スタブ/モック)
- 重複/分岐の抽象化(ストラテジ/ステート)
用語・ルール
- 凝集度/結合度、境界、契約、ポリモーフィズム
- SRP/OCP/LSP/ISP/DIP の定義とアンチ例
- PSR-1/12, PSR-4, PSR-7/18 等
例
LSP(契約を守るサブタイプ)
<button class="copy" data-copy>Copy</button><?php
abstract class Rectangle { protected int $w; protected int $h; public function setWidth(int $w){$this->w=$w;} public function setHeight(int $h){$this->h=$h;} public function area(): int { return $this->w * $this->h; } }
class Square extends Rectangle { // 典型的なLSP違反例
public function setWidth(int $w){ $this->w=$this->h=$w; }
public function setHeight(int $h){ $this->w=$this->h=$h; }
}
// → 期待していた矩形の契約(独立に幅/高さを設定できる)が破られる
?>
対策: ビジネス上の意味が異なるなら継承ではなく別型/合成を検討。
DIP(ポート&アダプタ)
<button class="copy" data-copy>Copy</button><?php
interface UserRepository { public function save(array $user): void; }
class PdoUserRepository implements UserRepository { public function __construct(private \PDO $pdo){} public function save(array $user): void { /* INSERT */ } }
class InMemoryUserRepository implements UserRepository { private array $store=[]; public function save(array $user): void { $this->store[]=$user; } }
class RegisterUserUseCase { public function __construct(private UserRepository $repo){} public function __invoke(array $input): void { /* validate... */ $this->repo->save($input); } }
$usecase = new RegisterUserUseCase(new InMemoryUserRepository()); // テスト時に差替
$usecase(['id'=>1,'name'=>'alice']);
?>
関連リンク
- SOLID: Wikipedia (JP)
- Principles of OOD (Robert C. Martin)
- Clean Architecture(書籍)
- Martin Fowler
- PHP-FIG PSR 一覧
各サイトの利用規約・ライセンスを遵守してください。
より具体的に(リファクタ演習)
Before(匂いのあるコード)
<button class="copy" data-copy>Copy</button><?php
class OrderService {
public function create(array $order): void {
// 受注
// 支払い
$pdo = new \PDO('dsn','u','p'); // 下位詳細に直接依存(DIP違反)
$pdo->prepare('INSERT ...');
// 通知
mail($order['email'], 'ok', '...'); // 直呼び(差替不能)
}
}
?>
After(責務分割+抽象依存)
<button class="copy" data-copy>Copy</button><?php
interface PaymentGateway { public function charge(int $yen): void; }
interface OrderRepository { public function save(array $order): void; }
interface Notifier { public function send(string $to, string $msg): void; }
class OrderService {
public function __construct(
private PaymentGateway $pay,
private OrderRepository $repo,
private Notifier $notify,
){}
public function create(array $order): void {
$this->pay->charge($order['amount']);
$this->repo->save($order);
$this->notify->send($order['email'], '受注完了');
}
}
?>
生成は Factory / DI コンテナ等に委譲。ユースケースは抽象にのみ依存。