Adapter — nó là gì, mẫu chuyển đổi giao diện cho phát triển di động

Tác giả: IT Sectr Đã đăng: 2026-02-18 Thời gian đọc: 10 phút

Adapter là một mẫu thiết kế cấu trúc chuyển đổi giao diện của một lớp thành giao diện khác mà client mong đợi. Trong phát triển di động, Adapter được sử dụng phổ biến nhất trong RecyclerView.Adapter cho Android và UITableViewDataSource cho iOS. Theo Google I/O (2024), RecyclerView được sử dụng trong 95% ứng dụng Android để hiển thị danh sách và mỗi ứng dụng đều yêu cầu triển khai Adapter riêng.

Những điểm chính

  • Adapter là một mẫu cấu trúc cho phép các đối tượng có giao diện không tương thích hoạt động cùng nhau
  • RecyclerView.Adapter là một triển khai của mẫu trong Android, chuyển đổi dữ liệu thành ViewHolder cho danh sách
  • UITableViewDataSource là một triển khai trong iOS, kết hợp các mẫu Adapter và DataSource
  • Object Adapter sử dụng thành phần: adapter chứa tham chiếu đến đối tượng được điều chỉnh
  • Class Adapter sử dụng đa kế thừa — chỉ khả dụng trong C++, không có trong Java/Swift/Kotlin

Mẫu Adapter là gì?

Adapter là một mẫu cấu trúc giải quyết vấn đề không tương thích giao diện. Nó bọc một đối tượng (Adaptee) vào một lớp (Adapter) với giao diện mà client mong đợi (Target). Client làm việc với Target mà không biết về sự tồn tại của Adaptee. Đây là một biến thể của mẫu Wrapper.

Kotlin
// Lớp hiện có với giao diện không tương thích
class LegacyAuthApi {
    fun loginWithToken(token: String): Map {
        return mapOf("status" to "ok", "user_id" to 42)
    }
}

// Giao diện mục tiêu (client mong đợi)
interface AuthService {
    suspend fun login(credentials: Credentials): Result
}

// Adapter — chuyển đổi LegacyAuthApi thành 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 chuyển đổi lời gọi login(credentials) thành loginWithToken(token) cho API cũ. Client (ViewModel) làm việc thông qua giao diện AuthService và không biết bên dưới là Firebase Auth mới nhất hay API cũ hàng thập kỷ. Điều này cho phép thay thế các triển khai mà không cần thay đổi mã client.

Adapter trong Android: RecyclerView và ListAdapter

RecyclerView.Adapter là triển khai phổ biến nhất của mẫu Adapter trong Android. Nó chuyển đổi dữ liệu (một danh sách đối tượng) thành ViewHolder mà RecyclerView hiển thị trên màn hình. Với sự ra đời của ListAdapter (Android Architecture Components), mẫu này đã có được hỗ trợ tính toán khác biệt tích hợp để tạo hiệu ứng cho các thay đổi.

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 sử dụng ListAdapter với DiffUtil để vẽ lại danh sách hiệu quả. Khi dữ liệu được cập nhật, DiffCallback tính toán sự khác biệt giữa danh sách cũ và mới, và RecyclerView chỉ tạo hiệu ứng cho các mục đã thay đổi — mà không vẽ lại toàn bộ danh sách. Đây là một lợi thế chính so với ListView cũ, vốn yêu cầu notifyDataSetChanged() thủ công.

Adapter trong iOS: UITableView và DiffableDataSource

UITableViewDataSource đồng thời là mẫu Adapter và mẫu DataSource. Nó chuyển đổi dữ liệu mô hình thành các ô của bảng. Từ iOS 13, Apple đã giới thiệu DiffableDataSource — một sự thay thế hiện đại, giống như ListAdapter trong Android, tự động tính toán các thay đổi và tạo hiệu ứng cho các bản cập nhật.

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
    }
}

// Phiên bản hiện đại: 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 với DiffableDataSource tự động tạo hiệu ứng cho các thay đổi khi dữ liệu được cập nhật: các mục xuất hiện, biến mất hoặc di chuyển với hiệu ứng mượt mà. DiffableDataSource của iOS và ListAdapter của Android giải quyết cùng một vấn đề với các API khác nhau nhưng cùng một logic — tính toán khác biệt + hiệu ứng tự động.

Object Adapter vs Class Adapter

Object Adapter sử dụng thành phần: adapter chứa tham chiếu đến đối tượng Adaptee và ủy quyền các lời gọi cho nó. Class Adapter sử dụng kế thừa: adapter kế thừa Adaptee và đồng thời triển khai giao diện mục tiêu. Trong Java, Swift và Kotlin, Class Adapter không khả thi do thiếu đa kế thừa — chỉ còn Object Adapter.

Đặc điểmObject AdapterClass Adapter
Cơ chếThành phần (chứa Adaptee)Kế thừa (mở rộng Adaptee)
Linh hoạtCao hơn — Adaptee có thể thay đổi tại thời gian chạyThấp hơn — Adaptee cố định tại thời gian biên dịch
Liên kếtLỏng lẻo (qua giao diện)Chặt chẽ (qua kế thừa)
Thích ứng lớp conCó — Adaptee có thể là bất kỳ lớp con nàoKhông — chỉ một siêu lớp cụ thể
Khả dụngJava, Kotlin, Swift, C++, C#Chỉ ngôn ngữ có đa kế thừa (C++)

Trong phát triển di động, chỉ Object Adapter được sử dụng. RecyclerView.Adapter chứa tham chiếu đến dữ liệu và LayoutInflater, UITableViewDataSource chứa một mảng các mục. Thành phần làm cho mã linh hoạt và dễ kiểm tra hơn so với kế thừa.

Ví dụ Adapter trong các dự án thực tế

Adapter không chỉ được sử dụng cho danh sách. Hãy xem xét ba kịch bản từ thực tiễn phát triển di động nơi mẫu giải quyết vấn đề tích hợp các thành phần không tương thích.

Kịch bảnAdapteeTargetAdapter
Tích hợp thư viện xác thực cũLegacyAuthLib (dựa trên callback)AuthService (suspend)LegacyAuthAdapter
Thích ứng JSON với mô hình mớiGsonParserJsonParser (Kotlinx Serialization)GsonAdapter
Giao diện thống nhất cho dịch vụ pushFCM, APNs, Huawei PushPushService token registerPushServiceAdapter

Mỗi Adapter đóng gói logic chuyển đổi: GsonAdapter dịch chuỗi JSON thành đối tượng qua @SerializedName, PushServiceAdapter trừu tượng hóa việc đăng ký token cho các nền tảng khác nhau. Client (logic nghiệp vụ) làm việc với một giao diện thống nhất và không phụ thuộc vào triển khai cụ thể.

Lỗi thường gặp khi triển khai Adapter

Lỗi trong triển khai Adapter dẫn đến danh sách chậm, rò rỉ bộ nhớ và lỗi cập nhật dữ liệu. Hãy xem xét ba vấn đề phổ biến.

Thao tác nặng trong onBindViewHolder

onBindViewHolder được gọi cho mỗi ô trong khi cuộn. Tạo đối tượng, phân tích JSON hoặc gọi cơ sở dữ liệu trong phương thức này dẫn đến giảm khung hình (frame drops). Giải pháp: thực hiện tất cả các tính toán nặng trước, truyền dữ liệu đã sẵn sàng vào adapter. Sử dụng thư viện bộ nhớ đệm cho hình ảnh — Glide, Coil, Kingfisher.

Bỏ qua DiffUtil / DiffableDataSource

Gọi notifyDataSetChanged() mỗi khi cập nhật sẽ vẽ lại toàn bộ danh sách, gây nhấp nháy và mất tiêu điểm trên các đầu vào. Giải pháp: sử dụng ListAdapter với DiffUtil trong Android hoặc DiffableDataSource trong iOS. Tính toán khác biệt mất <1 ms cho danh sách lên đến 1000 mục và đảm bảo hiệu ứng mượt mà.

Lưu trữ đối tượng nặng trong adapter

Adapter tồn tại lâu bằng RecyclerView/UITableView. Lưu trữ Bitmap hoặc mảng lớn trong adapter dẫn đến rò rỉ bộ nhớ khi thay đổi cấu hình (xoay màn hình, thay đổi chủ đề). Giải pháp: adapter chỉ nên lưu trữ các mô hình dữ liệu nhẹ (data class / struct) và tải tài nguyên nặng qua ViewHolder khi cần.

Câu hỏi thường gặp

Sự khác biệt giữa Adapter và Proxy là gì?

Adapter chuyển đổi giao diện Adaptee thành giao diện Target — client nhận được một cách tương tác mới. Proxy cung cấp cùng giao diện với đối tượng gốc, nhưng thêm kiểm soát truy cập, bộ nhớ đệm hoặc khởi tạo chậm. Proxy không thay đổi giao diện, Adapter thì có.

Tại sao RecyclerView.Adapter là một ví dụ của mẫu Adapter?

RecyclerView.Adapter chuyển đổi dữ liệu (ví dụ: danh sách User) thành ViewHolder mà RecyclerView có thể hiển thị. RecyclerView mong đợi ViewHolder, dữ liệu ở định dạng List<User> — Adapter điều chỉnh cái này cho cái kia. Ngoài ra, Adapter quản lý vòng đời của ViewHolder thông qua bể tái chế, cải thiện hiệu suất danh sách.

Sự khác biệt giữa Object Adapter và Class Adapter là gì?

Object Adapter sử dụng thành phần: nó chứa tham chiếu đến Adaptee và ủy quyền các lời gọi. Class Adapter sử dụng đa kế thừa: nó kế thừa Adaptee và triển khai Target. Trong Java, Kotlin và Swift, chỉ Object Adapter khả dụng do thiếu đa kế thừa lớp. Class Adapter chỉ khả thi trong C++.

Khi nào sử dụng Adapter thay vì Facade?

Adapter được sử dụng khi bạn cần làm cho hai lớp hiện có hoạt động cùng nhau bằng cách chuyển đổi giao diện của một trong số chúng. Facade được sử dụng khi bạn cần đơn giản hóa tương tác với một hệ thống con phức tạp bằng cách cung cấp một giao diện đơn giản. Adapter dành cho tương thích, Facade dành cho đơn giản hóa.

Có thể triển khai Adapter mà không cần giao diện không?

Có, sử dụng bao đóng. Thay vì một giao diện với một phương thức, bạn có thể truyền một hàm. Ví dụ, trong Swift: let adapter = { (data: Data) -> [CellConfig] in /* chuyển đổi */ }. Cách tiếp cận này được gọi là adapter chức năng. Tuy nhiên, cho 2+ phương thức, giao thức/giao diện vẫn được ưu tiên — nó làm cho hợp đồng rõ ràng.

Tổng kết

  • Adapter là một mẫu cấu trúc chuyển đổi giao diện Adaptee không tương thích thành Target thông qua một lớp trung gian
  • RecyclerView.Adapter (Android) và UITableViewDataSource (iOS) là các ví dụ chính của mẫu trong phát triển di động
  • Object Adapter sử dụng thành phần; Class Adapter (chỉ C++) sử dụng đa kế thừa
  • ListAdapter + DiffUtil (Android) và DiffableDataSource (iOS) là các phiên bản tiến hóa hiện đại với tính toán khác biệt tự động và hiệu ứng
  • Thao tác nặng trong onBindViewHolder, bỏ qua DiffUtil và lưu trữ tài nguyên trong adapter là những lỗi phổ biến
  • Adapter được sử dụng để tích hợp mã cũ, thích ứng định dạng và thống nhất các triển khai khác nhau
  • Khuyến nghị: sử dụng ListAdapter/DiffableDataSource thay vì quản lý cập nhật danh sách thủ công

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm