-
-
Save AliSoftware/9e4946c8b6038572d678 to your computer and use it in GitHub Desktop.
// #!Swift-1.1 | |
import Foundation | |
// MARK: - (1) classes | |
// Solution 1: | |
// - Use classes instead of struct | |
// Issue: Violate the concept of moving model to the value layer | |
// http://realm.io/news/andy-matuschak-controlling-complexity/ | |
typealias JSONDict = [NSObject:AnyObject] | |
class Vehicle1 { | |
let model: String | |
let color: String | |
init(jsonDict: JSONDict) { | |
model = jsonDict["model"] as String | |
color = jsonDict["color"] as String | |
} | |
} | |
class Car1 : Vehicle1 { | |
let horsepower: Double | |
let license_plate: String | |
override init(jsonDict: JSONDict) { | |
super.init(jsonDict: jsonDict) | |
horsepower = jsonDict["horsepower"] as Double | |
license_plate = jsonDict["license_plate"] as String | |
} | |
} | |
class Bicycle1 : Vehicle1 { | |
let chainrings: Int | |
let sprockets: Int | |
override init(jsonDict: JSONDict) { | |
super.init(jsonDict: jsonDict) | |
chainrings = jsonDict["chainrings"] as Int | |
sprockets = jsonDict["sprockets"] as Int | |
} | |
} | |
// MARK: - (2) struct + composition | |
// Solution 2: | |
// - keep value types | |
// - use composition. | |
// Issue: We violate the encapsulation principle, exposing the internal composition to the outside world | |
struct Vehicle2 { | |
let model: String | |
let color: String | |
init(jsonDict: JSONDict) { | |
model = jsonDict["model"] as String | |
color = jsonDict["color"] as String | |
} | |
} | |
struct Car2 { | |
let vehicle: Vehicle2 | |
let horsepower: Double | |
let license_plate: String | |
init(jsonDict: JSONDict) { | |
vehicle = Vehicle2(jsonDict: jsonDict) | |
horsepower = jsonDict["horsepower"] as Double | |
license_plate = jsonDict["license_plate"] as String | |
} | |
} | |
struct Bicycle2 { | |
let vehicle: Vehicle2 | |
let chainrings: Int | |
let sprockets: Int | |
init(jsonDict: JSONDict) { | |
vehicle = Vehicle2(jsonDict: jsonDict) | |
chainrings = jsonDict["chainrings"] as Int | |
sprockets = jsonDict["sprockets"] as Int | |
} | |
} | |
// MARK: - (3) struct, protocol + composition for parsing | |
// Solution 3: | |
// - keep value types, use a protocol | |
// - use intermediate struct only for parsing to keep encapsulation | |
// Issue: None… except code verbosity | |
protocol Vehicle3 { | |
var model: String { get } | |
var color: String { get } | |
} | |
private struct VehicleFields3 : Vehicle3 { | |
let model: String | |
let color: String | |
init(jsonDict: JSONDict) { | |
model = jsonDict["model"] as String | |
color = jsonDict["color"] as String | |
} | |
} | |
struct Car3 : Vehicle3 { | |
let model: String | |
let color: String | |
let horsepower: Double | |
let license_plate: String | |
init(jsonDict: JSONDict) { | |
let vehicle = VehicleFields3(jsonDict: jsonDict) | |
model = vehicle.model | |
color = vehicle.color | |
horsepower = jsonDict["horsepower"] as Double | |
license_plate = jsonDict["license_plate"] as String | |
} | |
} | |
struct Bicycle3 : Vehicle3 { | |
let model: String | |
let color: String | |
let chainrings: Int | |
let sprockets: Int | |
init(jsonDict: JSONDict) { | |
let vehicle = VehicleFields3(jsonDict: jsonDict) | |
model = vehicle.model | |
color = vehicle.color | |
chainrings = jsonDict["chainrings"] as Int | |
sprockets = jsonDict["sprockets"] as Int | |
} | |
} | |
// MARK: - (4) struct, protocols + global function for parsing | |
// Solution 4: [Does not compile] | |
// - keep value types, use a protocol | |
// - use a global function to fill the objects's fields conforming to the protocol | |
// Issue: does not work (it seems we can't pass 'self' as inout in the init() method) | |
// exposes the setter in the protocol and the structs anyway (so bad access protection) | |
protocol Vehicle4 { | |
var model: String { get set } | |
var color: String { get set } | |
} | |
private func parseVehicle4Fields(inout obj: Vehicle4, jsonDict: JSONDict) { | |
obj.model = jsonDict["model"] as String | |
obj.color = jsonDict["color"] as String | |
} | |
struct Car4 : Vehicle4 { | |
var model: String | |
var color: String | |
let horsepower: Double | |
let license_plate: String | |
init(jsonDict: JSONDict) { | |
parseVehicle4Fields(&self, jsonDict) // Error: Car4 is not identical to Vehicle4 | |
horsepower = jsonDict["horsepower"] as Double | |
license_plate = jsonDict["license_plate"] as String | |
} | |
} | |
struct Bicycle4 : Vehicle4 { | |
var model: String | |
var color: String | |
let chainrings: Int | |
let sprockets: Int | |
init(jsonDict: JSONDict) { | |
parseVehicle4Fields(&self, jsonDict) // Error: Bicycle4 is not identical to Vehicle4 | |
chainrings = jsonDict["chainrings"] as Int | |
sprockets = jsonDict["sprockets"] as Int | |
} | |
} |
Would be great if you add some description to the gist
@MadNik:Done, thx ;) Didn't realize this gist had such a success, wrote it a while back (might need to be updated for Swift 2 or with other new alternatives the language might maybe offer?)
BTW, people finding that gist from searching about structs & inheritance in Swift might also be interested in my (way more recent) article dedicated to explain how you could use the Mixins pattern (allowed by Swift 2's default protocol implementations) instead of inheritance 😉
@AliSoftware Here is how you can make the fourth solution work. The only downside is that you need to initialize properties before you call parseVehicle4Fields(jsonDict)
protocol Vehicle4 {
var model: String { get set }
var color: String { get set }
}
extension Vehicle4 {
private mutating func parseVehicle4Fields(jsonDict: JSONDict) {
model = jsonDict["model"] as! String
color = jsonDict["color"] as! String
}
}
struct Car4 : Vehicle4 {
var model = ""
var color = ""
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
horsepower = jsonDict["horsepower"] as! Double
license_plate = jsonDict["license_plate"] as! String
parseVehicle4Fields(jsonDict)
}
}
struct Bicycle4 : Vehicle4 {
var model = ""
var color = ""
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
chainrings = jsonDict["chainrings"] as! Int
sprockets = jsonDict["sprockets"] as! Int
parseVehicle4Fields(jsonDict)
}
}
@illusionofchaos I started out with an implementation like that, but didn't like how you have to initialize all the properties with dummy values first. It's fine for scalar values like String, but once your struct has struct / class properties with their own init methods it becomes a pain. It turns out that although you can't call instance methods before all properties are initialized, you CAN call static methods. So my implementation is like this:
protocol Vehicle4 {
var model: String { get set }
var color: String { get set }
}
extension Vehicle4 {
static func parseVehicle4Fields(jsonDict: JSONDict) -> (String, String) {
let model = jsonDict["model"] as! String
let color = jsonDict["color"] as! String
return (model, color)
}
}
struct Car4 : Vehicle4 {
var model
var color
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
(model, color) = Car4.parseVehicle4Fields(jsonDict)
horsepower = jsonDict["horsepower"] as! Double
license_plate = jsonDict["license_plate"] as! String
}
}
struct Bicycle4 : Vehicle4 {
var model
var color
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
(model, color) = Bicycle4.parseVehicle4Fields(jsonDict)
chainrings = jsonDict["chainrings"] as! Int
sprockets = jsonDict["sprockets"] as! Int
}
}
Can a struct in swift inherit a class ?
I like this article. It states the situation well.
I think it's also essential for models to be Equatable, Hashable (for inclusion in sets) and CustomStringConvertible (for debugging). Would love to see another example that implements those.