Facade は、クラスの複雑なサブシステムに対して簡素化されたインターフェースを提供する構造的デザインパターンです。モバイル開発では、FacadeはService LayerやUseCaseとして実装されることが多く、ネットワーク、データベース、分析とのやり取りを隠します。Martin Fowler(Patterns of Enterprise Application Architecture、2003年)によると、Facadeはサービスレイヤーを整理するための重要なパターンの一つです。
重要ポイント
Facade は、サブシステムの一連のインターフェースに対して統一されたインターフェースを提供する構造的パターンです。サブシステムの利用を簡素化する高水準のインターフェースを定義します。Facadeは新しい機能を追加しません。既存のコンポーネントを調整し、その相互作用の複雑さをクライアントから隠します。
// 複雑なサブシステム
class AuthApi {
suspend fun login(email: String, pass: String): TokenResponse
}
class UserDao {
suspend fun saveUser(user: UserEntity)
suspend fun getUser(id: Long): UserEntity?
}
class AnalyticsTracker {
fun track(event: String, params: Map )
}
// Facade — UIのためのシンプルなインターフェース
class AuthService(
private val api: AuthApi,
private val dao: UserDao,
private val analytics: AnalyticsTracker
) {
suspend fun loginUser(email: String, password: String): Result {
return runCatching {
val token = api.login(email, password)
val user = User(token.userId, email, token.accessToken)
dao.saveUser(user.toEntity())
analytics.track("login_success", mapOf("method" to "email"))
user
}
}
}AuthService は、AuthApi、UserDao、AnalyticsTrackerをViewModelから隠すFacadeです。UIは、API、データベース、分析への3つの別々のリクエストの代わりにloginUser(email, password)を呼び出します。これにより結合度が下がります。もし明日AuthApiがFirebaseAuthになったり、UserDaoがRoomに移行したりしても、Facadeだけが変わり、UIは変わりません。
Service Layer はモバイルアプリケーションにおけるFacadeの一般的な実装です。ビジネスロジックとレイヤー間の調整をカプセル化します。AndroidではService LayerはしばしばUseCase(Clean Architecture)を通じて実装され、iOSではManagerやServiceプロトコルを通じて実装されます。
| コンポーネント | サブシステムでの役割 | Facadeが隠すもの |
|---|---|---|
| AuthApi | サーバーへのネットワークリクエスト | リクエスト形式、エンドポイント、HTTPエラー処理 |
| UserDao | トークンのローカル保存 | データベーススキーマ、SQLクエリ、マイグレーション |
| AnalyticsTracker | 分析イベントの送信 | Firebase/AppMetrica SDK、イベント形式 |
| NetworkMonitor | ネットワーク可用性の確認 | ConnectivityManager、BroadcastReceiver |
AuthService は4つのコンポーネントすべてを統合します。ViewModelは、裏でネットワークリクエスト、データベース書き込み、トラッキング、ネットワーク確認が行われていることを知らずに1つのメソッドを呼び出します。テスト時にはAuthServiceをモックに置き換え、実際のコンポーネントと統合せずに認証ロジック全体を検証できます。
Facade、Adapter、Mediator は構造的パターンですが、解決する問題は異なります。3つとも仲介オブジェクトを導入するため、混同されがちです。モバイルアプリの例で違いを分析しましょう。
| 側面 | Facade | Adapter | Mediator |
|---|---|---|---|
| 目的 | サブシステムのインターフェースを簡素化する | インターフェースを変換する | コンポーネント間の結合度を下げる |
| 方向 | 1つのインターフェース → サブシステム | クライアント → Adaptee | N個のコンポーネント ↔ Mediator |
| インターフェースの変更 | 新しい簡素化されたものを作る | 既存のものを変換する | 変更せず調整する |
| サブシステムはパターンを知っているか? | いいえ | いいえ | はい、Mediatorを通じて通信する |
| モバイル開発での例 | UseCase / Service Layer | RecyclerView.Adapter | iOSのCoordinator |
Facade はサブシステムを隠しません。クライアントは必要に応じてAuthApiに直接アクセスできます。Adapter はAdapteeのインターフェースを必ず変更します。Mediator は互いを知らない可能性のある多数のオブジェクト間の複雑な相互作用を調整します。
Facadeの実装 は、Clean Architectureを用いたAndroid向けKotlinで、各ビジネスシナリオのエントリーポイントとしてUseCaseを使用します。UseCaseは、リポジトリ、マッパー、その他の依存関係をUIレイヤーから隠すFacadeです。
// RepositoryもFacadeだが、より低いレベル
class UserRepositoryImpl(
private val local: UserLocalDataSource,
private val remote: UserRemoteDataSource,
private val mapper: UserMapper
) : UserRepository {
override suspend fun getUserProfile(id: String): UserProfile {
val cached = local.getUser(id)
if (cached != null && !cached.isStale) {
return mapper.toProfile(cached)
}
val dto = remote.fetchUser(id)
val entity = mapper.toEntity(dto)
local.saveUser(entity)
return mapper.toProfile(entity)
}
}
// UseCase — ビジネスシナリオのためのFacade
class LoadUserProfileUseCase(
private val repo: UserRepository,
private val analytics: AnalyticsTracker
) {
suspend operator fun invoke(userId: String): Result {
return runCatching {
val profile = repo.getUserProfile(userId)
analytics.track("profile_loaded", mapOf("user_id" to userId))
profile
}
}
}LoadUserProfileUseCase はプロフィール読み込みシナリオのためのFacadeです。キャッシュロジック(local → remote)、DTO → Entity → Profileのマッピング、分析トラッキングを隠します。ViewModelはinvoke(userId)を呼び出し、準備済みのUserProfileまたはエラーを受け取ります。UseCaseはリポジトリをモックオブジェクトに置き換えることで単独でテストできます。
iOSのFacade はしばしばManagerやServiceとして実装されます。Androidとは異なり、iOSはFacadeのインターフェースを定義するためにプロトコルを使用し、テストで実装を簡単に差し替えられます。メディアを扱うFacadeを見てみましょう。読み込み、キャッシュ、表示です。
protocol MediaServiceProtocol {
func loadImage(from url: URL) async -> Result<UIImage, Error>
}
final class MediaService: MediaServiceProtocol {
private let cache: ImageCache
private let downloader: ImageDownloader
private let decoder: ImageDecoder
func loadImage(from url: URL) async -> Result<UIImage, Error> {
// 1. キャッシュを確認する
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. データを読み込む
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. デコードする
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. キャッシュに保存する
cache.setImage(image, for: url)
return .success(image)
}
}MediaService は3段階のプロセス(キャッシュ → 読み込み → デコード)をカプセル化します。UIはImageCache、URLSession、ImageDecoderを管理する代わりに1つのメソッドloadImage(from:)を呼び出します。テスト時には、MediaServiceProtocolを実際の読み込みなしで事前設定された画像を返すモックに置き換えられます。
間違い はFacadeの設計における利点を無効にします。簡素化の代わりに、システム全体が依存するGod Objectが生まれます。3つの主要な問題を分析しましょう。
1つのFacadeが認証、プロフィール読み込み、メッセージ送信、同期のメソッドを含む場合、それはGod Objectです。兆候:1つのクラスに15以上の公開メソッド。解決策:責任領域ごとに複数の専門化されたFacadeに分割する — AuthService、ProfileService、MessagingService。
Facadeがサブシステム固有の型(たとえばFirebaseUserやRealmObject)を返す場合、クライアントは依然として特定の実装に縛られます。解決策:Facadeは独自の型(data class / struct)のみを返し、クライアントをサブシステムの詳細から完全に抽象化する必要があります。
Facadeがサブシステムへの直接アクセスを禁止すると、ボトルネックになります。時にはクライアントがサブシステムの特定のメソッドを必要とし、Facadeを通らせるのは冗長です。Facadeは厳格なゲートキーパーであってはなりません。便利なインターフェースを提供しますが、コンポーネントへの直接アクセスをブロックしません。
よくある質問
Facade はサブシステムに簡素化されたインターフェースを提供し、しばしば新しいメソッド群を作成します。Proxy は元のオブジェクトと同じインターフェースを維持しますが、アクセス制御や遅延読み込みを追加します。Facadeは簡素化のため、Proxyは制御のためです。
Service Layer はアプリケーションアーキテクチャレベルでのFacadeパターンの実装です。UIとビジネスロジックの境界を定義し、サービスの実装詳細を隠します。AndroidではService LayerはしばしばUseCaseを通じて実装され、iOSではManagerやServiceプロトコルを通じて実装されます。
God Facade は、1つのクラスが複数の無関係なサブシステムの責任を引き受けるときに発生します。兆候:15以上の公開メソッド、異なるドメインのメソッド(認証 + 支払い + 通知)、テストが難しいクラス(10以上の依存関係)。解決策:ドメイン別のFacadeに分割します。
1〜2画面のアプリではFacadeは冗長です。UIからAPIとデータベースを直接呼び出す方が簡単で明確です。Facadeは5画面以上と3つ以上のサブシステムで効果を発揮します。中小規模のプロジェクトでは、追加のUseCaseラッパーなしでRepositoryを唯一のFacadeレイヤーとすれば十分です。
Facadeはテストを簡素化します。サブシステム全体を1つのモックオブジェクトで置き換えるからです。3つのコンポーネント(ネットワーク + データベース + 分析)をモックする代わりに、1つのFacadeをモックすれば十分です。Swiftではこれにプロトコルが使われ、Kotlinではインターフェースが使われます。Facadeはコンポーネントの調整を検証する統合テストにも便利です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。