概要
Clean Coder Blog の同心円図 及び class 図を忠実に再現してみた実装例です。
⚠️ 実装の重要ポイント
-
Gatewaysの配置: 図に従い、
Gateways(具象)は Green Layer に配置 -
Red Layerの独立: Interactorが外部と通信するためのインターフェース(
UserDataPort)を Red Layer で定義 - 依存性の逆転 (DIP): Green Layer(Gateway)が Blue Layer(DB)の具体的な型に依存しないよう、外部から処理を注入
- UIとPresenterの関係: UIは最外周(Blue)に位置し、Presenter(Green)からの通知を受け取って描画に専念
忠実な再現コード (Dart)
// =============================================================================
// 1. Entities (Yellow Layer) - Enterprise Business Rules
// =============================================================================
class UserEntity {
final String id;
final String name;
UserEntity({required this.id, required this.name});
}
// =============================================================================
// 2. Use Cases (Red Layer) - Application Business Rules
// =============================================================================
/// [図の Use Case Input Port]
abstract interface class UseCaseInputPort {
void execute();
}
/// [図の Use Case Output Port]
abstract interface class UseCaseOutputPort {
void present(UserEntity entity);
}
/// [Data Port] - Red Layerで定義される境界
/// Interactorはこの抽象を介して外部(DB等)とやり取りする
abstract interface class UserDataPort {
Future<UserEntity> loadData();
}
/// [図の Use Case Interactor]
class UseCaseInteractor implements UseCaseInputPort {
final UseCaseOutputPort outputPort;
final UserDataPort dataPort; // Red Layerの抽象にのみ依存
UseCaseInteractor({required this.outputPort, required this.dataPort});
@override
Future<void> execute() async {
// 1. DataPort(境界)を通じてデータを取得
final entity = await dataPort.loadData();
// 2. OutputPort(境界)を通じて結果を外へ渡す
outputPort.present(entity);
}
}
// =============================================================================
// 3. Interface Adapters (Green Layer) - Controllers, Presenters, Gateways
// =============================================================================
/// [図の Controller]
class UserController {
final UseCaseInputPort inputPort;
UserController(this.inputPort);
void handle() => inputPort.execute();
}
/// [図の Presenter]
class UserPresenter implements UseCaseOutputPort {
// UIが購読するためのコールバック
void Function(String)? onViewModelUpdate;
@override
void present(UserEntity entity) {
// UI用にデータを整形(ViewModelの作成)
final viewModel = "Name: ${entity.name}";
onViewModelUpdate?.call(viewModel);
}
}
/// [図の Gateways]
/// Red Layerの抽象を実装し、外部(Blue)への橋渡しを行う
class UserGateway implements UserDataPort {
// Blue Layerの具体的クラスに依存しないよう、関数として注入を受ける
final Future<String> Function() fetchRawData;
UserGateway(this.fetchRawData);
@override
Future<UserEntity> loadData() async {
final raw = await fetchRawData();
// 外部データをEntityに変換(Adapterとしての役割)
return UserEntity(id: "1", name: raw);
}
}
// =============================================================================
// 4. Frameworks & Drivers (Blue Layer) - UI, DB, Devices
// =============================================================================
/// [図の DB / External Interfaces]
class ExternalDatabase {
Future<String> fetchRaw() async => "Data from PostgreSQL";
}
/// [図の UI]
class UserInterface {
final UserController controller;
final UserPresenter presenter;
UserInterface(this.controller, this.presenter) {
// Presenterからの通知を待ち受ける
presenter.onViewModelUpdate = (vm) => _render(vm);
}
void _render(String vm) => print("UI Display: $vm");
void onUserClick() => controller.handle();
}
// =============================================================================
// 5. Main Setup (Flow of Control)
// =============================================================================
void main() {
// --- Blue Layer ---
final db = ExternalDatabase();
// --- Green Layer ---
final presenter = UserPresenter();
final gateway = UserGateway(db.fetchRaw); // Blueの処理を注入
// --- Red Layer ---
final interactor = UseCaseInteractor(
outputPort: presenter,
dataPort: gateway,
);
// --- Assembler (Finalizing Blue & Green) ---
final controller = UserController(interactor);
final ui = UserInterface(controller, presenter);
// 実行開始 (UI -> Controller -> Interactor -> Gateway -> Presenter -> UI)
ui.onUserClick();
}
ドキュメント補足:層の関係性まとめ
| 層 (Layer) | 対応クラス | 役割 |
|---|---|---|
| Entities | UserEntity |
ビジネスの核心となるデータ構造。 |
| Use Cases |
Interactor, Ports
|
アプリケーション固有のルール。Gatewaysの名前を直接使わずDataPortとして定義。 |
| Interface Adapters |
Controller, Presenter, Gateway
|
変換担当。Redのインターフェースを実装し、外側と内側を繋ぐ。 |
| Frameworks & Drivers |
UI, Database
|
詳細担当。フレームワーク、DB、表示デバイスの具体的な実装。 |
