Last active
October 14, 2024 16:44
-
-
Save AliSoftware/9e4946c8b6038572d678 to your computer and use it in GitHub Desktop.
Swift, Struct & Inheritance: How to balance the will of using Struct & Value Types and the need for Inheritance?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #!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 | |
} | |
} |
@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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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)