Swift:使用大量属性初始化模型类的正确方法

时间:2017-02-13 10:23:42

标签: swift design-patterns initialization optional

如何使用大量属性初始化类/结构?

这个问题可能会在没有Swift上下文的情况下被问到,但是Swift带来了它的味道,所以我在标题和标签中添加了Swift标签。

假设您有一个包含20个属性的User类。他们中的大多数不应该是零或空。让我们假设这些属性不是相互依赖的。让我们假设它的33%应该是类的逻辑不变(let)。假设它们中至少有65%没有有意义的默认值。你将如何设计这个类并初始化它的实例?

到目前为止,我几乎没有什么想法,但似乎没有一件事让我完全满意:

  • 将所有属性线性地放在类中并制作巨大的init方法:

    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) {
        }
    }
    
  • 尝试将属性分组为子类型并分别处理较小的inits

    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) {
        }
    }
    
  • 另一个解决方案是稍微破坏需求:要么声明一些属性可选,要么在init之后设置值或声明虚拟默认值并在init之后设置正常值,这在概念上似乎是相同的

    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: "user@example.com"
    user.lastLoginDate = Date()
    

是否有一个很好的范例/模式来处理这种情况?

1 个答案:

答案 0 :(得分:1)

您可以尝试使用构建器模式。

实施例

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)

从这里取得的例子:https://github.com/ochococo/Design-Patterns-In-Swift

如果您正在寻找更像Java的解决方案,您可以尝试这样的事情。

替代示例

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()

示例摘自:http://ctarda.com/2017/09/elegant-swift-default-parameters-vs-the-builder-pattern