Adapter একটি স্ট্রাকচারাল ডিজাইন প্যাটার্ন যা একটি ক্লাসের ইন্টারফেসকে অন্য ইন্টারফেসে রূপান্তর করে যা ক্লায়েন্ট আশা করে। মোবাইল ডেভেলপমেন্টে, Adapter সবচেয়ে বেশি ব্যবহৃত হয় Android-এ RecyclerView.Adapter এবং iOS-এ UITableViewDataSource হিসেবে। Google I/O (2024) অনুসারে, Android অ্যাপের 95% ক্ষেত্রে তালিকা প্রদর্শনের জন্য RecyclerView ব্যবহৃত হয়, এবং প্রতিটির নিজস্ব Adapter বাস্তবায়ন প্রয়োজন।
মূল পয়েন্ট
Adapter একটি স্ট্রাকচারাল প্যাটার্ন যা ইন্টারফেস অসামঞ্জস্যের সমস্যা সমাধান করে। এটি একটি অবজেক্ট (Adaptee) কে একটি ক্লাসে (Adapter) মোড়ায় যা ক্লায়েন্টের প্রত্যাশিত ইন্টারফেস (Target) ধারণ করে। ক্লায়েন্ট Target-এর সাথে কাজ করে এবং Adaptee-এর অস্তিত্ব সম্পর্কে জানে না। এটি Wrapper প্যাটার্নের একটি প্রকরণ।
// অসামঞ্জস্যপূর্ণ ইন্টারফেস সহ বিদ্যমান ক্লাস
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 নাকি এক দশক পুরনো লিগেসি API রয়েছে। এটি ক্লায়েন্ট কোড পরিবর্তন না করেই বাস্তবায়ন প্রতিস্থাপনের অনুমতি দেয়।
RecyclerView.Adapter Android-এ Adapter প্যাটার্নের সবচেয়ে সাধারণ বাস্তবায়ন। এটি ডেটা (অবজেক্টের তালিকা) কে ViewHolders-এ রূপান্তর করে যা RecyclerView স্ক্রিনে প্রদর্শন করে। ListAdapter (Android Architecture Components) আসার সাথে সাথে, প্যাটার্নটি পরিবর্তন অ্যানিমেট করার জন্য বিল্ট-ইন ডিফ গণনা সমর্থন অর্জন করে।
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 শুধুমাত্র পরিবর্তিত আইটেমগুলিকে অ্যানিমেট করে — পুরো তালিকা পুনরায় অঙ্কন না করে। এটি পুরনো ListView-এর তুলনায় একটি মূল সুবিধা, যার জন্য ম্যানুয়াল notifyDataSetChanged() প্রয়োজন হত।
UITableViewDataSource একই সাথে Adapter প্যাটার্ন এবং DataSource প্যাটার্ন উভয়ই। এটি মডেল ডেটাকে টেবিল সেলে রূপান্তর করে। iOS 13 থেকে, Apple DiffableDataSource চালু করেছে — একটি আধুনিক প্রতিস্থাপন, যা Android-এ ListAdapter-এর মতো, স্বয়ংক্রিয়ভাবে পরিবর্তন গণনা করে এবং আপডেট অ্যানিমেট করে।
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 কম্পোজিশন ব্যবহার করে: অ্যাডাপ্টারে Adaptee অবজেক্টের রেফারেন্স থাকে এবং কলগুলি ডেলিগেট করে। Class Adapter ইনহেরিটেন্স ব্যবহার করে: অ্যাডাপ্টার Adaptee থেকে ইনহেরিট করে এবং একই সাথে টার্গেট ইন্টারফেস প্রয়োগ করে। Java, Swift এবং Kotlin-এ, মাল্টিপল ইনহেরিটেন্সের অভাবে Class Adapter অসম্ভব — শুধুমাত্র Object Adapter সম্ভব।
| বৈশিষ্ট্য | Object Adapter | Class Adapter |
|---|---|---|
| পদ্ধতি | কম্পোজিশন (Adaptee ধারণ করে) | ইনহেরিটেন্স (Adaptee-কে বাড়ায়) |
| নমনীয়তা | উচ্চ — Adaptee রানটাইমে পরিবর্তন করা যায় | নিম্ন — Adaptee কম্পাইল টাইমে স্থির |
| সংযুক্তি | দুর্বল (ইন্টারফেসের মাধ্যমে) | শক্তিশালী (ইনহেরিটেন্সের মাধ্যমে) |
| উপশ্রেণি অভিযোজন | হ্যাঁ — Adaptee যেকোনো উপশ্রেণি হতে পারে | না — শুধুমাত্র নির্দিষ্ট সুপারক্লাস |
| উপলব্ধতা | Java, Kotlin, Swift, C++, C# | শুধুমাত্র মাল্টিপল ইনহেরিটেন্সযুক্ত ভাষা (C++) |
মোবাইল ডেভেলপমেন্টে, শুধুমাত্র Object Adapter ব্যবহৃত হয়। RecyclerView.Adapter-এ ডেটা এবং LayoutInflater-এর রেফারেন্স থাকে, UITableViewDataSource-এ আইটেমের একটি অ্যারে থাকে। কম্পোজিশন ইনহেরিটেন্সের তুলনায় কোডকে আরও নমনীয় এবং পরীক্ষাযোগ্য করে তোলে।
Adapter শুধুমাত্র তালিকার জন্যই ব্যবহৃত হয় না। আসুন মোবাইল ডেভেলপমেন্ট অনুশীলন থেকে তিনটি পরিস্থিতি বিবেচনা করি যেখানে প্যাটার্নটি অসামঞ্জস্যপূর্ণ উপাদানগুলিকে একীভূত করার সমস্যা সমাধান করে।
| পরিস্থিতি | Adaptee | Target | Adapter |
|---|---|---|---|
| পুরনো auth লাইব্রেরি একীভূতকরণ | LegacyAuthLib (কলব্যাক-ভিত্তিক) | AuthService (suspend) | LegacyAuthAdapter |
| নতুন মডেলের জন্য JSON অভিযোজন | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| পুশ পরিষেবার জন্য ইউনিফাইড ইন্টারফেস | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
প্রতিটি Adapter রূপান্তর যুক্তি Encapsulate করে: GsonAdapter @SerializedName-এর মাধ্যমে JSON স্ট্রিংকে অবজেক্টে অনুবাদ করে, PushServiceAdapter বিভিন্ন প্ল্যাটফর্মের জন্য টোকেন নিবন্ধনকে অ্যাবস্ট্রাক্ট করে। ক্লায়েন্ট (ব্যবসায়িক যুক্তি) একটি ইউনিফাইড ইন্টারফেসের সাথে কাজ করে এবং নির্দিষ্ট বাস্তবায়নের উপর নির্ভর করে না।
Adapter বাস্তবায়নে ভুল তালিকার ধীরগতি, মেমোরি লিক এবং ডেটা আপডেটে বাগ সৃষ্টি করে। আসুন তিনটি সাধারণ সমস্যা দেখি।
স্ক্রল করার সময় প্রতিটি সেলের জন্য onBindViewHolder কল করা হয়। এই পদ্ধতিতে অবজেক্ট তৈরি, JSON পার্স বা ডেটাবেস কল ফ্রেম ড্রপের কারণ হয়। সমাধান: আগে থেকেই সমস্ত ভারী গণনা সম্পাদন করুন, অ্যাডাপ্টারে প্রস্তুত ডেটা পাস করুন। ছবির জন্য ক্যাশিং লাইব্রেরি ব্যবহার করুন — Glide, Coil, Kingfisher।
প্রতিটি আপডেটে notifyDataSetChanged() কল করা পুরো তালিকা পুনরায় আঁকে, যার ফলে ঝিকিমিকি এবং ইনপুটে ফোকাস হারানো হয়। সমাধান: Android-এ DiffUtil-এর সাথে ListAdapter বা iOS-এ DiffableDataSource ব্যবহার করুন। ডিফ গণনা 1000 আইটেম পর্যন্ত তালিকার জন্য <1 মি.সে. সময় নেয় এবং মসৃণ অ্যানিমেশন নিশ্চিত করে।
অ্যাডাপ্টার ততদিন বেঁচে থাকে যতদিন RecyclerView/UITableView। অ্যাডাপ্টারে Bitmap বা বড় অ্যারে সংরক্ষণ কনফিগারেশন পরিবর্তনে (স্ক্রিন রোটেশন, থিম পরিবর্তন) মেমোরি লিকের কারণ হয়। সমাধান: অ্যাডাপ্টারে শুধুমাত্র হালকা ডেটা মডেল (data class / struct) সংরক্ষণ করা উচিত, এবং প্রয়োজন অনুসারে ViewHolder-এর মাধ্যমে ভারী সংস্থান লোড করা উচিত।
সচরাচর জিজ্ঞাসিত প্রশ্ন
Adapter Adaptee ইন্টারফেসকে Target ইন্টারফেসে রূপান্তর করে — ক্লায়েন্ট মিথস্ক্রিয়ার একটি নতুন উপায় পায়। Proxy মূল অবজেক্টের মতো একই ইন্টারফেস প্রদান করে, কিন্তু অ্যাক্সেস নিয়ন্ত্রণ, ক্যাশিং বা অলস আরম্ভকরণ যোগ করে। Proxy ইন্টারফেস পরিবর্তন করে না, Adapter পরিবর্তন করে।
RecyclerView.Adapter ডেটা (যেমন, User-এর তালিকা) কে ViewHolder-এ রূপান্তর করে যা RecyclerView প্রদর্শন করতে পারে। RecyclerView ViewHolder আশা করে, ডেটা List<User> ফরম্যাটে — Adapter একটিকে অন্যটির সাথে খাপ খাইয়ে নেয়। অতিরিক্তভাবে, Adapter রিসাইকেল পুলের মাধ্যমে ViewHolder জীবনচক্র পরিচালনা করে, তালিকার কর্মক্ষমতা উন্নত করে।
Object Adapter কম্পোজিশন ব্যবহার করে: এতে Adaptee-এর রেফারেন্স থাকে এবং কলগুলি ডেলিগেট করে। Class Adapter মাল্টিপল ইনহেরিটেন্স ব্যবহার করে: এটি Adaptee থেকে ইনহেরিট করে এবং Target প্রয়োগ করে। Java, Kotlin এবং Swift-এ, মাল্টিপল ক্লাস ইনহেরিটেন্সের অভাবে শুধুমাত্র Object Adapter উপলব্ধ। Class Adapter শুধুমাত্র C++-এ সম্ভব।
Adapter ব্যবহার করা হয় যখন আপনাকে দুটি বিদ্যমান ক্লাসকে একসাথে কাজ করাতে হবে, তাদের একটির ইন্টারফেস রূপান্তর করে। Facade ব্যবহার করা হয় যখন আপনাকে একটি সরল ইন্টারফেস প্রদান করে একটি জটিল উপতন্ত্রের সাথে মিথস্ক্রিয়া সহজ করতে হবে। Adapter সামঞ্জস্যের জন্য, Facade সরলীকরণের জন্য।
হ্যাঁ, ক্লোজার ব্যবহার করে। একটি পদ্ধতি বিশিষ্ট ইন্টারফেসের পরিবর্তে, একটি ফাংশন পাস করা যেতে পারে। উদাহরণস্বরূপ, Swift-এ: let adapter = { (data: Data) -> [CellConfig] in /* রূপান্তর */ }। এই পদ্ধতিকে ফাংশনাল অ্যাডাপ্টার বলা হয়। তবে, 2+ পদ্ধতির জন্য, প্রোটোকল/ইন্টারফেস পছন্দনীয় — এটি চুক্তিকে স্পষ্ট করে।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন