Học Design Pattern: Abstract Factory Pattern
Đặt vấn đề
Trong phát triển phần mềm, chúng ta sẽ gặp tình huống cần làm việc với các nhóm đối tượng có liên quan.
Tưởng tượng rằng team bạn cần phát triển một tính năng trên ứng dụng di động dùng để hiển thị nhiều Component khác nhau trên 1 màn hình, các Component này được hiển thị theo thứ tự và số lượng bất kỳ, đồng thời các Component này còn thuộc nhiều Team phát triển khác nhau.
Thách thức ở đây là tạo ra các đối tượng Component một cách linh hoạt, tránh hard code và đảm bảo tính tương thích, mở rộng cao (high scalability) khi biz logic thay đổi. Đó là lúc Abstract Factory Pattern được đưa vào sử dụng.
Code Không Dùng Abstract Factory Pattern
class TitleComponent(val text: String) {
fun createView(parent: ViewGroup): View {
// dump return a view
}
}
class ImageComponent(val imageResId: Int) {
fun createView(parent: ViewGroup): View {
// dump return view
}
}
class TextComponent(val text: String) {
fun createView(parent: ViewGroup): View {
// dump return view
}
}
class ComponentAdapter(private val components: List<Any>) :
RecyclerView.Adapter<ComponentAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
return when(components[position]) {
is Title -> TitleComponent(components[position])
is Image -> ImageComponent(components[position])
is Text -> TextComponent(components[position])
else -> View()
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(components[position])
}
override fun getItemCount() = components.size
}
fun main() {
val data = listOf(
TitleComponent("Welcome"),
ImageComponent(R.drawable.image1),
TextComponent("This is some text."),
ImageComponent(R.drawable.image2),
TitleComponent("Another Section"),
TextComponent("More details here.")
)
val parentView = RecyclerView()
val adapter = ComponentAdapter(data)
parentView.adapter = adapter
}
Với cách dùng trên ta có thể thấy mỗi lần chạy qua onCreateViewHolder sẽ phải trigger một hàm constructor một cách thủ công. Việc này về Logic không sai, nhưng khi logic nhiều lên cũng như việc nhúng tay của nhiều team lên chung 1 bộ source sẽ vô hình chung làm cho mã nguồn của chúng ta rối ren, khó kiểm soát được trách nhiệm của mỗi người. Giải pháp là áp dùng Abstract Factory Pattern.
Code Dùng Abstract Factory Pattern
interface TextComponentFactory {
fun createTitleComponent(data: Any): ViewHolder
fun createTextComponent(data: Any): ViewHolder
}
inteface ImageComponentFactory {
fun createImageComponent(data: Any): ViewHolder
}
class ComponentAdapter(
private val components: List<Any>,
private val textComponentFactory: TextComponentFactory,
private val imageComponentFactory: ImageComponentFactory
) :
RecyclerView.Adapter<ComponentAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
return when(components[position]) {
is Title -> textComponentFactory.createTitleComponent(components[position])
is Image -> imageComponentFactory.createImageComponent(components[position])
is Text -> textComponentFactory.createTextComponent(components[position])
else -> View()
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(components[position])
}
override fun getItemCount() = components.size
}
Với cách áp dụng trên, chúng ta đã đưa sự lệ thuộc của Adapter vào các Component trở nên abstract (ảo hóa) nó vừa đảm bảo được DIP (Dependency Inversion Principle), vừa có thể áp dụng một cách điệu ghệ Abstract Factory Pattern. Quả đúng là võ công trong thiện hạ đều có sự liên quan mật thiết đến nhau. Khi thuần thục một chiêu, ta có thể dễ dàng lĩnh ngộ nội công của nhiều môn phái khác 🙂
Bài Học Từ Abstract Factory Pattern
Khi tiếp nhận 1 source code hoặc bắt đầu phát triển 1 tính năng bạn hãy nghĩ đến việc tính năng đó, mã nguồn đó đang bị phụ thuộc lên bao nhiêu module vệ tinh khác, bao nhiêu lập trình viên khác. Và tìm mọi cách make it abstract – làm ảo hóa các dependencies của module bạn đang phát triển. Đó chính là mấu chốt của việc ứng dụng Abstract Factory Pattern.
Chúc bạn thành công!