Skip to content

Instantly share code, notes, and snippets.

@sofacoder
Forked from d-ronnqvist/gist:7ea8b1dc7d10f23917c2
Last active August 29, 2015 14:11
Show Gist options
  • Save sofacoder/ebf0ef6077c0be078376 to your computer and use it in GitHub Desktop.
Save sofacoder/ebf0ef6077c0be078376 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.
//
// 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?
// V5 using generics (Out of the head)
struct InputV5<T> {
let name: String
let kind: T
}
let positionInputV5 = InputV1(name: "position", kind: positionSymbol)
// Now you can have API that's checked on compile time but also have generic
// functions that accept any input. You can also restrict T to certain types.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment