はじめに
ソフトウェア設計において「クラスをどうまとめるか?」は重要なテーマです。
クラス設計には SOLID 原則 が知られていますが、コンポーネント(パッケージ/モジュール)単位でのまとめ方を考える必要があります。
Robert C. Martin(Uncle Bob)は、このために Component Cohesion Principles(凝集度の原則) を提唱しました。
この記事では、その3つの原則を整理し、実務での活用方法を解説します。
Component Cohesion の3原則
1. REP: The Reuse/Release Equivalence Principle
(再利用/リリース等価の原則)
- 定義: 再利用の最小単位は「リリース可能なコンポーネント」である。
- 意味: コンポーネントは独立してバージョン管理・配布できる状態でなければならない。
-
例:
- ✅ 認証機能一式を
auth
モジュールとして切り出し、他アプリでも利用可能に - ❌ クラスを単独でコピー&ペーストして再利用
- ✅ 認証機能一式を
2. CCP: The Common Closure Principle
(共通閉包の原則)
- 定義: 一緒に変更されるクラスは同じコンポーネントにまとめる。
- 意味: 変更の影響範囲を局所化することが目的。
- SOLID の SRP(単一責任原則)をコンポーネントレベルに拡張した考え方。
-
例:
- ✅ 「ユーザ登録画面」「ユーザ登録用ユースケース」「ユーザ登録リポジトリ契約」を同じ
auth
モジュールに配置 - ❌ 「ユーザ登録画面」は
ui
モジュール、「ユーザ登録API」はnetwork
モジュールに分離(両方同時に変更が必要になる)
- ✅ 「ユーザ登録画面」「ユーザ登録用ユースケース」「ユーザ登録リポジトリ契約」を同じ
3. CRP: The Common Reuse Principle
(共通再利用の原則)
- 定義: 一緒に使われるクラスは同じコンポーネントにまとめる。
- 意味: 不要な依存を避け、利用者が余計な依存を背負わないようにする。
-
例:
- ✅ 共通 UI パーツ(Button, Card, Dialog)を
core-ui
にまとめる - ❌ 共通 UI パーツと日付ユーティリティを同じモジュールに入れてしまい、UIを使うと不要な Util にも依存させられる
- ✅ 共通 UI パーツ(Button, Card, Dialog)を
3原則のトレードオフ
これら3つの原則は、しばしば衝突します。
- REP を重視 → 細かすぎるモジュールが増え、管理が大変になる
- CCP を重視 → 変更のたびに大きなモジュールをリリースする必要が出る
- CRP を重視 → 再利用単位が細かくなりすぎ、逆に再利用しづらくなる
👉 したがって「プロジェクト規模・チーム運用体制」に合わせた バランス設計 が重要です。
実践例(Flutter プロジェクト)
❌ 悪い例(凝集性が低い)
lib/
screens/
login_screen.dart
services/
auth_service.dart
utils/
date_util.dart
string_util.dart
-
login_screen
とauth_service
が別モジュール → 認証機能を修正すると両方触る必要あり(CCP違反) -
utils
に雑多な処理を入れて、UI だけ欲しいのに日付処理まで依存してしまう(CRP違反)
✅ 良い例(凝集性が高い)
features/
auth/
presentation/
login_screen.dart
domain/
login_usecase.dart
user_entity.dart
data/
auth_service.dart
core/
ui/
button.dart
card.dart
util/
date_util.dart
- 認証関連を
auth
に集約(CCP) -
core/ui
は UI 部品だけ(CRP) -
auth
をパッケージ化して配布すればそのまま再利用可能(REP)
まとめ
- REP: 再利用はリリース可能単位で
- CCP: 一緒に変更されるものをまとめる
- CRP: 一緒に使うものだけをまとめる
- 3つはしばしばトレードオフ → 状況に応じたバランスが必要