Design Pattern in Swift - Factory method

Giới thiệu

Design Patern trong lập trình được hiểu đơn giản là các phương thức khai thác thác thế mạnh của ngôn ngữ giúp lập trình viên tối ưu code, dễ dàng cho việc maintain hoặc expand về sau. Bắt đầu từ số này, tôi sẽ viết một series các design pattern trong Swift, như một cách để tự giúp bản thân học hỏi thêm cũng như share một phần nào đó kinh nghiệm còn non trẻ của mình với những người khác. Và Design Pattern đầu tiên tôi chọn là Factory Method

Nội dung:

Để biết chúng ta dùng một pattern đã phù hợp chưa, chúng cần đáp ứng những yêu cầu sau:

Mục đích:

Factory method được dùng khi có nhiều loại adopt chung một protocol hoặc kế thừa chung một base class, tức là chúng ít nhất có đặc điểm chung nào đó giống nhau, tuy nhiên có những điểm khác biệt, và trong project chúng ta cần detect được chính xác loại nào cần được sử dụng.

Dấu hiệu nhận biết:

Chúng ta biết đã dùng đúng nó khi đạt được mục đích sử dụng ở (1) và đối tượng sử dụng không hề biết về cách thức xử lý logic để detect được đối tượng cần dùng.

Triển khai:

Để hiểu rõ hơn về factory method, chúng ta sẽ cùng thực hiện một ví dụ demo sau:

Nội dung yêu cầu:

Giả sử chúng ta đang làm một ứng dung nấu ăn, với nhiều món ăn khác nhau được thực hiện. Để đơn giản hoá mô hình, chúng ta giả sử chỉ có 5 loại món ăn là món cho người già(Old), món cho thanh niên(Young), món cho trẻ em(Baby), và món cho đàn ông trẻ(YoungMan), món cho phụ nữ trẻ(YoungWoman), cả 5 loại trên đều là sub class của loại Food. Vấn đề là mỗi loại có một mức calo khác nhau và nhiệm vụ của chúng ta là phải chọn ra những món ăn phù hợp dựa vào chỉ số calo cho trước.

class Food {
    private var calo: Float?
    private var feContent: Float?
    
    init(calo: Float, feContent: Float) {
        self.calo = calo
        self.feContent = feContent
    }
}

class Old: Food {
    init () {
        super.init(calo: 250.0, feContent: 1)
    }
}

class Young: Food {
    init() {
        super.init(calo: 500.0, feContent: 2)
    }
}

class Baby: Food {
    init() {
        super.init(calo: 750.0, feContent: 3)
    }
}

Thực hiện:

Chúng ta xây dựng hàm xử lý logic để detec ra loại đồ ăn phù hợp cho từng độ tuổi tại base class Food như sau:

class Food {
    private var calo: Float?
    private var feContent: Float?
    
    init(calo: Float, feContent: Float) {
        self.calo = calo
        self.feContent = feContent
    }
    
    class func getFoodWithCalo(calo: Float) -> Food? {
        switch calo {
        case 0..<250:
            return Baby()
        case 250..<500:
            return Old()
        case 500..<1000:
            return Young()
        default:
            return nil
        }
    }
}

Sau đó chúng ta gọi:

let food = Food.getFoodWithCalo(500)
print(food!.classForCoder)

Output:
"Young"

Phân tích

Như vậy chúng ta đã xây dựng được một factory pattern với yêu cầu khá đơn giản như trên. Tất cả phần thực hiện logic để detect ra được loại món ăn được đóng gói trong base class Food, thành phần gọi nó không hề biết về logic trên, chúng đơn giản chỉ yêu cầu Food class chỉ ra loại món ăn phù hợp với hàm lượng calo là 500, và kết quả trả về là với hàm lượng calo này thì món ăn dành cho người trẻ(Young)

Tuy nhiên, nếu bài toán mở rộng với 2 loại nữa là YoungMan và YoungWoman, là những subClass của loại Young thì chúng ta sẽ cần xử lý sâu hơn như sau:

class Young: Food {
    convenience init() {
        self.init(calo: 500, feContent: 2)
    }
    
    override init(calo: Float, feContent: Float) {
        super.init(calo: calo, feContent: feContent)
    }
    
    override class func getFoodWithCalo(calo: Float) -> Food? {
        if calo < 750.0 {
            return YoungWoman()
        } else {
            return YoungMan()
        }
    }
    
}

class YoungMan: Young {
    init() {
        super.init(calo: 900, feContent: 2)
    }
}

class YoungWoman: Young {
    init() {
        super.init(calo: 600, feContent: 2)
    }
}

Lúc này, khi nhập calo bằng 700, chúng ta có kết quả trả về cụ thể hơn, thay vì là Young như trước:

let food = Food.getFoodWithCalo(700)
print(food!.classForCoder)

Output:
YoungWoman

Kết luận

Với những phân tích và ví dụ trên, tôi đã giới thiệu xong về factory method, một trong những design pattern đơn giản nhưng rất phổ biến trong Swift. Hy vọng bài viết này sẽ giúp các bạn có cái nhìn sâu và hứng thú trong việc tìm và áp dụng design pattern một cách chính xác trong các dự án của mình. Chúc các bạn thành công!