Substituindo uma propriedade armazenada no Swift

126

Notei que o compilador não me permite substituir uma propriedade armazenada por outro valor armazenado (o que parece estranho):

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor = "Red" // Cannot override with a stored property lightSaberColor
}

No entanto, estou autorizado a fazer isso com uma propriedade computada:

class Jedi {
    let lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor : String{return "Red"}

}

Por que não estou autorizado a atribuir outro valor?

Por que substituir uma propriedade armazenada é uma abominação e fazê-lo com um kosher calculado? O que eles estão pensando?

cfischer
fonte

Respostas:

82

Por que não me é permitido atribuir outro valor a ele?

Você definitivamente pode atribuir um valor diferente a uma propriedade herdada. Você pode fazer isso se inicializar a propriedade em um construtor que aceita esse valor inicial e transmitir um valor diferente da classe derivada:

class Jedi {
    // I made lightSaberColor read-only; you can make it writable if you prefer.
    let lightSaberColor : String
    init(_ lsc : String = "Blue") {
        lightSaberColor = lsc;
    }
}

class Sith : Jedi {
    init() {
        super.init("Red")
    }
}

let j1 = Jedi()
let j2 = Sith()

println(j1.lightSaberColor)
println(j2.lightSaberColor)

Substituir uma propriedade não é o mesmo que atribuir um novo valor - é mais como atribuir uma classe a uma propriedade diferente. De fato, é isso que acontece quando você substitui uma propriedade calculada: o código que calcula a propriedade na classe base é substituído pelo código que calcula a substituição dessa propriedade na classe derivada.

É possível substituir a propriedade armazenada real, ou seja, lightSaberColorque tenha algum outro comportamento?

Além dos observadores, as propriedades armazenadas não têm comportamento; portanto, não há realmente nada a substituir. É possível fornecer um valor diferente à propriedade através do mecanismo descrito acima. Isso faz exatamente o que o exemplo da pergunta está tentando obter, com uma sintaxe diferente.

dasblinkenlight
fonte
2
@MaxMacLeod Além dos observadores, as propriedades armazenadas não têm comportamento; portanto, não há realmente nada a substituir. Ele queria atribuir a uma propriedade armazenada um valor diferente na subclasse, mas não tinha certeza sobre o mecanismo para alcançá-la. A resposta explica como isso pode ser feito no Swift. Sinto muito pela resposta tardia, parece que seu comentário causa confusão suficiente para atrair votos negativos, então decidi explicar o que está acontecendo.
Dasblinkenlight
55

Para mim, seu exemplo não funciona no Swift 3.0.1.

Entrei no playground esse código:

class Jedi {
    let lightsaberColor = "Blue"
}

class Sith: Jedi {
    override var lightsaberColor : String {
        return "Red"
    }
}

Lança erro no momento da compilação no Xcode:

Não é possível substituir a propriedade 'let' property 'lightsaberColor imutável pelo getter de um' var '

Não, você não pode alterar o tipo de propriedade armazenada. O Princípio de Substituição de Liskov obriga a permitir que uma subclasse seja usada em um local onde a superclasse é desejada.

Mas se você mudar para var e, portanto, adicionar o setna propriedade computada, poderá substituir a propriedade armazenada por uma propriedade calculada do mesmo tipo.

class Jedi {
    var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
        set {
            // nothing, because only red is allowed
        }
    }
}

Isso é possível porque pode fazer sentido alternar da propriedade armazenada para a propriedade calculada.

Mas substitua um var propriedade por uma varpropriedade armazenada não faz sentido, porque você só pode alterar o valor da propriedade.

No entanto, você não pode substituir uma propriedade armazenada por uma propriedade armazenada.


Eu não diria que os Sith são Jedi :-P. Portanto, é claro que isso não pode funcionar.

Binariano
fonte
18
class SomeClass {
    var hello = "hello"
}
class ChildClass: SomeClass {
    override var hello: String {
        set {
            super.hello = newValue
        }
        get {
            return super.hello
        }    
    }
}
Zhiping Yang
fonte
13
Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
DimaSan
15

Você provavelmente deseja atribuir outro valor à propriedade:

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override init() {
        super.init()
        self.lightSaberColor = "Red"
    }
}
zisoft
fonte
9
mesmo se aplica como por comentário acima
Max MacLeod
10

Para o Swift 4, da documentação da Apple :

Você pode substituir uma instância herdada ou propriedade de tipo para fornecer seu próprio getter e setter personalizado para essa propriedade ou adicionar observadores de propriedade para permitir que a propriedade de substituição observe quando o valor da propriedade subjacente é alterado.

riyasavla
fonte
7

Infelizmente, no Swift, isso não é possível. A melhor alternativa é a seguinte:

class Jedi {
    private(set) var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
    }
}
Noah Wilder
fonte
3

Eu tive o mesmo problema para definir uma constante para um controlador de exibição.

Como estou usando o construtor de interface para gerenciar a exibição, não posso usá-la init(), portanto, minha solução alternativa foi semelhante a outras respostas, exceto pelo uso de uma variável computada somente leitura nas classes base e herdada.

class Jedi {
    var type: String {
        get { return "Blue" }
    }
}

class Sith: Jedi {
    override var type: String {
        get { return "Red" }
    }
}
Yassine ElBadaoui
fonte
3

Se você tentar fazer isso no Swift 5, será recebido por um

Não é possível substituir a propriedade 'let' imutável 'lightSaberColor' pelo getter de um 'var'

Sua melhor aposta é declará-lo como uma propriedade computada.

Isso funciona, pois estamos substituindo a get {}função

class Base {
   var lightSaberColor: String { "base" }
}

class Red: Base {
   override var lightSaberColor: String { "red" }
}
Hugo Alonso
fonte
2

Swift não permite substituir uma variável. stored propertyEm vez disso, você pode usarcomputed property

class A {
    var property1 = "A: Stored Property 1"

    var property2: String {
        get {
            return "A: Computed Property 2"
        }
    }

    let property3 = "A: Constant Stored Property 3"

    //let can not be a computed property
    
    func foo() -> String {
        return "A: foo()"
    }
}

class B: A {

    //now it is a computed property
    override var property1: String {

        set { }
        get {
            return "B: overrode Stored Property 1"
        }
    }

    override var property2: String {
        get {
            return "B: overrode Computed Property 2"
        }
    }
    
    override func foo() -> String {
        return "B: foo()"
    }

    //let can not be overrode
}
func testPoly() {
    let a = A()
    
    XCTAssertEqual("A: Stored Property 1", a.property1)
    XCTAssertEqual("A: Computed Property 2", a.property2)
    
    XCTAssertEqual("A: foo()", a.foo())
    
    let b = B()
    XCTAssertEqual("B: overrode Stored Property 1", b.property1)
    XCTAssertEqual("B: overrode Computed Property 2", b.property2)
    
    XCTAssertEqual("B: foo()", b.foo())
    
    //B cast to A
    XCTAssertEqual("B: overrode Stored Property 1", (b as! A).property1)
    XCTAssertEqual("B: overrode Computed Property 2", (b as! A).property2)
    
    XCTAssertEqual("B: foo()", (b as! A).foo())
}

É mais claro quando comparado com Java, onde um campo de classe não pode ser substituído e não suporta polimorfismo porque é definido em tempo de compilação (executado com eficiência). É chamado de variável oculta [Sobre] Não é recomendável usar esta técnica porque é difícil de ler / dar suporte

[Propriedade rápida]

yoAlex5
fonte
1

Você também pode usar uma função para substituir. Não é uma resposta direta, mas pode enriquecer este tópico)

Classe A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

public func shouldDoSmth() -> Bool {
    return true
}

Classe B: A

public func shouldDoSmth() -> Bool {
    return false
}
Nik Kov
fonte