Last active
September 28, 2019 22:23
-
-
Save masonmark/9036c1767ffef24c15e450478861cdde to your computer and use it in GitHub Desktop.
One way to let a subclass inherit a method referencing its own type in Swift
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
// NOTE: this first attempt has been superseded by a slightly better one: | |
// https://gist.github.com/masonmark/967ec16161d6e0b26e146f32c2f35898 | |
// Mason 2019-09-28: This is one way to allow subclasses to inherit a | |
// method with a parameter whose type refers to the actual subclass type | |
// (and not the parent class's type). | |
// | |
// One purpose here is to define a base class, that can have various | |
// subclasses, and enable all of the subclasses to inherit a class method | |
// named configure(), which creates an instance of the subclass and then | |
// configures the instance and its various subclass-specific properties. | |
// | |
// This turned out not to be so straighforward, so there is some convoluted | |
// set up code to make it work. | |
// | |
// I am not sure this is the best way to do this in Swift 5.1; it is just | |
// the first way I found that worked. | |
protocol BaseConfigurable { | |
// static func configure( configurator: (Self) -> Void) -> Self; | |
// | |
// We cannot have the above definition in the protocol itself. It | |
// must be defined only in the protocol extension, otherwise we get | |
// error: | |
// | |
// Protocol 'BaseConfigurable' requirement 'configure(configurator:)' cannot | |
// be satisfied by a non-final class ('Configurable') because it uses 'Self' | |
// in a non-parameter, non-result type position | |
} | |
extension BaseConfigurable where Self: Configurable { | |
static func configure( _ configurator: (Self) -> Void) -> Self { | |
let result = Self.init(); | |
configurator(result); | |
return result; | |
} | |
} | |
class Configurable: BaseConfigurable { | |
required init() { | |
// We need this required init, otherwise we get this error: | |
// | |
// Constructing an object of class type 'Self' with a metatype | |
// value must use a 'required' initializer | |
} | |
} | |
class Person: Configurable { | |
var name = "Alice" | |
var age = 0 | |
var bio: String { | |
"My name is \(name), and I am a \(Self.self). I am \(age) years old." | |
} | |
} | |
class PoliceOfficer: Person { | |
var badgeNumber: String = "0000000" | |
} | |
class Lieutenant: PoliceOfficer { | |
var hasBeard = true | |
var cigarCount = 0 | |
} | |
class FireFighter: Person { | |
var helmetSize: Int? | |
var hasLicenseToDriveFireEngine = false | |
} | |
let lisa = Person.configure() { lisa in | |
lisa.name = "Lisa" | |
lisa.age = 39 | |
} | |
let fred = Lieutenant.configure() { fred in | |
fred.name = "Fred" | |
fred.age = 57 | |
fred.hasBeard = false | |
} | |
// let biff = FireFighter.configure() { biff in | |
// biff.hasLicenseToDriveFireEngine = true; | |
// biff.helmetSize = 15 | |
// biff.age = 21 | |
// biff.name = "Biff Wigginberger Jr." | |
// } | |
// instead of the above, do it another way for example: | |
func configureBiff(biff: FireFighter) { | |
biff.hasLicenseToDriveFireEngine = true; | |
biff.helmetSize = 15 | |
biff.age = 21 | |
biff.name = "Biff Wigginberger Jr." | |
} | |
let biff = FireFighter.configure(configureBiff) | |
print(lisa.bio) | |
print(fred.bio) | |
print(biff.bio) | |
// prints: | |
// My name is Lisa, and I am a Person. I am 39 years old. | |
// My name is Fred, and I am a Lieutenant. I am 57 years old. | |
// My name is Biff Wigginberger Jr., and I am a FireFighter. I am 21 years old. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment