神刀安全网

Refactoring to: Parameter Objects

I’m currently reading Refactoring to Patterns (affiliate link). Yesterday, when I wrote about theCreation Methodfor an object that has a lot of parameters, it made me think of @modocache ‘s amazing talk on Swift Patterns in iOS API Design , specifically the part of about Parameter Objects . This has come up for me a few times since I initially saw the talk, so I wanted to document it here.

The Problem

Let’s say you’re writing a BananaUIKit library that includes a simple BananaAlertView:

Refactoring to: Parameter Objects

The initial code might look something like this:

public class BananaAlertView {          publicstatic func show(         withTitletitle: String,         message: String,         dismissButtonText: String)     {         // implementation here     } } 

This is great until a user of this framework comes along and asks for the ability to make the BananaAlertView brown instead of yellow…

To make sure this change is not breaking to other consumers of this framework, we can use Swift’s default parameters:

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

This works fine as long as we’re adding parameters to functions, but this doesn’t work if we want to add parameters to something else, such as a closure for when a button on the BananaAlertView is clicked:

public class BananaAlertView {          // actions for when a button is clicked     publictypealias ButtonCallback = (buttonIndex: Int) -> Void          publicstatic func show(         withTitletitle: String,         message: String,         dismissButtonText: String,         // callback parameter         dismissButtonCallback: ButtonCallback)     {         // implementation here     } }   // Usage   BananaAlertView.show(     withTitle: "This is Bananas",     message: "Someone has been monkeying around :see_no_evil:",     dismissButtonText: "Banana",     dismissButtonCallback: { buttonIndexin         // implementation here     }) 

But what if we need to change the parameters in the closure? What if the client also need the button’s text?

The solution is to just add the button’s text as an argument to the ButtonCallback:

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

But this breaks everything… When calling the show method, the ButtonCallback now has to take in two arguments instead of one…

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

So what do we do? Parameter objects to the rescue!

The Solution

The solution is to create a parameter object for the closure:

public class BananaAlertView {          // parameter object     public struct ButtonCallbackParameters {         let buttonIndex: Int         let buttonTitle: String     }          // this now only takes a single parameter     publictypealias ButtonCallback = (parameters: ButtonCallbackParameters) -> Void          publicstatic func show(         withTitletitle: String,         message: String,         dismissButtonText: String,         dismissButtonCallback: ButtonCallback)     {         // implementation here     } }   BananaAlertView.show(     withTitle: "This is Bananas",     message: "Someone has been monkeying around :see_no_evil:",     dismissButtonText: "Banana",     // the parameters object has all the parameters     // that the client will ever need!     dismissButtonCallback: { parametersin         if parameters.buttonTitle == "Banana" {             // handle it here         }     }) 

If we ever need to add an additional parameter, it’s completely fine. The buttonCallback never changes!

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

And of course you can easily remove (or deprecate) parameters:

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

Other Uses

And of course this could be used in a more general way to refactor a method as it gets more and more parameters:

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 {         publiclet dismissButtonText = "Bananana"         publiclet tintColor = UIColor.yellowColor()         publiclet animationDuration: NSTimeInterval = 1     }          publicstatic func show(         withTitletitle: String,         message: String,         options: AlertViewOptions)     {         // implementation here     } } 

Trade-Offs

And just like with any pattern, it’s good to know it, but it comes with tradeoffs. It is our job as programmers to find the right balance :no_good:.

For parameter objects, the positive is that you can Future-proof your API, but they are a lot of overhead. You don’t want to have to create a new struct for every single method and closure in your app!

So use this wisely!

Finally, I highly recommend watching @modocache’s full talk here !

Join me for a Swift Community Celebration :tada: in New York City on September 1st and 2nd. Use code NATASHATHEROBOT to get $100 off!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Refactoring to: Parameter Objects

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址