Adapter — 概要、モバイル開発のためのインターフェース変換パターン

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

Adapterは、あるクラスのインターフェースをクライアントが期待する別のインターフェースに変換する構造的なデザインパターンです。モバイル開発では、AdapterはAndroidのRecyclerView.AdapterやiOSのUITableViewDataSourceとして最も頻繁に使用されます。Google I/O(2024)によると、RecyclerViewはAndroidアプリの95%でリスト表示に使用されており、それぞれが独自のAdapter実装を必要とします。

重要なポイント

  • Adapterは、互換性のないインターフェースを持つオブジェクトが連携できるようにする構造的パターンです
  • RecyclerView.AdapterはAndroidにおけるパターンの実装で、データをリスト用のViewHolderに変換します
  • UITableViewDataSourceはiOSにおける実装で、AdapterパターンとDataSourceパターンを組み合わせたものです
  • Object Adapterはコンポジションを使用します:アダプターはアダプティーオブジェクトへの参照を持ちます
  • Class Adapterは多重継承を使用します — C++でのみ可能で、Java/Swift/Kotlinでは利用できません

Adapterパターンとは?

Adapterは、インターフェースの非互換性の問題を解決する構造的パターンです。オブジェクト(Adaptee)を、クライアントが期待するインターフェース(Target)を持つクラス(Adapter)でラップします。クライアントはTargetと連携し、Adapteeの存在を知りません。これはWrapperパターンの一種です。

Kotlin
// 互換性のないインターフェースを持つ既存のクラス
class LegacyAuthApi {
    fun loginWithToken(token: String): Map {
        return mapOf("status" to "ok", "user_id" to 42)
    }
}

// ターゲットインターフェース(クライアントが期待するもの)
interface AuthService {
    suspend fun login(credentials: Credentials): Result
}

// Adapter — LegacyAuthApiをAuthServiceに変換
class LegacyAuthAdapter(
    private val legacyApi: LegacyAuthApi
) : AuthService {
    override suspend fun login(credentials: Credentials): Result {
        val token = "${credentials.login}:${credentials.password}"
            .encodeToByteArray().let { Base64.encodeToString(it) }
        val response = legacyApi.loginWithToken(token)
        return if (response["status"] == "ok") {
            Result.success(User(response["user_id"] as Int))
        } else {
            Result.failure(AuthException("Login failed"))
        }
    }
}

LegacyAuthAdapterは、古いAPIのためにlogin(credentials)呼び出しをloginWithToken(token)に変換します。クライアント(ViewModel)はAuthServiceインターフェースを通じて動作し、その下にあるのが最新のFirebase Authか10年前のレガシーAPIかを知りません。これにより、クライアントコードを変更せずに実装を置き換えることができます。

AndroidのAdapter:RecyclerViewとListAdapter

RecyclerView.Adapterは、AndroidにおけるAdapterパターンの最も一般的な実装です。データ(オブジェクトのリスト)を、RecyclerViewが画面に表示するViewHolderに変換します。ListAdapter(Android Architecture Components)の登場により、パターンは変更をアニメーション化するための組み込みの差分計算サポートを獲得しました。

Kotlin
class UserAdapter(
    private val onItemClick: (User) -> Unit
) : ListAdapter(DiffCallback()) {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(
        holder: UserViewHolder,
        position: Int
    ) {
        holder.bind(getItem(position), onItemClick)
    }

    class UserViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        
        private val tvName = itemView.findViewById(R.id.tvName)
        
        fun bind(user: User, click: (User) -> Unit) {
            tvName.text = user.name
            itemView.setOnClickListener { click(user) }
        }
    }

    private class DiffCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(old: User, new: User) = old.id == new.id
        override fun areContentsTheSame(old: User, new: User) = old == new
    }
}

UserAdapterは、効率的なリスト再描画のためにDiffUtilとともにListAdapterを使用します。データが更新されると、DiffCallbackが古いリストと新しいリストの差を計算し、RecyclerViewは変更されたアイテムのみをアニメーション化します — リスト全体を再描画することなく。これは、手動のnotifyDataSetChanged()が必要だった古いListViewに対する重要な利点です。

iOSのAdapter:UITableViewとDiffableDataSource

UITableViewDataSourceは、AdapterパターンとDataSourceパターンの両方を兼ねています。モデルデータをテーブルセルに変換します。iOS 13以降、AppleはDiffableDataSourceを導入しました — AndroidのListAdapterと同様に、変更を自動的に計算し、更新をアニメーション化する最新の代替品です。

Swift
class UserTableViewAdapter: NSObject, UITableViewDataSource {
    var items: [User] = []
    
    func tableView(_ tableView: UITableView,
                    numberOfRowsInSection section: Int) -> Int {
        items.count
    }
    
    func tableView(_ tableView: UITableView,
                    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "UserCell", for: indexPath)
        let user = items[indexPath.row]
        cell.textLabel?.text = user.name
        return cell
    }
}

// 最新バージョン:DiffableDataSource
enum Section { case main }

class ModernUserAdapter {
    var dataSource: UITableViewDiffableDataSource<Section, User>!
    
    func configure(for tableView: UITableView) {
        dataSource = UITableViewDiffableDataSource(
            tableView: tableView
        ) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(
                withIdentifier: "UserCell", for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }
    }
    
    func update(with users: [User]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
        snapshot.appendSections([.main])
        snapshot.appendItems(users)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

ModernUserAdapterはDiffableDataSourceを使用して、データが更新されると変更を自動的にアニメーション化します:アイテムがスムーズなアニメーションで表示、非表示、または移動します。iOSのDiffableDataSourceとAndroidのListAdapterは、異なるAPIで同じ問題を解決しますが、同じロジック(差分計算+自動アニメーション)を使用します。

Object Adapter vs Class Adapter

Object Adapterはコンポジションを使用します:アダプターはAdapteeオブジェクトへの参照を持ち、呼び出しを委譲します。Class Adapterは継承を使用します:アダプターはAdapteeを継承し、同時にターゲットインターフェースを実装します。Java、Swift、Kotlinでは、多重継承がないためClass Adapterは不可能です — Object Adapterのみが可能です。

特性Object AdapterClass Adapter
メカニズムコンポジション(Adapteeを含む)継承(Adapteeを拡張)
柔軟性高い — Adapteeを実行時に変更可能低い — Adapteeはコンパイル時に固定
結合度弱い(インターフェース経由)強い(継承経由)
サブクラスの適応はい — Adapteeは任意のサブクラスにできるいいえ — 特定のスーパークラスのみ
利用可能性Java, Kotlin, Swift, C++, C#多重継承のある言語のみ(C++)

モバイル開発では、Object Adapterのみが使用されます。RecyclerView.AdapterはデータとLayoutInflaterへの参照を持ち、UITableViewDataSourceはアイテムの配列を持ちます。コンポジションは継承と比較して、コードをより柔軟でテストしやすくします。

実際のプロジェクトでのAdapterの例

Adapterはリストだけでなく使用されます。互換性のないコンポーネントを統合する問題をパターンが解決する、モバイル開発の実践から3つのシナリオを考えてみましょう。

シナリオAdapteeTargetAdapter
レガシーauthライブラリの統合LegacyAuthLib(コールバックベース)AuthService(suspend)LegacyAuthAdapter
新しいモデルへのJSON適応GsonParserJsonParser(Kotlinx Serialization)GsonAdapter
プッシュサービスの統一インターフェースFCM, APNs, Huawei PushPushService token registerPushServiceAdapter

各Adapterは変換ロジックをカプセル化します:GsonAdapterは@SerializedNameを介してJSON文字列をオブジェクトに変換し、PushServiceAdapterは異なるプラットフォームのトークン登録を抽象化します。クライアント(ビジネスロジック)は統一されたインターフェースで動作し、特定の実装に依存しません。

Adapter実装における一般的な間違い

Adapter実装の間違いは、リストの遅延、メモリリーク、データ更新のバグを引き起こします。3つの一般的な問題を見てみましょう。

onBindViewHolderでの重い処理

onBindViewHolderはスクロール中に各セルに対して呼び出されます。このメソッドでのオブジェクト作成、JSON解析、データベース呼び出しはフレームドロップの原因になります。解決策:すべての重い計算を事前に行い、準備済みのデータをアダプターに渡します。画像にはキャッシュライブラリ(Glide、Coil、Kingfisher)を使用します。

DiffUtil / DiffableDataSourceの無視

更新のたびにnotifyDataSetChanged()を呼び出すと、リスト全体が再描画され、ちらつきや入力フォーカスの喪失を引き起こします。解決策:AndroidではDiffUtilとともにListAdapterを、iOSではDiffableDataSourceを使用します。差分計算は最大1000アイテムのリストで<1ミリ秒かかり、スムーズなアニメーションを保証します。

アダプターでの重いオブジェクトの保存

アダプターはRecyclerView/UITableViewと同じ期間存続します。アダプターにBitmapや大きな配列を保存すると、設定変更(画面回転、テーマ変更)時にメモリリークを引き起こします。解決策:アダプターは軽量なデータモデル(data class / struct)のみを保存し、重いリソースは必要に応じてViewHolderを介してロードする必要があります。

よくある質問

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

AdapterはAdapteeインターフェースをTargetインターフェースに変換します — クライアントは新しい相互作用の方法を得ます。Proxyは元のオブジェクトと同じインターフェースを提供しますが、アクセス制御、キャッシング、または遅延初期化を追加します。Proxyはインターフェースを変更しませんが、Adapterは変更します。

なぜRecyclerView.AdapterがAdapterパターンの例なのですか?

RecyclerView.Adapterはデータ(例:Userのリスト)を、RecyclerViewが表示できるViewHolderに変換します。RecyclerViewはViewHolderを期待し、データはList<User>形式です — Adapterが一方を他方に適応させます。さらに、Adapterはリサイクルプールを通じてViewHolderのライフサイクルを管理し、リストのパフォーマンスを向上させます。

Object AdapterとClass Adapterの違いは何ですか?

Object Adapterはコンポジションを使用します:Adapteeへの参照を持ち、呼び出しを委譲します。Class Adapterは多重継承を使用します:Adapteeを継承し、Targetを実装します。Java、Kotlin、Swiftでは、多重クラス継承がないためObject Adapterのみが利用可能です。Class AdapterはC++でのみ可能です。

Facadeの代わりにAdapterを使用するのはいつですか?

Adapterは、既存の2つのクラスを連携させる必要がある場合に、一方のインターフェースを変換して使用します。Facadeは、シンプルなインターフェースを提供して複雑なサブシステムとの相互作用を簡素化する必要がある場合に使用します。Adapterは互換性のため、Facadeは簡素化のためです。

インターフェースなしでAdapterを実装できますか?

はい、クロージャを使用して可能です。1つのメソッドを持つインターフェースの代わりに関数を渡すことができます。例:Swiftの場合:let adapter = { (data: Data) -> [CellConfig] in /* 変換 */ }。このアプローチは関数型アダプターと呼ばれます。ただし、2つ以上のメソッドの場合、プロトコル/インターフェースが依然として推奨されます — 契約を明示的にします。

まとめ

  • Adapterは、互換性のないAdapteeインターフェースを中間クラスを介してTargetに変換する構造的パターンです
  • RecyclerView.Adapter(Android)とUITableViewDataSource(iOS)は、モバイル開発におけるパターンの主要な例です
  • Object Adapterはコンポジションを使用します。Class Adapter(C++のみ)は多重継承を使用します
  • ListAdapter + DiffUtil(Android)とDiffableDataSource(iOS)は、自動差分とアニメーションを備えた最新の進化版です
  • onBindViewHolderでの重い処理、DiffUtilの無視、アダプターでのリソース保存は一般的な間違いです
  • Adapterは、レガシーコードの統合、形式の適応、異なる実装の統一に使用されます
  • 推奨:手動のリスト更新管理の代わりにListAdapter/DiffableDataSourceを使用してください

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

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

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

こちらもお読みください