Facade: モバイルアーキテクチャにおけるファサードパターンの基礎

著者: IT Sectr 公開日: 2026-02-18 読了時間: 9 分

Facade は、クラスの複雑なサブシステムに対して簡素化されたインターフェースを提供する構造的デザインパターンです。モバイル開発では、FacadeはService LayerやUseCaseとして実装されることが多く、ネットワーク、データベース、分析とのやり取りを隠します。Martin Fowler(Patterns of Enterprise Application Architecture、2003年)によると、Facadeはサービスレイヤーを整理するための重要なパターンの一つです。

重要ポイント

  • Facade — クラス、ライブラリ、フレームワークの複雑なシステムにシンプルなインターフェースを提供する構造的パターン
  • Service Layer — API、キャッシュ、分析をUIから隠すモバイルアーキテクチャにおけるFacadeの実装
  • Facadeはサブシステムを隠さない — クライアントは必要に応じて直接アクセスできる
  • Clean ArchitectureにおけるUseCase — 1つのビジネスシナリオを調整するFacadeの一種
  • FacadeとAdapterの比較: Facadeはインターフェースを簡素化し、Adapterはあるインターフェースを別のものに変換する

Facadeパターンとは何か?

Facade は、サブシステムの一連のインターフェースに対して統一されたインターフェースを提供する構造的パターンです。サブシステムの利用を簡素化する高水準のインターフェースを定義します。Facadeは新しい機能を追加しません。既存のコンポーネントを調整し、その相互作用の複雑さをクライアントから隠します。

Kotlin
// 複雑なサブシステム
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は変わりません。

モバイルアーキテクチャにおけるFacade: Service Layer

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

Facade、Adapter、Mediator は構造的パターンですが、解決する問題は異なります。3つとも仲介オブジェクトを導入するため、混同されがちです。モバイルアプリの例で違いを分析しましょう。

側面 Facade Adapter Mediator
目的 サブシステムのインターフェースを簡素化する インターフェースを変換する コンポーネント間の結合度を下げる
方向 1つのインターフェース → サブシステム クライアント → Adaptee N個のコンポーネント ↔ Mediator
インターフェースの変更 新しい簡素化されたものを作る 既存のものを変換する 変更せず調整する
サブシステムはパターンを知っているか? いいえ いいえ はい、Mediatorを通じて通信する
モバイル開発での例 UseCase / Service Layer RecyclerView.Adapter iOSのCoordinator

Facade はサブシステムを隠しません。クライアントは必要に応じてAuthApiに直接アクセスできます。Adapter はAdapteeのインターフェースを必ず変更します。Mediator は互いを知らない可能性のある多数のオブジェクト間の複雑な相互作用を調整します。

Android向けKotlinでのFacadeの実装

Facadeの実装 は、Clean Architectureを用いたAndroid向けKotlinで、各ビジネスシナリオのエントリーポイントとしてUseCaseを使用します。UseCaseは、リポジトリ、マッパー、その他の依存関係をUIレイヤーから隠すFacadeです。

Kotlin
// 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向けSwiftでのFacadeの実装

iOSのFacade はしばしばManagerやServiceとして実装されます。Androidとは異なり、iOSはFacadeのインターフェースを定義するためにプロトコルを使用し、テストで実装を簡単に差し替えられます。メディアを扱うFacadeを見てみましょう。読み込み、キャッシュ、表示です。

Swift
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使用時の典型的な間違い

間違い はFacadeの設計における利点を無効にします。簡素化の代わりに、システム全体が依存するGod Objectが生まれます。3つの主要な問題を分析しましょう。

God Facade — 責任が多すぎる

1つのFacadeが認証、プロフィール読み込み、メッセージ送信、同期のメソッドを含む場合、それはGod Objectです。兆候:1つのクラスに15以上の公開メソッド。解決策:責任領域ごとに複数の専門化されたFacadeに分割する — AuthService、ProfileService、MessagingService。

サブシステムの詳細を漏らすFacade

Facadeがサブシステム固有の型(たとえばFirebaseUserRealmObject)を返す場合、クライアントは依然として特定の実装に縛られます。解決策:Facadeは独自の型(data class / struct)のみを返し、クライアントをサブシステムの詳細から完全に抽象化する必要があります。

唯一のエントリーポイントとしてのFacade

Facadeがサブシステムへの直接アクセスを禁止すると、ボトルネックになります。時にはクライアントがサブシステムの特定のメソッドを必要とし、Facadeを通らせるのは冗長です。Facadeは厳格なゲートキーパーであってはなりません。便利なインターフェースを提供しますが、コンポーネントへの直接アクセスをブロックしません。

よくある質問

FacadeとProxyの違いは何ですか?

Facade はサブシステムに簡素化されたインターフェースを提供し、しばしば新しいメソッド群を作成します。Proxy は元のオブジェクトと同じインターフェースを維持しますが、アクセス制御や遅延読み込みを追加します。Facadeは簡素化のため、Proxyは制御のためです。

FacadeはService Layerと同じものですか?

Service Layer はアプリケーションアーキテクチャレベルでのFacadeパターンの実装です。UIとビジネスロジックの境界を定義し、サービスの実装詳細を隠します。AndroidではService LayerはしばしばUseCaseを通じて実装され、iOSではManagerやServiceプロトコルを通じて実装されます。

FacadeはいつGod Objectになるのですか?

God Facade は、1つのクラスが複数の無関係なサブシステムの責任を引き受けるときに発生します。兆候:15以上の公開メソッド、異なるドメインのメソッド(認証 + 支払い + 通知)、テストが難しいクラス(10以上の依存関係)。解決策:ドメイン別のFacadeに分割します。

小さなアプリにFacadeは必要ですか?

1〜2画面のアプリではFacadeは冗長です。UIからAPIとデータベースを直接呼び出す方が簡単で明確です。Facadeは5画面以上と3つ以上のサブシステムで効果を発揮します。中小規模のプロジェクトでは、追加のUseCaseラッパーなしでRepositoryを唯一のFacadeレイヤーとすれば十分です。

Facadeを使用するコードをテストするには?

Facadeはテストを簡素化します。サブシステム全体を1つのモックオブジェクトで置き換えるからです。3つのコンポーネント(ネットワーク + データベース + 分析)をモックする代わりに、1つのFacadeをモックすれば十分です。Swiftではこれにプロトコルが使われ、Kotlinではインターフェースが使われます。Facadeはコンポーネントの調整を検証する統合テストにも便利です。

まとめ

  • Facade — 複雑なサブシステムにシンプルなインターフェースを提供する構造的パターン
  • Service LayerUseCase — モバイルアーキテクチャにおけるFacadeの一般的な実装
  • Facade はサブシステムを隠さない:クライアントは必要に応じてコンポーネントに直接アクセスできる
  • FacadeとAdapter: Facadeは簡素化し、Adapterは変換する;FacadeとMediator: Facadeは一方向、Mediatorは双方向
  • God Facade — アンチパターン:1つのクラスに15以上のメソッドがあると単一責任の違反を示す
  • Facadeのためのプロトコル/インターフェースは必須 — サブシステムをモックテストする唯一の方法
  • 推奨:5画面以上と3つ以上のサブシステムでFacadeを導入;小規模プロジェクトではRepositoryで十分

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください