プレゼンターとUIステート
読了時間:約15分
はじめに
プレゼンテーション層はPrismアーキテクチャの重要なコンポーネントであり、ユーザーインターフェースとユーザー操作の管理を担当しています。この層の中心となるのはプレゼンターであり、UI論理とステート管理を処理します。このドキュメントでは、Prismアーキテクチャにおけるプレゼンターの役割、UIステートの管理方法、および他の層との通信方法について説明します。
Prismアーキテクチャにおけるプレゼンターは、ユーザーインターフェースとアプリケーションのビジネスロジックの橋渡しの役割を果たします。プレゼンターはドメインデータをプレゼンテーション用の形式に変換し、UIステートを管理し、ユーザー操作を処理し、オーケストレーション層と通信します。プレゼンターとUIステート管理を理解することは、アプリケーションのドメインモデルを正確に反映する、応答性が高く保守性の高いユーザーインターフェースを作成するために不可欠です。
Prismアーキテクチャにおけるプレゼンテーション層
目的と責任
Prismアーキテクチャにおけるプレゼンテーション層は以下の責任を負っています:
- 情報の表示:アプリケーションデータをユーザーフレンドリーな形式で表示
- ユーザー入力の取得:ユーザー操作の受け取りと検証
- UIステートの管理:UIコンポーネントと画面のステートの追跡
- データ変換:ドメインデータをプレゼンテーション形式に変換、およびその逆
- ビジネスロジックとの通信:ユーザーアクションをビジネス操作に変換
プレゼンテーション層はすべてのUI関連の懸念事項をカプセル化し、アプリケーションの残りの部分が特定のUIフレームワークやプラットフォームに縛られることなくビジネスロジックに集中できるようにします。
主要コンポーネント
プレゼンテーション層はいくつかの主要コンポーネントで構成されています:
- UIコンポーネント:単純なボタンから複雑なカスタムコントロールまで、ユーザーインターフェースを構成する視覚要素
- プレゼンター/ビューモデル:UIとアプリケーションロジックの間の仲介者で、プレゼンテーションロジックを処理
- 画面:特定の機能のための完全なユーザーインターフェース
- ナビゲーションコントローラー:画面間の遷移と全体的なフローを管理
- UIステート管理:UI固有のステートの保存と更新を処理
これらのコンポーネントが連携して、UI関連の懸念事項とビジネスロジックの間にクリーンで保守可能な分離を作り出します。
PrismUI:推奨プレゼンテーションパターン
PrismUIはPrismアーキテクチャの推奨プレゼンテーションパターンであり、全体的なアーキテクチャのコア原則に沿った構造化されたUIアーキテクチャアプローチを提供します。
PrismUIの主な特徴
- 階層的プレゼンター構造:UIコンポーネント構造を反映した3層階層でプレゼンターを整理
- インテントベースの通信:ユーザーアクションを表現する明示的なインテントオブジェクトを使用
- 単方向ステートフロー:プレゼンターからビューへの予測可能なステートフローを実装
- 関心事の明確な分離:UIロジックをビュー実装とビジネスロジックの両方から分離
この構造化されたアプローチにより、明確な責任とコミュニケーションパターンを持つ、柔軟で保守可能なプレゼンテーション層が作成されます。
他の層との通信
PrismUIの通信はPrismアーキテクチャ全体で使用されるパッケージ配送モデルに従います:
このモデルでは:
- インテント定義は共通層に存在
- ステート定義は共通層に存在
- プレゼンターは共通層の定義を使用してインテントオブジェクトを作成
- プレゼンターは共通層の定義を使用してステートオブジェクトを処理
プレゼンター階層
PrismUIの重要な革新の一つは階層的プレゼンター構造であり、各階層が特定の責任を持つ3つの明確なレベルにプレゼンターを整理します。
トップレベルプレゼンター
トップレベルプレゼンターは画面またはフローレベルで動作します:
- 画面プレゼンター:メインナビゲーションフローの一部である完全な画面を管理
- モーダルプレゼンター:メインフローを一時的に中断する全画面または部分画面のオーバーレイを管理
- トップダイアログプレゼンター:複数のコンポーネントや独立したステート管理を持つ複雑なダイアログを管理
主な責任:
- オーケストレーション層と直接通信
- 画面間または主要セクション間のナビゲーションを管理
- 画面内の異なる機能間を調整
- ミッドレベルプレゼンターの作成と管理
- オーケストレーション層から受け取ったステートオブジェクトを処理
- オーケストレーション層に送信するインテントオブジェクトを作成
ミッドレベルプレゼンター
ミッドレベルプレゼンターは画面内の論理的なセグメントや機能を管理します:
- セグメントプレゼンター:画面やモーダル内の論理的なセクションを管理
- フロープレゼンター:複数の画面/ビューにまたがる可能性のある複数ステップのプロセスを管理
- ミッドダイアログプレゼンター:情報を表示したり基本的な入力を収集したりする、よりシンプルなダイアログを管理
主な責任:
- 機能固有のステートとロジックを管理
- コンポーネントレベルプレゼンターの作成と管理
- 重要なインテントを親プレゼンターに転送
- 画面レベルのステートを特定の機能用に変換
コンポーネントレベルプレゼンター
コンポーネントレベルプレゼンターは個々のUIコンポーネントを管理します:
- コンポーネントプレゼンター:個々のUIコンポーネントを管理
- リストアイテムプレゼンター:リストやコレクション内のアイテムを管理
主な責任:
- コンポーネント固有のUIロジックを処理
- 集中的なコンポーネントステートを維持
- ユーザー操作からインテントを生成
- 親プレゼンターから調整されたステートを受け取る
プレゼンター階層フロー
この階層的フローにより以下が保証されます:
- コンポーネントは必要なことだけを処理
- インテントとステートオブジェクトはアーキテクチャ内を効率的に流れる
- 各層は適切な責任を持つ
- 通信は適切な境界を維持
ビュータイプとそのプレゼンター
Prismアーキテクチャは、異なるビュータイプに対して明確なカテゴリを定義し、それぞれに特定のプレゼンター関係を持たせています:
画面とモーダル
- 画面:メインナビゲーションフロー内の完全なアプリケーション画面
- モーダル:フローを中断する全画面または部分画面のオーバーレイ
- トップダイアログ:複数のコンポーネントを持つ複雑なダイアログ
- プレゼンターレベル:トップレベル(オーケストレーション層への直接アクセス)
セグメントとセクション
- セグメント:複数のコンポーネントを含む画面の論理的なセクション
- フロー:複数ステッププロセスのコーディネーター
- ミッドダイアログ:親プレゼンターによって管理されるよりシンプルなダイアログ
- プレゼンターレベル:ミッドレベル(親プレゼンターを通じて通信)
個別コンポーネント
- コンポーネント:個々のUI要素
- リストアイテム:コレクション内のアイテム
- ポップアップとトースト:一時的なUI要素
- プレゼンターレベル:コンポーネントレベル(親プレゼンターを通じて通信)
この構造化されたアプローチにより、各UI要素は責任の明確な階層を維持しながら、適切なプレゼンターサポートを持つことが保証されます。
UIステート管理
UIステートとは
UIステートはユーザーインターフェースの現在の状態を表します。これには以下が含まれます:
- データステート:表示されている情報
- 操作ステート:ユーザー操作の現在の状態
- ナビゲーションステート:ユーザーがアプリケーション内のどこにいるか
- フィードバックステート:ローディングインジケーター、エラーメッセージなど
- フォームステート:入力フィールドとフォームの状態
UIステートはドメインステートとは異なり、基盤となるビジネスデータではなく、情報がどのように表示されるかに特に焦点を当てています。
ステート構成
インテントオブジェクトと同様に、プレゼンテーション層で使用されるステートオブジェクトもパッケージ配送アプローチに従います:
ステート構造
Prismアーキテクチャでは、UIステートは通常、明確な階層を持つ不変オブジェクトとして構造化されています:
- 画面ステート:トップレベルプレゼンターによって管理される画面全体のステート
- 機能ステート:ミッドレベルプレゼンターによって管理される特定の機能のステート
- コンポーネントステート:コンポーネントプレゼンターによって管理される個々のコンポーネントのステート
各レベルはそのスコープに関連するステートのみを含み、上位レベルは適切な部分を下位レベルに渡します。
ステートフローパターン
UIステートはプレゼンター階層を予測可能な方法で流れます:
- 下向きフロー:ステートは上位レベルから下位レベルのプレゼンターへ流れる
- 変換:各レベルは特定のニーズに合わせてステートを変換する
- 不変性:ステートオブジェクトは不変で、変更には新しいインスタンスが作成される
- 選択的伝播:関連するステート変更のみが更新をトリガーする
この単方向フローにより、予測可能で保守可能なステート管理システムが作成されます。
ステート定義の場所
プレゼンテーション層で使用されるすべてのステートタイプは共通層で定義する必要があります:
Common/
└── State/
├── Base/
│ └── BaseState.swift # すべてのステートの基本インターフェース
├── UI/
│ ├── LoginScreenState.swift # 画面レベルのステート
│ ├── ProfileFormState.swift # 機能レベルのステート
│ └── ButtonState.swift # コンポーネントレベルのステート
└── ...
これにより以下が保証されます:
- すべての層が必要なステート定義にアクセスできる
- ステートオブジェクトは一貫した構造と動作を持つ
- ステートは層間を自然に流れることができる
- 通信は適切な境界を維持する
ステート更新の処理
Prismアーキテクチャのステート更新は以下のパターンに従います:
- イベントベースの更新:ステートはドメインやユーザーアクションからのイベントに応じて変更される
- アトミックな更新:ステートは部分的ではなく、完全な単位として更新される
- 派生値:現在のステートに基づいて計算された値
- ステート差分検出:実際のステート変更のみがUI更新をトリガーする
これらのパターンにより、アプリケーション全体で効率的で予測可能なステート管理が保証されます。
インテントベースの通信
インテントとは
Prismアーキテクチャにおけるインテントはユーザーアクションまたは要求を表現します。インテントは以下の特徴を持ちます:
- 明示的:特定の目的を持つ明確に定義されたオブジェクト
- 不変:作成後は変更されない
- 自己完結型:要求されたアクションに必要なすべてのデータを含む
- 階層的:プレゼンター階層に合わせて整理される
インテントはユーザーが達成したいことを表現し、それがどのように実装されるべきかは表現しません。
インテントフロー
インテントはプレゼンター階層を上向きに流れます:
- 生成:コンポーネントプレゼンターはユーザーアクションからインテントを生成
- バブリング:インテントはミッドレベルとトップレベルのプレゼンターへとバブルアップ
- 処理:各レベルはインテントを処理するか上位に渡す
- 変換:トップレベルプレゼンターはUIインテントをユースケース要求に変換
この上向きフローにより、インテントがアーキテクチャの適切なレベルで処理されることが保証されます。
インテント構成
ステートオブジェクトと同様に、すべてのインテントタイプは共通層で定義する必要があります:
Common/
└── Intent/
├── Base/
│ └── BaseIntent.swift # すべてのインテントの基本インターフェース
├── UI/
│ ├── LoginIntent.swift # UI固有のインテント
│ ├── NavigationIntent.swift # ナビゲーション関連のインテント
│ └── FormIntent.swift # フォーム操作インテント
└── ...
一般的なインテントパターン
一般的なインテントパターンには以下が含まれます:
- アクションインテント:特定のアクションを要求(例:
SaveProfileIntent
) - ナビゲーションインテント:ナビゲーション変更を要求(例:
ShowDetailsIntent
) - データインテント:データ変更を要求(例:
UpdateNameIntent
) - イベントインテント:UIイベントについて通知(例:
ItemSelectedIntent
)
これらのパターンは、ユーザー操作を処理するための構造化されたアプローチを提供します。
インテント作成
インテントは共通層の定義を使用してプレゼンテーション層で作成されます:
プレゼンターレベル間の通信
下向きデータフロー
データはターゲットを絞ったアプローチでプレゼンター階層を下向きに流れます:
- トップレベルプレゼンター:オーケストレーション層からドメインデータを受け取り、画面レベルのステートに変換
- ミッドレベルプレゼンター:親から集中したステートを受け取り、特定の機能用に変換
- コンポーネントレベルプレゼンター:親から最小限の、コンポーネント固有のステートを受け取る
各レベルは必要なステートのみを受け取るため、不要な結合が防止され、パフォーマンスが向上します。
上向きインテントフロー
インテントは構造化された方法でプレゼンター階層を上向きに流れます:
- コンポーネントレベルプレゼンター:ユーザー操作からインテントを生成
- ミッドレベルプレゼンター:処理できるインテントを処理し、それ以外を親にバブルアップ
- トップレベルプレゼンター:画面レベルのインテントを処理し、重要なインテントをオーケストレーション層の要求に変換
このパターンにより、インテントが適切なレベルで処理され、重要なインテントのみがオーケストレーション層に到達することが保証されます。
最新のUIフレームワークとの統合
宣言型UIフレームワーク
PrismUIは、SwiftUI、Jetpack Compose、Reactなどの宣言型UIフレームワークと特によく機能します:
- ステート駆動レンダリング:これらのフレームワークはPrismUIのステートベースのアプローチと自然に整合
- コンポーネント階層:これらのフレームワークのコンポーネントベースの性質はPrismUIのプレゼンター階層を反映
- 単方向データフロー:これらのフレームワークはPrismUIの下向きステートフローをサポート
- イベント処理:ユーザー操作を処理するためのクリーンなメカニズムを提供
この自然な整合により、PrismUIは最新のUIフレームワークと特に効果的です。
フレームワーク固有の適応
コア原則は一貫していますが、PrismUIは特定のUIフレームワークに適応します:
SwiftUI統合
Jetpack Compose統合
React統合
テスト戦略
プレゼンターテスト
PrismUIのプレゼンターはテスト可能性を考慮して設計されています:
- ユニットテスト:プレゼンターロジックを分離してテスト
- インテントテスト:インテントの正しい処理を検証
- ステートテスト:適切なステート変換を確認
- 統合テスト:他のコンポーネントとの相互作用をテスト
これらのアプローチにより、UIテストフレームワークを必要とせずにプレゼンテーションロジックの包括的なテストが可能になります。
コンポーネントテスト
プレゼンターを含む完全なUIのテスト:
- スナップショットテスト:特定のステートに対するUIレンダリングを検証
- インタラクションテスト:ユーザー操作と結果のステート変更をテスト
- ナビゲーションテスト:インテントに基づく正しいナビゲーションフローを検証
- 統合テスト:実際またはモックデータを使用して完全な機能をテスト
この多層テストアプローチにより、個々のコンポーネントとその相互作用の両方が正しく機能することが保証されます。
避けるべき一般的なアンチパターン
ビュー内のUIロジック
ビュー内にUIロジックを配置すると、いくつかの問題が発生します:
- テスト可能性の低下:ビュー内のUIロジックはテストが難しい
- ロジックの重複:類似のロジックがビュー間で繰り返される可能性がある
- 密結合:ビューが特定の動作に密接に結合される
UIロジックはビューではなくプレゼンターに保持します。
プレゼンター内のビジネスロジック
プレゼンターにビジネスロジックを含めることも問題を引き起こします:
- 層違反:ビジネスロジックはドメインまたはオーケストレーション層に属する
- 再利用性の低下:プレゼンターが特定のビジネスルールに縛られる
- テストの複雑さ:テストがより複雑になる
ビジネスロジックはプレゼンターではなく、適切な層に保持します。
双方向データバインディング
便利ですが、双方向データバインディングは問題を引き起こす可能性があります:
- 予測不能な更新:ステート変更が複数のソースから来る可能性がある
- デバッグの難しさ:変更のソースを追跡するのが難しい
- カスケード効果:変更が予測不能にカスケードする可能性がある
明示的な更新による単方向データフローを優先します。
共通層定義以外でのインテントとステートの手動作成
共通層の定義を使用せずにインテントとステートオブジェクトを作成すると、不整合が発生します:
- 不整合な構造:インテントとステートオブジェクトが異なる構造を持つ可能性がある
- 保守の困難さ:通信パターンの変更には複数の場所での更新が必要
- 型の非互換性:オブジェクトが他の層の期待と互換性がない可能性がある
インテントとステートオブジェクトには常に共通層の定義を使用してください。
実例:プロフィール機能
これらのコンセプトが実際にどのように連携するかを示すために、ユーザープロフィール機能を考えてみましょう:
プレゼンター階層
- トップレベル:
ProfileScreenPresenter
(プロフィール画面全体を管理) - ミッドレベル:
ProfileInfoPresenter
(個人情報セクションを管理)、ActivityFeedPresenter
(アクティビティセクションを管理) - コンポーネントレベル:
AvatarPresenter
、NameFieldPresenter
、ActivityItemPresenter
インテントとステートフロー
この構造化されたアプローチにより、データとユーザーアクションの両方が明確で追跡可能なフローでシステム内を流れます。
プレゼンテーションにおける共通層の役割
共通層はプレゼンテーション層に以下を提供することで重要な役割を果たします:
- インテント定義:プレゼンターが使用する基本タイプと特定のインテントタイプ
- ステート定義:プレゼンターが使用する基本タイプと特定のステートタイプ
- 表示オブジェクト:プレゼンテーション用のUI対応データ構造
- フォーマットユーティリティ:表示用データのフォーマットヘルパー
- 検証ユーティリティ:ユーザー入力を検証するためのツール
これらのタイプをすべて共通層で定義することにより、適切な層の境界を維持しながらアプリケーション全体での一貫性を確保します。
プレゼンテーションタイプ用の共通層構造
Common/
├── Intent/
│ ├── Base/
│ │ └── BaseIntent.swift # 基本インテントインターフェース
│ └── UI/
│ ├── ProfileIntent.swift # プロフィール画面インテント
│ ├── NavigationIntent.swift # ナビゲーションインテント
│ └── FormIntent.swift # フォーム操作インテント
├── State/
│ ├── Base/
│ │ └── BaseState.swift # 基本ステートインターフェース
│ └── UI/
│ ├── ProfileScreenState.swift # プロフィール画面ステート
│ ├── ProfileFormState.swift # プロフィールフォームステート
│ └── NavbarState.swift # ナビゲーションステート
└── DisplayObjects/
├── UserDisplayObject.swift # ユーザー表示データ
├── ActivityItemDO.swift # 表示用アクティビティアイテム
└── ...
結論
プレゼンターとUIステート管理はPrismアーキテクチャのプレゼンテーション層の重要なコンポーネントです。トップレベル、ミッドレベル、コンポーネントレベルのプレゼンターからなる階層的プレゼンター構造は、ユーザーインターフェースの自然な構造を反映した明確な組織を提供します。下向きに流れるステートと上向きに流れるインテントの単方向フローにより、予測可能で保守可能なシステムが作成されます。
共通層からのインテントとステート定義を使用することで、プレゼンテーション層は適切なアーキテクチャ境界を保持しながら一貫した通信パターンを維持します。このアプローチは、Prismアーキテクチャ全体で使用されるパッケージ配送モデルに沿っています。
プレゼンターとUIステート管理を実装する際は、階層構造の維持、ステートの不変性と下向きフローの保持、ユーザーアクションに明示的なインテントの使用、ドメインとプレゼンテーション形式間のデータの適切な変換に焦点を当てます。これらの原則に従うことで、ユーザーに対して応答性が高く、アプリケーションのビジネスロジックとも適切に統合されたユーザーインターフェースを作成できます。