How can I use a class extension to override a protocol extension?

125 Views Asked by At

Let's say I have a color model:

protocol Color {
    var value: String? { get }
}

class UnknownColor: Color {
    let value: String? = nil
}

class KnownColor: Color {
    let value: String?

    init(value: String? = nil) {
        self.value = value
    }
}

In my view file, I add some view-details to my Color model. These details aren't model specific, they're view specific.

fileprivate extension Color {
    fileprivate var representation: String {
        return self.value!
    }
}

fileprivate extension UnknownColor {
    fileprivate var representation: String {
        return "#000"
    }
}

Now, when I use my color model in the view file, I expect my UnknownColors to represent themselves as "#000", but that's not the case when UnknownColor is cast as a Color.

let color1 = KnownColor()
let color2 = KnownColor(value:"#fff")
let color3 = UnknownColor()

color1.representation  // Fatal error  (GOOD)
color2.representation  // "#fff"  (GOOD)
color3.representation  // "#000" (GOOD)

if let color = color3 as? Color {
    color.representation // Fatal error  (BAD, expected "#000")
}

I want to avoid blatant type-checking for UnknownColor, so a solution like this is not ideal:

func colorRepresentation(_ color: Color) {
    if let color = color as? UnknownColor {
        return "#000"
    } else {
        return color.value!
    }
}

I want to avoid making further changes to the Color model at all costs.

There may be many implementations of the Color protocol that want to make use of Color.representation so changing the extension Color to extension KnownColor is not an option.

Is there a way that I can restructure my code so that the UnknownColor.representation gets used when the Color is actually an UnknownColor?

1

There are 1 best solutions below

3
On

I copy and pasted your code directly into a Playground and it worked fine (color3.representation == "#000").

Are the extensions in a separate file? If so, the fileprivate keyword is going to make them invisible to the classes.

For reference, here is the entire code I put into the Playground:

protocol Color {
    var value: String? { get }
}

class UnknownColor: Color {
    let value: String? = nil
}

class KnownColor: Color {
    let value: String?

    init(value: String? = nil) {
        self.value = value
    }
}

fileprivate extension Color {
    fileprivate var representation: String {
        return self.value!
    }
}

fileprivate extension UnknownColor {
    fileprivate var representation: String {
        return "#000"
    }
}

let color1 = KnownColor()
let color2 = KnownColor(value:"#fff")
let color3 = UnknownColor()

//color1.representation  // Fatal error  (GOOD)
color2.representation  // "#fff"  (GOOD)
color3.representation  // Fatal error  (BAD, expected "#000")