Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: The proper way to initialize model class with a lot of properties

How do you initialize your classes/structs with a lot of properties?

This question could probably be asked without Swift context but Swift brings a flavour to it, so I add Swift tag in headline and tags.

Let's say you have a User class with 20 properties. Most of them should not be nil or empty. Let's assume these properties are not interdependent. Let's assume that 33% of it should be constant (let) by the logic of the class. Let's assume that at least 65% of them do not have meaningful default values. How would you design this class and initialize an instance of it?

So far I have few thoughts but none of it seems to be completely satisfactory to me:

  • put all of the properties linearly in the class and make huge init method:

    class User {
        // there is 20 properties like that
        let id : String
        let username : String
        let email : String
        ...
        var lastLoginDate : Date
        var lastPlayDate : Date
    
        // then HUUUUGE init
        init(id: String, 
             username: String,
             ...
             lastPlayDate: Date) {
        }
    }
    
  • try to group properties into sub types and deal with smaller inits separately

    class User {
        struct ID {
            let id : String
            let username : String
            let email : String
        }
        struct Activity {
            var lastLoginDate : Date
            var lastPlayDate : Date 
        }
        let id : ID
        ...
        var lastActivity : Activity
    
        // then not so huge init
        init(id: ID, 
             ...
             lastActivity: Activity) {
        }
    }
    
  • another solution is to break requirements a bit: either declare some of the properties optional and set values after init or declare dummy default values and set normal values after init, which conceptually seems to be the same

    class User {
        // there is 20 properties like that
        let id : String
        let username : String
        let email : String
        ...
        var lastLoginDate : Date?
        var lastPlayDate : Date?
    
        // then not so huge init
        init(id: String, 
             username: String,
             email: String) {
        }
    }
    
    // In other code 
    var user = User(id: "1", username: "user", email: "[email protected]"
    user.lastLoginDate = Date()
    

Is there a nice paradigm/pattern how to deal with such situations?

like image 622
user1264176 Avatar asked Feb 13 '17 10:02

user1264176


1 Answers

You can try the builder pattern.

Example

class DeathStarBuilder {

    var x: Double?
    var y: Double?
    var z: Double?

    typealias BuilderClosure = (DeathStarBuilder) -> ()

    init(buildClosure: BuilderClosure) {
        buildClosure(self)
    }
}

struct DeathStar : CustomStringConvertible {

    let x: Double
    let y: Double
    let z: Double

    init?(builder: DeathStarBuilder) {

        if let x = builder.x, let y = builder.y, let z = builder.z {
            self.x = x
            self.y = y
            self.z = z
        } else {
            return nil
        }
    }

    var description:String {
        return "Death Star at (x:\(x) y:\(y) z:\(z))"
    }
}

let empire = DeathStarBuilder { builder in
    builder.x = 0.1
    builder.y = 0.2
    builder.z = 0.3
}

let deathStar = DeathStar(builder:empire)

Example taken from here: https://github.com/ochococo/Design-Patterns-In-Swift

If you are looking for a bit more Java like solution, you can try something like this.

Alternative Example

final class NutritionFacts {
    private let servingSize: Int
    private let servings: Int
    private let calories: Int
    private let fat: Int
    private let sodium: Int
    private let carbs: Int
 
    init(builder: Builder) {
        servingSize = builder.servingSize
        servings = builder.servings
        calories = builder.calories
        fat = builder.fat
        sodium = builder.sodium
        carbs = builder.carbs
    }
 
    class Builder {
        let servingSize: Int
        let servings: Int
 
        private(set) var calories = 0
        private(set) var fat = 0
        private(set) var carbs = 0
        private(set) var sodium = 0
 
        init(servingSize: Int, servings: Int) {
            self.servingSize = servingSize
            self.servings = servings
        }
 
        func calories(value: Int) -> Builder {
            calories = value
            return self
        }
 
        func fat(value: Int) -> Builder {
            fat = value
            return self
        }
 
        func carbs(value: Int) -> Builder {
            carbs = value
            return self
        }
 
        func sodium(value: Int) -> Builder {
            sodium = value
            return self
        }
 
        func build() -> NutritionFacts {
            return NutritionFacts(builder: self)
        }
    }
}
 
let facts = NutritionFacts.Builder(servingSize: 10, servings: 1)
    .calories(value: 20)
    .carbs(value: 2)
    .fat(value: 5)
    .build()

Example taken from: http://ctarda.com/2017/09/elegant-swift-default-parameters-vs-the-builder-pattern

like image 73
Ehtesham Hasan Avatar answered Oct 30 '22 14:10

Ehtesham Hasan