Created
December 10, 2014 20:04
-
-
Save d-ronnqvist/7ea8b1dc7d10f23917c2 to your computer and use it in GitHub Desktop.
I'm designing something where there are multiple different objects that can hold exactly _one_ out of a couple of different other types, and I don't know what style I prefer.
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
// I'm designing something where there are multiple different objects that can hold | |
// exactly _one_ out of a couple of different other types, and I don't know what style I prefer. | |
// | |
// For example, an Input always has a name and it can hold a Symbol, a Target, or a Texture. | |
// It should always hold exactly one of them. In just the same way, a Symbol has a name and can | |
// hold different data of exactly exactly _one_ type of many. | |
// | |
// Which one of the following alternatives do you prefer? | |
// assume an instance of Symbol (doens't matter what it looks like) | |
let positionSymbol = Symbol(/* ... */) | |
// ----- V1 ----- // | |
// My first though was to have a separate enum that defines the kind of value being stored (with associated values) | |
enum InputKind { | |
case SymbolKind(Symbol) | |
case TargetKind(Target) | |
case TextureKind(Texture) | |
} | |
struct InputV1 { | |
let name: String | |
let kind: InputKind | |
} | |
// A new input is then created like this | |
let positionInputV1 = InputV1(name: "position", kind: .SymbolKind(positionSymbol)) | |
// This makes it resonably easy to switch on the different kinds of data, | |
// but the init gets a bit wordy and I feel like it's unnecessary to make the enum so "public" | |
// ----- V2 ----- // | |
// Next I thought that I could have optional variables for the different types of data to store | |
struct InputV2 { | |
let name: String | |
let symbol: Symbol? | |
let target: Target? | |
let texture: Texture? | |
} | |
// A new input is then created like this | |
let positionInputV2 = InputV2(name: "position", symbol: positionSymbol, target: nil, texture: nil) | |
// I can get to the different kinds of data using optional binding (if let), | |
// but it doesn't protect me from having multiple types of data or no data at all | |
// ----- V3 ----- // | |
// Then I wend back to the first alternative an put the enum inside the stuct. | |
struct InputV3 { | |
enum Kind { | |
case SymbolKind(Symbol) | |
case TargetKind(Target) | |
case TextureKind(Texture) | |
} | |
let name: String | |
let kind: Kind | |
} | |
// A new input is then created like this | |
let positionInputV3 = InputV3(name: "position", kind: .SymbolKind(positionSymbol)) | |
// This meant that I could give the enum a shorter, more contextual name | |
// but still had the problem with a lengthy init | |
// ----- V4 ----- // | |
// The same problem can be solved using inheritance, but it didn't feel very Swift to me | |
class InputV4 { | |
let name: String | |
init(_ aName: String) { | |
name = aName | |
} | |
} | |
class SymbolInputV4 : InputV4 { | |
let symbol: Symbol | |
init(_ aName: String, symbol aSymbol:Symbol) { | |
symbol = aSymbol | |
super.init(aName) | |
} | |
} | |
class TargetInputV4 : InputV4 { | |
let target: Target | |
init(_ aName: String, symbol aTarget:Target) { | |
target = aTarget | |
super.init(aName) | |
} | |
} | |
class TextureInputV4 : InputV4 { | |
let texture: Texture | |
init(_ aName: String, symbol aTexture:Texture) { | |
texture = aTexture | |
super.init(aName) | |
} | |
} | |
// A new input is then created like this | |
let positionInputV4 = SymbolInputV4("position", symbol: positionSymbol) | |
// Which version do you prefer? Do you have any other versions that I haven't thought of? | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What about generics?
https://gist.github.com/sofacoder/ebf0ef6077c0be078376