Hiện tại tôi đang đọc cuốn Refactoring to Patterns. Hôm qua, khi tôi viết về Creation Method cho một đối tượng mà có rất nhiều parameter, nó khiến tôi nghĩ đến buổi chia sẻ rất hay về Swift Patterns in iOS API Design của @modocache, đặc biệt là phần nói về Parameter Objects. Do vậy tôi đã tổng hợp lại vào thành bài viết dưới đây:

Hình dung bạn đang viết một thư viện có tên là BananaUIKit, trong đó có một AlertView đơn giản như sau:

BananaAlertView

Code khởi tạo sẽ như sau:

public class BananaAlertView {
    
    public static func show(
        withTitle title: String,
        message: String,
        dismissButtonText: String)
    {
        // implementation here
    }
}

Về cơ bản thì cấu trúc code trên không có vấn đề gì cho đến khi user có yêu cầu muốn đổi BananaAlertView từ màu nâu thành màu vàng. Và để đảm bào sự thay đổi này không làm ảnh hưởng tới các phần khác, chúng ta có thể sử dụng default parameter của Swift:

public class BananaAlertView {
    
    public static func show(
        withTitle title: String,
        message: String,
        dismissButtonText: String,
        // new non-breaking change
        tintColor: UIColor = .yellowColor())
    {
        // implementation here
    }
}

Mọi thứ vẫn hoạt động ổn giống như khi chúng ta thêm paremeter vào trong function, nhưng sẽ là không ổn nếu như chúng ta muốn thêm parameter vào một vài chỗ khác, như là closure để xử lý event click vào event của BananaAlertView:

public class BananaAlertView {
    
    // actions for when a button is clicked
    public typealias ButtonCallback = (buttonIndex: Int) -> Void
    
    public static func show(
        withTitle title: String,
        message: String,
        dismissButtonText: String,
        // callback parameter
        dismissButtonCallback: ButtonCallback)
    {
        // implementation here
    }
}

// Usage

BananaAlertView.show(
    withTitle: "This is Bananas",
    message: "Someone has been monkeying around 🙈",
    dismissButtonText: "Banana",
    dismissButtonCallback: { buttonIndex in
        // implementation here
    })

Điều gì sẽ xảy ra nếu chúng ta thay đổi parameter ở trong closure? Và nếu khách hàng cũng cần lấy ra text của button?

Giải pháp ở đây là chỉ cần thêm button text như là một argument của ButtonCallback:

public typealias ButtonCallback = (buttonIndex: Int, buttonTitle: String) -> Void

Nhưng điều này sẽ phá vỡ mọi thứ, khi chúng ta gọi đến method show, ButtonCallback bây giờ sẽ phải nhận 2 argument thay vì 1 

// Usage

BananaAlertView.show(
    withTitle: "This is Bananas",
    message: "Someone has been monkeying around 🙈",
    dismissButtonText: "Banana",
    // this now breaks
    // the closure needs to take a buttonIndex and a buttonText now
    dismissButtonCallback: { buttonIndex in
        // implementation here
    })

Giải pháp

Giải pháp ở đây là tạo một parameter object cho closure:

public class BananaAlertView {
    
    // parameter object
    public struct ButtonCallbackParameters {
        let buttonIndex: Int
        let buttonTitle: String
    }
    
    // this now only takes a single parameter
    public typealias ButtonCallback = (parameters: ButtonCallbackParameters) -> Void
    
    public static func show(
        withTitle title: String,
        message: String,
        dismissButtonText: String,
        dismissButtonCallback: ButtonCallback)
    {
        // implementation here
    }
}

BananaAlertView.show(
    withTitle: "This is Bananas",
    message: "Someone has been monkeying around 🙈",
    dismissButtonText: "Banana",
    // the parameters object has all the parameters
    // that the client will ever need!
    dismissButtonCallback: { parameters in
        if parameters.buttonTitle == "Banana" {
            // handle it here
        }
    })
 
 

Nếu như chúng ta phải thêm parameter thì vẫn không có vấn đề gì, buttonCallback vẫn không thay đổi.

   public struct ButtonCallbackParameters {
        let buttonIndex: Int
        let buttonTitle: String
        // new parameter
        let buttonCount: Int
    }

Và tất nhiên là bạn có thể dễ dàng gỡ bỏ nhưng parameter mà mình không còn dùng đến nữa.

   public struct ButtonCallbackParameters {
        let buttonIndex: Int
        // deprecate buttonTitle in next version
        @available(*, deprecated=2.0)
        let buttonTitle: String
        let buttonCount: Int
    }

Ở những trường hợp khác thì đây cũng là một cách để refactor parameter của các method.

public class BananaAlertView {
    
    // view options are all things that
    // are not essential to displaying an alert view
    // default values can be provided here
    public struct AlertViewOptions {
        public let dismissButtonText = "Bananana"
        public let tintColor = UIColor.yellowColor()
        public let animationDuration: NSTimeInterval = 1
    }
    
    public static func show(
        withTitle title: String,
        message: String,
        options: AlertViewOptions)
    {
        // implementation here
    }
}

Cuối cùng thì cũng giống như các patterns khác, bên cạnh những cái tốt thì nó cũng có hạn chế nhất định. Ví dụ như nó có thể sẽ khiến API của bạn phình to ra, bạn sẽ không muốn phải tạo một struct cho riêng một method hay closure trong app của mình. Vì vậy, hãy sử dụng nó một cách hợp lý. Cuối cùng, bạn có thể tham khảo qua buổi nói chuyện của @modocache’s tại đây.

Nguồn bài viết:  https://www.natashatherobot.com/parameter-objects/

Khóa học lập trình di động tại Techmaster:

Để cài đặt MacOSX lên phần cứng không phải Apple liên hệ chuyên gia cài Hackintosh:

  • Nguyễn Minh Sơn: 01287065634
  • Huỳnh Minh Sơn: 0936225565
  • Website: caidatmacos.com

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster