diegoRodicio

Está documentación está a túa disposición sin ningún custo económico. Sen embargo, para a súa elaboración dedico moito tempo e recursos, polo que agradecería unha colaboración co que consideres oportuno. Gracias.

A herdanza permítenos herdar propiedades e métodos dunha clase a outra.

  • Ao herdar podemos usar os métodos e propiedades sen necesidade de volver a crealos na nova clase (é unha das vantaxes de herdar, que podemos usar métodos e propiedades xa existentes da clase que herdamos).
  • Ao usar herdanza o que buscamos é encapsular o noso código en tipos ben definidos para reutilizar código. Desta maneira o noso código queda máis limpo e fácil de entender.

Definicións #

Hai uns termos que usamos ao traballar con herdanza:

  • Superclase, é a clase da que van herdar outras clases. Tamén se lle adoita chamar clase abstracta, clase base ou clase pai.
  • Subclase, é a clase que herdou dunha superclase. Tamén se lle coñece como clase derivada ou clase filla.

Ao derivar clases, creamos o que a miúdo se coñece como xerarquía de clases.

En Swift, unha subclase só pódese derivar dunha soa clase principal directa. Este é un concepto coñecido como herdanza única

Superclase ou clase base #

Partimos da seguinte clase:

class Animal {
    var nome: String
    var idade: Int

    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }

    func son() {
        print("Sonido do Animal")
    }
}

Esta clase é moi abstracta, é dicir, non estamos a tratar con ningún animal en concreto. Imaxina que necesitas crear unha clase Can máis específica:

class Can {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

Can ten exactamente as mesmas propiedades e método que a clase Animal.

Así, podemos crear unha subclase de Animal e chamala Can, que herdará as súas propiedades e método. Desta maneira, Can queda simplificada:

class Animal {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

class Can: Animal {
    
}

Agora que temos o tipo Can herdando de Animal, imos crear unha instancia de Can:

class Animal {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

class Can: Animal {
    
}

let can = Can()

// RESULTADO 👇
// erro: missing arguments for parameters 'nome', 'idade' in call

Obtemos un erro, debido a que na subclase Can temos que ter en conta e inicializar as propiedades da superclase. É por iso que para crear unha instancia de Can debemos usar un inicializador da superclase (xa que debemos dar un valor a todas as súas propiedades):

var can = Can(nome: "Boby", idade: 2)
print("\(can.nome)")

// RESULTADO 👇
// Boby

Ampliar a funcionalidade dunha subclase #

Dentro da clase Can podemos engadir máis métodos ou propiedades que non existen na superclase.

Engadir propiedades #

Se queremos engadir unha propiedade na clase Can, vemos que o seguinte código da un erro de compilación:

class Can: Animal {
    var patas: Int
}

// RESULTADO 👇
// Error stored property 'patas' without initial value prevents synthesized initializers

Para solventar o erro, na clase Can temos que crear un inicializador para dar un valor inicial á propiedade patas:

class Can: Animal {
    var patas: Int
    
    init(patas: Int) {
        self.patas = patas
    }
}

Agora imos obter outro erro xa que debemos chamar ao init de Animal para poder dar un valor e iniciar ás propiedades de Animal (a superclase de Can). Iso o facemos utilizando super.init, que se encarga de chamar ao inicializador da superclase cos valores iniciais que se asignasen ás súas propiedades:

class Can: Animal {
    var patas: Int
    
    init(patas: Int, nome: String, idade: Int) {
        self.patas = patas
        super.init(nome: nome, idade: idade)
    }
}

var can = Can(patas: 4, nome: "Boby", idade: 2)
print("\(can.nome)")

Engadir métodos #

Neste caso, creamos o método comer():

class Animal {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

class Can: Animal {
    var patas: Int
    
    init(patas: Int, nome: String, idade: Int) {
        self.patas = patas
        super.init(nome: nome, idade: idade)
    }
    
    func comer() {
        print("Comer carne")
    }
}

var can = Can(patas: 4, nome: "Boby", idade: 2)
can.comer()

// RESULTADO 👇
// Comer carne

Exemplo #

Neste exemplo creamos unha clase chamada ContaBancaria deseñada para ter un número de conta bancaria e o saldo actual correspondente:

class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

Supoñamos que ademais da clase ContaBancaria tamén necesitamos unha clase para usar contas de aforro. Unha conta de aforros (ao igual que unha conta bancaria) tamén terá que ter un número de conta, un saldo actual e métodos para acceder a eses datos.

  • Para iso, creamos unha nova clase que sexa unha subclase da clase ContaBancaria.
  • A nova clase herdará todas as características da clase ContaBancaria, podéndose ampliar para agregar a funcionalidade adicional requirida para unha conta de aforros.

Creamos unha subclase de ContaBancaria que chamaremos ContaAforro simplemente especificando ContaBancaria como a clase da que herdamos:

class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

class ContaAforro:ContaBancaria {
    
}

var aforro = ContaAforro(numero: 12345, saldo: 0.0)
aforro.visualizarSaldo() //Imprime: Saldo actual: 0.0

Tede en conta que, aínda que aínda non engadimos ningunha variable ou método, a clase herdou todos os métodos e propiedades da clase principal ContaBancaria.

  • Polo tanto, podemos crear unha instancia da clase ContaAforro e establecer variables e métodos de chamada exactamente da mesma maneira que o fixemos coa clase ContaBancaria nos exemplos anteriores (liñas 20 e 21).
  • Dito isto, realmente non logramos nada a menos que tomemos medidas para ampliar a clase ContaAforro.

A continuación:

  • Agora necesitamos ampliar a subclase (ContaAforro) para que teña as características que necesitamos para que sexa útil para almacenar a información da conta de aforros.
  • Para facer isto, simplemente engadimos as propiedades e métodos que proporcionan a nova funcionalidade, do mesmo xeito que o fariamos para calquera outra clase que desexemos crear:
class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

class ContaAforro:ContaBancaria {
    var tipoInteres: Float = 0.0
    
    func calcularInteres() -> Float {
        return tipoInteres * saldoConta
    }
}

var aforro = ContaAforro(numero: 12345, saldo: 1000.0)
aforro.tipoInteres = 0.10
aforro.visualizarSaldo() //Imprime: Saldo actual: 0.0
print("Intereses: \(aforro.calcularInteres())")

override – Sobreescritura de métodos herdados #

Cando se utiliza a herdanza, pode ser que necesitemos sobreescribir algún método herdado da clase principal:

  • Cando, por exemplo, na subclase teñamos que modificar un método herdado da clase principal para proporcionar a funcionalidade que precisa dita subclase.
  • Tamén é posible que herdes un método que non faga o que necesites. Para iso, anulamos o método herdado e escribimos unha nova versión do método na subclase.

Hai dúas regras que deben obedecerse ao sobreescribir un método:

  • Mesmo número e tipo de parámetros
  • Mesmo tipo de retorno

Así, no seguinte exemplo, na clase ContaBancaria temos un método chamado visualizarSaldo() que amosa o número de conta bancaria e o saldo actual. Na subclase ContaAforro é posible que tamén queiramos visualizar a taxa de interese actual asignada á conta. Para lograr isto, simplemente sobreescribimos o método visualizarSaldo() na subclase ContaAforro, empregando o prefixo override:

class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

class ContaAforro:ContaBancaria {
    var tipoInteres: Float = 0.0
    
    func calcularInteres() -> Float {
        return tipoInteres * saldoConta
    }
    
    override func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
        print("A taxa de interés é: \(tipoInteres)")
    }
}

var aforro = ContaAforro(numero: 12345, saldo: 1000.0)
aforro.tipoInteres = 0.10
aforro.visualizarSaldo()
//Imprime:
//O número de conta é: 12345
//Saldo actual: 1000.0
//A taxa de interés é: 0.1

super #

Tamén é posible facer unha chamada ao método anulado na superclase desde dentro dunha subclase. Dentro da subclase (ContaAforro), poderiamos chamar ao método visualizarBalance() da superclase (ContaBancaria) para mostrar o número de conta e o saldo, antes de que se amose a taxa de interese, eliminando así unha maior duplicación de código:

class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

class ContaAforro:ContaBancaria {
    var tipoInteres: Float = 0.0
    
    func calcularInteres() -> Float {
        return tipoInteres * saldoConta
    }
    
    override func visualizarSaldo(){
        super.visualizarSaldo()
        print("A taxa de interés é: \(tipoInteres)")
    }
}

var aforro = ContaAforro(numero: 12345, saldo: 1000.0)
aforro.tipoInteres = 0.10
aforro.visualizarSaldo()
//Imprime:
//O número de conta é: 12345
//Saldo actual: 1000.0
//A taxa de interés é: 0.1

Inicializar Subclases #

Como é frecuente cometer erros, vou a incidir na inicialización das subclases con outro exemplo, neste caso baseado na clase ContaAforro.

A clase ContaAforro herda o método de inicialización init da clase principal ContaBancaria.

A clase ContaAforro necesita o seu propio init para garantir que a propiedade tipoInteres se inicialice:

class ContaBancaria {
    var numConta: Int = 0
    var saldoConta: Float = 0.0
    
    init(numero: Int,saldo: Float) {
        numConta = numero
        saldoConta = saldo
    }
    
    func visualizarSaldo(){
        print("O número de conta é: \(numConta)")
        print("Saldo actual: \(saldoConta)")
    }
}

class ContaAforro:ContaBancaria {
    var tipoInteres: Float = 0.0
    
    init(numero: Int,saldo: Float,tipo: Float) {
        super.init(numero: numero, saldo: saldo)
        tipoInteres = tipo
    }
    
    func calcularInteres() -> Float {
        return tipoInteres * saldoConta
    }
    
    override func visualizarSaldo(){
        print("A taxa de interés é: \(tipoInteres)")
        super.visualizarSaldo()
    }
}

var aforro = ContaAforro(numero: 12345, saldo: 1000.0, tipo: 0.10)
aforro.visualizarSaldo()
//Imprime:
//O número de conta é: 12345
//Saldo actual: 1000.0
//A taxa de interés é: 0.1

Para evitar posibles problemas de inicialización, o método init da superclase (ContaBancaria) sempre debe chamarse despois de que se completen as tarefas de inicialización para a subclase (ContaAforro).

extension – Extender unha clase #

As extensións pódense utilizar para engadir características como métodos, inicializadores, propiedades calculadas e subíndices a unha clase existente sen necesidade de crear e facer referencia a unha subclase.

Isto é particularmente poderoso cando se utilizan extensións para agregar funcionalidade ás clases integradas da linguaxe Swift e os frameworks SDK de iOS.

Supoñamos que necesitamos agregar un par de propiedades calculadas adicionais á clase Double:

extension Double {
    var cadrado: Double{
        return self *  self
    }
    var cubo: Double{
        return self *  self * self
    }
}

var numero: Double = 2.0
print("O cadrado de \(numero) é \(numero.cadrado)")
print("O cubo de \(numero) é \(numero.cubo)")
//Imprime:
//O cadrado de 2.0 é 4.0
//O cubo de 2.0 é 8.0

As extensións proporcionan unha forma rápida de ampliar a funcionalidade dunha clase sen necesidade de usar subclases :

  • Con todo, as subclases aínda teñen algunhas vantaxes sobre as extensións
  • Non é posible, por exemplo, anular a funcionalidade existente dunha clase usando unha extensión e as extensións non poden conter propiedades almacenadas.

Evitar herdar dunha clase #

En ocasións queremos restrinxir que se poida herdar dunha clase. Para facelo ésó temos que usar a keyword final diante de nosa clase

No seguinte exemplo, imos crear a clase Animal e imos indicar que ningunha outra clase pode herdar dela:

final class Animal {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

Se agora creamos unha nova clase chamada Can e tentamos herdar Animal, recibimos un erro do compilador:

class Animal {
    var nome: String
    var idade: Int
    
    init(nome: String, idade: Int) {
        self.nome = nome
        self.idade = idade
    }
    
    func son() {
        print("Sonido do Animal")
    }
}

class Can: Animal {
}

/ RESULTADO 👇
// Error: inheritance from a final class 'Animal'

Share Your Valuable Opinions