Refactoring to: Creation Method
As I spoke at and attended several conferences last month, the book Refactoring to Patterns (affiliate link) kept coming up again and again, especially in my favorite talks. I finally have a little time to read it (before all the after-WWDC-announcement craziness ascends), and I’d like to document the patterns I like for future reference. I also found it better to remember the information by translating the Java in the book to Swift.
The first pattern is the Creation Method.
Before
Let’s say you have a model, such as a Loan, that has a lot of different initializers. In this case each initializer is there for a reason – each one corresponds to a different loan type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
struct Loan { let commitment: NSDecimalNumber let riskRating: Float let maturity: Int let expiry: NSDate? let capitalStrategy: String? let outstanding: NSDecimalNumber? init(commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate?, capitalStrategy: String?, outstanding: NSDecimalNumber?) { self.commitment = commitment self.riskRating = riskRating self.maturity = maturity self.expiry = expiry self.capitalStrategy = capitalStrategy self.outstanding = outstanding } init(commitment: NSDecimalNumber, riskRating: Float, maturity: Int) { self.init(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: nil, capitalStrategy: nil, outstanding: nil) } init(commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) { self.init(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: nil, outstanding: nil) } init(commitment: NSDecimalNumber, outstanding: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) { self.init(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: nil, outstanding: outstanding) } init(capitalStrategy: String, commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) { self.init(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: capitalStrategy, outstanding: nil) } init(capitalStrategy: String, commitment: NSDecimalNumber, outstanding: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) { self.init(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: capitalStrategy, outstanding: outstanding) } } |
Looking at this code, you would have no idea (unless you implicitly know all the loan types) of which initializer to use. Of course, someone new coming to this code base might not be familiar with the business information, so they might use the wrong initializer, further confusing the intent of all the initializers.
Time to refactor!
After
The Creator Method refactors this to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
struct Loan { let commitment: NSDecimalNumber let riskRating: Float let maturity: Int let expiry: NSDate? let capitalStrategy: String? let outstanding: NSDecimalNumber? // the original initializer can now be private private init(commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate?, capitalStrategy: String?, outstanding: NSDecimalNumber?) { self.commitment = commitment self.riskRating = riskRating self.maturity = maturity self.expiry = expiry self.capitalStrategy = capitalStrategy self.outstanding = outstanding } static func createTermLoan(commitment: NSDecimalNumber, riskRating: Float, maturity: Int) -> Loan { return Loan(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: nil, capitalStrategy: nil, outstanding: nil) } static func createTermLoan(capitalStrategy: String, commitment: NSDecimalNumber, outstanding: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) -> Loan { return Loan(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: capitalStrategy, outstanding: outstanding) } static func createRevolverLoan(commitment: NSDecimalNumber, outstanding: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) -> Loan { return Loan(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: nil, outstanding: outstanding) } static func createRevolverLoan(capitalStrategy: String, commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) -> Loan { return Loan(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: capitalStrategy, outstanding: nil) } static func createRCTL(commitment: NSDecimalNumber, riskRating: Float, maturity: Int, expiry: NSDate) -> Loan { return Loan(commitment: commitment, riskRating: riskRating, maturity: maturity, expiry: expiry, capitalStrategy: nil, outstanding: nil) } } |
Notice how beautiful the result is! When you initialize a loan, you now know the intent.
While I’m generally not big on these type of class initializers and there might be a better way to do this in Swift depending on the actual code situation (e.g enums), I will definitely consider refactoring to this pattern in cases where it would make my code this much more readable and clearer.
Join me for a Swift Community Celebration 🎉 in New York City on September 1st and 2nd. Use code NATASHATHEROBOT to get $100 off!