Design Pattern: Builder Pattern trong iOS

by TheLN
128 views

Design Pattern: Builder pattern trong iOS

Nghe đến Design Pattern, chắc hẳn mỗi lập trình viên đều biết đến kỹ thuật quan trọng này và đã từng áp dụng nó ít nhất một lần. Design pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình hướng đối tượng (OOP). Phần lớn các ngôn ngữ lập trình đều có thể áp dụng Design pattern và Swift cũng không ngoại lệ!

Design pattern có 3 nhóm chính:

  • Creational Pattern bao gồm: Abstract Factory, Factory Method, Singleton, Builder, Prototype.
  • Structural Pattern bao gồm: Adapter, Bridge, Composite, Decorator, Facade, Proxy và Flyweight.
  • Behavioral Pattern bao gồm: Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy và Visitor.

Hôm nay, chúng ta sẽ cùng tìm hiểu một design pattern trong nhóm Creational Pattern (Nhóm khởi tạo) là Builder pattern.

Nội dung

  • Builder pattern là gì?
  • Vấn đề
  • Sử dụng nó như thế nào
  • Áp dụng trong iOS
  • Tổng kết

Builder pattern là gì?

Trong OOP, Builder pattern là một loại pattern thuộc nhóm Creational pattern (Tạo dựng), vì vậy nó sẽ giúp chúng ta giải quyết các vấn đề liên quan tới tạo dựng một object bằng cách:

  • Chia nhỏ các hàm khởi tạo của một object.
  • Đặt cấu hình mặc định và logic xử lý liên quan tới việc khởi tạo một object từ trong class ra bên ngoài và đẩy vào Builder class.

Và việc chia nhỏ và đẩy vào Builder class như thế nào thì chúng ta hãy cùng tìm hiểu tiếp về vấn đề và áp dụng Builder Pattern trong một bài toán.

Vấn đề

Giả sử, chúng ta có một chương trình đặt bánh pizza, và mục tiêu của chúng ta là tạo ra chương trình đặt bánh để lấy yêu cầu từ khách hàng.

Chúng ta có đối tượng Pizza, vả khởi tạo các đối tượng Pizza:


class Pizza {
    
    var smell: String
    var base: String
    var size: String
    var isChiliSauce: Bool
    var isKetchup: Bool
    
    init(smell: String, base: String, size: String, isChiliSauce: Bool, isKetchup: Bool) {
        self.smell = smell
        self.base = base
        self.size = size
        self.isChiliSauce = isChiliSauce
        self.isKetchup = isKetchup
    }
}


let sizeM = Pizza(smell: "Bò", base: "Mỏng", size: "M", isChiliSauce: true, isKetchup: true)

let sizeMChicken = Pizza(smell: "Gà", base: "Mỏng", size: "M", isChiliSauce: true, isKetchup: true)

let sizeL = Pizza(smell: "Gà", base: "Dày", size: "L", isChiliSauce: true, isKetchup: true)

Chúng ta có thể thấy hàm tạo khá nhiều thuộc tính, và có những thuộc tính mặc định ít thay đổi. Vì những thuộc tính đó ít thay đổi nên sẽ không tránh khỏi việc duplicate code. Do đó, người ta sử dụng Builder Pattern.

Sử dụng nó như thế nào

Class diagram

Cùng phân tích một chút nhé:

  • Director: Đại diện cho module cần kết quả từ việc khởi tạo Pizza.
  • PizzaBuilder: Một class implement InterfaceBuilder, có nhiệm vụ khởi tạo ra Pizza, thông qua hàm build().
  • Pizza: Là object cần khởi tạo.

Demo

Một protocol nhằm khai báo các phương thức


protocol PizzaBuilderProtocol {
    
    func build() -> Pizza
    
    func setSmell(_ smell: String) -> PizzaBuilder

    func setBase(_ base: String) -> PizzaBuilder

    func setSize(_ size: String) -> PizzaBuilder
    
    func withChiliSauce(_ isChiliSauce: Bool) -> PizzaBuilder
    
    func withKetchup(_ isKetchup: Bool) -> PizzaBuilder
}

PizzaBuilder triển khai các phương thức đã khai báo ở PizzaBuilderProtocol


class PizzaBuilder: PizzaBuilderProtocol {
    
    private var pizza = Pizza()
    
    func build() -> Pizza {
        return self.pizza
    }
    
    func setSmell(_ smell: String) -> PizzaBuilder {
        pizza.smell = smell
        return self
    }
    
    func setBase(_ base: String) -> PizzaBuilder {
        pizza.base = base
        return self
    }
    
    func setSize(_ size: String) -> PizzaBuilder {
        pizza.size = size
        return self
    }
    
    func withChiliSauce(_ isChiliSauce: Bool) -> PizzaBuilder {
        pizza.isChiliSauce = isChiliSauce
        return self
    }
    
    func withKetchup(_ isKetchup: Bool) -> PizzaBuilder {
        pizza.isKetchup = isKetchup
        return self
    }
    
}

Cart ở đây giữ vai trò tương đương Director trong hình bên trên.


class Cart {
    
    let orderId: String
    let products: [Pizza]
    
    init(orderId: String, products: [Pizza]) {
        self.orderId = orderId
        self.products = products
    }
}

let pizza1 = PizzaBuilder()
            .setSize("s")
            .setSmell("Gà")
            .setBase("Dày")
            .build()
        
let cart1 = Cart(orderId: "OrderId1", products: [pizza1])

Cách thực hiện thật đơn giản phải không? Bản chất của nó là chia nhỏ các properties trong Object ra thành các hàm getter, setter trong Builder và sau đó thực hiện set giá trị cho chúng và khởi tạo Object qua hàm build(). Và đó chính là cách viết theo Builder Pattern.

Chắc hẳn khi các bạn đọc đến đây sẽ có một suy nghĩ rằng: Vậy tại sao không dùng default value? Đúng vậy, khi Builder Pattern được giới thiệu, ngôn ngữ họ sử dụng khi đó là C++. Nhưng, Swift của chúng ta đã cung cấp tính năng default value, và chúng ta có thể hoàn toàn loại bỏ Builder. Nói như vậy thì Builder Pattern sẽ trở nên vô dụng trong Swift?

Chắc chắn là không. Những tài liệu trên mạng về Builder Pattern khá nhiều, và nó đang hướng người đọc vào vấn đề default value trong các ngôn ngữ như: C++, Java…Chúng ta hãy cùng nhìn lại về những thứ mà Builder làm, đó là giảm thiệu sự phức tạp khi khởi tạo thông qua hàm builder(). Bản chất chính là nó đang đóng gói việc khởi tạo một Object. Khi đó, chúng ta sẽ có thể xử lý nhiều hiện object, do something… trước khi trả về một Object hoàn chỉnh.

Áp dụng trong iOS

Chúng ta có thể áp dụng Builder Pattern ở rất nhiều trường hợp trong lập trình iOS. Mình, mình áp dụng trong việc customize một style cho Label, Button…

Thường khi, chúng ta customize một Label theo một style nào đó, chúng ta sẽ thường khởi tạo như thế này, nhìn có vẻ khá rối và không được thú vị cho lắm.


let attrString = NSMutableAttributedString(string: title)
        
attrString.addAttributes([NSMutableAttributedString.Key.foregroundColor: UIColor.red],
                                  range: attrString.mutableString.range(of: title))
        
attrString.addAttributes([NSMutableAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 13)],
                                  range: attrString.mutableString.range(of: title))
        
attrString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2,
                                     range: NSMakeRange(0, attrString.length))
        
titleLabel.attributedText = attrString

Chúng ta có thể áp dụng Builder pattern trong trường hợp này để nhìn code trở lên dễ đọc hơn và cũng dễ dàng phát triển trong tương lai.

Tạo một builder và protocol


protocol AttributedStringBuilderProtocol {
    
    func build() -> NSMutableAttributedString
    func setFont(_ font: UIFont, forSubString subString: String) -> AttributedStringBuilder
    func setColor(_ color: UIColor, forSubString subString: String) -> AttributedStringBuilder
    func withStrikeThrough() -> AttributedStringBuilder
    
}

class AttributedStringBuilder: AttributedStringBuilderProtocol {
    
    private var attrString: NSMutableAttributedString
    
    init(string: String) {
        self.attrString = NSMutableAttributedString(string: string)
    }
    
    func build() -> NSMutableAttributedString {
        return self.attrString
    }
    
    func setFont(_ font: UIFont, forSubString subString: String) -> AttributedStringBuilder {
        self.attrString.addAttributes([NSMutableAttributedString.Key.font: font],
                                      range: self.attrString.mutableString.range(of: subString))
        return self
    }
    
    func setColor(_ color: UIColor, forSubString subString: String) -> AttributedStringBuilder {
        self.attrString.addAttributes([NSMutableAttributedString.Key.foregroundColor: color],
                                      range: self.attrString.mutableString.range(of: subString))
        return self
    }
    
    func withStrikeThrough() -> AttributedStringBuilder {
        self.attrString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2,
                                     range: NSMakeRange(0, self.attrString.length))
        return self
    }
}

Sử dụng


let atrString = AttributedStringBuilder(string: title)
            .setFont(.boldSystemFont(ofSize: 13), forSubString: title)
            .setColor(.red, forSubString: title)
            .withStrikeThrough()
            .build()

titleLabel.attributedText = attrString

Như vậy, với trường hợp trên, việc sử dụng Builder Pattern sẽ giúp những dòng code của chúng ta trở nên dễ nhìn hơn và thuận lợi trong việc phát triển tiếp những properties của nó.

Tổng kết

Tóm lại, Design pattern: Builder Pattern là một trong những pattern dễ thực hiện. Nhưng khi sử dụng pattern này chúng ta cần phải mất thời gian để xác định được trường hợp nào cần nó, nó có thể trở nên rườm rà hơn khi sử dụng trong Swift. Nhưng chúng ta hãy đọc, tìm hiểu để áp dụng những tư duy và ý tưởng của một pattern vào một bài toán phù hợp. Đầu tư thời gian vào việc chuẩn bị xây dựng một sản phẩm sẽ luôn sẽ giúp việc bảo trì code trở nên dễ dàng hơn.

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like