Guía del Lenguaje
Herencia

Herencia

Crea subclases para añadir o reemplazar funciones.

Una clase puede heredar métodos, propiedades y otras características de otra clase. Cuando una clase hereda de otra, la clase que hereda se conoce como subclase, y la clase de la que hereda se conoce como superclase. La herencia es un comportamiento fundamental que diferencia a las clases de otros tipos en Swift.

Las clases en Swift pueden llamar y acceder a métodos, propiedades y subíndices pertenecientes a su superclase y pueden proporcionar sus propias versiones reemplazantes de esos métodos, propiedades y subíndices para refinar o modificar su comportamiento. Swift te ayuda a asegurarte de que tus sustituciones (overrides) sean correctas al comprobar que la definición de una sustitución tiene una definición correspondiente en la superclase.

Las clases también pueden añadir observadores de propiedades a las propiedades heredadas para ser notificadas cuando el valor de una propiedad cambia. Los observadores de propiedades pueden añadirse a cualquier propiedad, independientemente de si se definió originalmente como propiedad almacenada o calculada.

Definición de una clase base

Cualquier clase que no herede de otra clase se conoce como clase base.

El siguiente ejemplo define una clase base llamada Vehiculo. Esta clase base define una propiedad almacenada llamada velocidadActual, con un valor predeterminado de 0.0 (infiriendo un tipo de propiedad Double). El valor de la propiedad velocidadActual es utilizado por una propiedad String de sólo lectura llamada descripcion para crear una descripción del vehículo.

La clase base Vehiculo también define un método llamado hacerRuido. Este método en realidad no hace nada para una instancia base de Vehiculo, pero será personalizado por subclases de Vehiculo más adelante:

class Vehiculo {
    var velocidadActual = 0.0
 
    var descripcion: String {
        return "viajando a \(velocidadActual) kilómetros por hora"
    }
 
    func hacerRuido() {
        // No hace nada - Un vehículo cualquiera no necesariamente hace un ruido
    }
}

Para crear una nueva instancia de Vehiculo, usa la sintaxis de inicializador, la cual se escribe como un nombre de tipo seguido de paréntesis vacíos:

let algunVehiculo = Vehiculo()

Una vez creada una nueva instancia de Vehiculo, puedes acceder a su propiedad descripcion para imprimir una descripción legible por humanos de la velocidad actual del vehículo:

print("Vehículo: \(algunVehiculo.descripcion)")
// Imprime "Vehículo: viajando a 0.0 kilómetros por hora"

La clase Vehiculo define características comunes para un vehículo arbitrario, pero no es muy útil por sí misma. Para hacerla más útil, debes refinarla para describir tipos de vehículos más específicos.

Creación de subclases

Crear una subclase significa basar una nueva clase en una clase existente. La subclase hereda características de la clase existente, las cuales puedes refinar posteriormente. También puedes añadir nuevas características a la subclase.

Para indicar que una subclase tiene una superclase, escribe el nombre de la subclase antes del nombre de la superclase, separados por dos puntos:

class AlgunaSubclase: AlgunaSuperclase {
    // La definición de la subclase va aquí
}

El siguiente ejemplo define una subclase llamada Bicicleta, con Vehiculo como su superclase:

class Bicicleta: Vehiculo {
    var tieneCanasta = false
}

La nueva clase Bicicleta automáticamente obtiene todas las características de Vehiculo —como sus propiedades velocidadActual y descripcion— y su método hacerRuido().

Además de las características que hereda, la clase Bicicleta define una nueva propiedad almacenada llamada tieneCanasta, con un valor predeterminado de false (infiriendo un tipo de propiedad Bool para la propiedad).

Por defecto, ninguna nueva instancia de Bicicleta que crees tendrá una canasta. Puedes asignar true a la propiedad tieneCanasta de un instancia particular de Bicicleta después de que dicha instancia haya sido creada:

let bicicleta = Bicicleta()
 
bicicleta.tieneCanasta = true

También puedes modificar la propiedad heredada velocidadActual de una instancia de Bicicleta, y consultar descripcion, la propiedad heredada por la instancia:

bicicleta.velocidadActual = 15.0
 
print("Bicicleta: \(bicicleta.descripcion)")
// Imprime "Bicicleta: viajando a 15.0 kilómetros por hora"

Es posible crear subclases a partir de otras subclases. El siguiente ejemplo crea una subclase de Bicicleta para una bicicleta de dos plazas conocida como «tándem»:

class Tandem: Bicicleta {
    var numeroActualDePasajeros = 0
}

Tandem hereda todas las propiedades y métodos de Bicicleta, quien a su vez hereda todas las propiedades y métodos de Vehiculo. La subclase Tandem también añade una nueva propiedad almacenada llamada numeroActualDePasajeros, con un valor predeterminado de 0.

Si creas una instancia de Tandem, podrás trabajar con cualquiera de sus propiedades nuevas y heredadas, y consultar descripcion, la propiedad de sólo lectura que hereda de Vehiculo:

let tandem = Tandem()
 
tandem.tieneCanasta = true
tandem.numeroActualDePasajeros = 2
tandem.velocidadActual = 22.0
 
print("Tándem: \(tandem.descripcion)")
// Imprime "Tándem: viajando a 22.0 kilómetros por hora"

Sustituciones (Overriding)

Una subclase puede proporcionar su propia implementación personalizada de un método de instancia, método de tipo, propiedad de instancia, propiedad de tipo, o subíndice que, de otro modo, heredaría de una superclase. Esto se conoce como sustitución (overriding).

Para sustituir una característica que de otro modo se heredaría, precede la definición sustituyente con la palabra clave override. De este modo, queda claro que tu intención es proporcionar una sustitución y que no estás agregando una definición idéntica por error. Una sustitución accidental puede causar un comportamiento inesperado y cualquier sustitución sin la palabra clave override es diagnosticada como un error cuando el código es compilado.

La palabra clave override también le indica al compilador de Swift que compruebe que la superclase de la clase sustituyente (o uno de sus padres) tiene una declaración que coincide con la que has proporcionado para la sustitución. Esta comprobación asegura que tu definición sustituyente es correcta.

Acceso a métodos, propiedades, y subíndices de superclases

Cuando se proporciona una sustitución de un método, propiedad o subíndice para una subclase, a veces es útil utilizar la implementación existente de la superclase como parte de la sustitución. Por ejemplo, puedes refinar el comportamiento de dicha implementación existente o almacenar un valor modificado en una variable heredada existente.

Cuando resulte apropiado, podrás acceder a la versión de la superclase de un método, propiedad o subíndice utilizando el prefijo super:

  • Un método sustituido llamado algunMetodo() puede llamar a la versión de la superclase de algunMetodo() llamando a super.algunMetodo() dentro de la implementación del método sustituido.
  • Una propiedad sustituida llamada algunaPropiedad() puede acceder a la versión de la superclase de algunaPropiedad() como super.algunaPropiedad() dentro de la implementación del getter o setter sustituyente.
  • Un subíndice sustituido para algunIndice puede acceder a la versión de la superclase del mismo subíndice como super[algunIndice] desde dentro de la implementación del subíndice sustituido.

Sustitución de métodos

Puedes sustituir un método de una instancia o tipo heredados para ofrecer una implementación personalizada o alternativa del método dentro de tu subclase.

El siguiente ejemplo define una nueva subclase de Vehiculo llamada Tren, la cual sustituye al método hacerRuido() que Tren hereda de Vehiculo:

class Tren: Vehiculo {
    override func hacerRuido() {
        print("Chu Chu")
    }
}

Si creas una nueva instancia de Tren y llamas a su método hacerRuido(), puedes ver que se llama a la versión del método de la subclase Tren:

let tren = Tren()
 
tren.hacerRuido()
// Imprime "Chu Chu"

Sustitución de propiedades

Puedes sustituir una instancia heredada o una propiedad de tipo para proporcionar tus propios getter y setter personalizados para dicha propiedad, o para añadir observadores de propiedad que permitan que la propiedad sustituida observe cuándo cambia el valor de la propiedad subyacente.

Sustitución de getters y setters de propiedades

Puedes proporcionar un getter personalizado (y un setter, si cabe lugar) para sustituir cualquier propiedad heredada, independientemente de si dicha propiedad se implementó como una propiedad almacenada o calculada en donde fue definida. La calidad de ser almacenada o calculada de una propiedad heredada no es conocida por una subclase; esta sólo sabe que la propiedad heredada tiene un nombre y un tipo determinados. Siempre debes indicar tanto el nombre como el tipo de la propiedad que estás redefiniendo, para permitirle al compilador comprobar que tu sustitución coincide con una propiedad de la superclase con el mismo nombre y tipo.

Puedes presentar una propiedad heredada de sólo lectura como una propiedad de lectura-escritura proporcionando un getter y un setter en la sustitución de la propiedad de la subclase. Sin embargo, no puedes presentar una propiedad heredada de lectura-escritura como una propiedad de sólo lectura.

El siguiente ejempo define una nueva clase llamada Carro, la cual es una subclase de Vehiculo. La clase Carro introduce una nueva propiedad almacenada llamada marcha, con un valor predeterminado entero de 1. La clase Carro también redefine la propiedad descripcion que hereda de Vehiculo, para proporcionar una descripción personalizada que incluye la marcha actual:

class Carro: Vehiculo {
    var marcha = 1
 
    override var descripcion: String {
        return super.descripcion + " en marcha \(marcha)"
    }
}

La sustitución de la propiedad descripcion comienza con el llamado a super.descripcion, el cual devuelve la propiedad descripcion de la clase Vehiculo. La versión de descripcion de la clase Carro añade texto extra al final de descripcion para proporcionar información sobre la marcha actual.

Si creas una instancia de la clase Carro y estableces sus propiedades marcha y velocidadActual, podrás ver que su propiedad descripcion devuelve la descripción personalizada definida dentro de la clase Carro:

let carro = Carro()
 
carro.velocidadActual = 25.0
carro.marcha = 3
 
print("Carro: \(carro.descripcion)")
// Carro: viajando a 25.0 kilómetros por hora en marcha 3

Sustitución de observadores de propiedades

Puedes utilizar la sustitución de propiedades para agregar observadores de propiedad a una propiedad heredada. Esto te permitirá recibir notificaciones cuando el valor de una propiedad heredada cambie, independientemente de cómo dicha propiedad haya sido implementada originalmente. Para obtener más información sobre los observadores de propiedad, consulta Observadores de propiedad.

El siguiente ejemplo define una nueva clase llamada CarroAutomatico, la cual es una subclase de Carro. La clase CarroAutomatico representa un carro con una caja de cambios automática, la cual selecciona, automáticamente, una marcha apropiada para usar basándose en la velocidad actual:

class CarroAutomatico: Carro {
    override var velocidadActual: Double {
        didSet {
            gear = Int(velocidadActual / 10.0) + 1
        }
    }
}

Cada vez que fijes la propiedad velocidadActual de una instancia de CarroAutomatico, el observador didSet de la propiedad establecerá la propiedad marcha de la instancia a la elección de una marcha apropiada para la nueva velocidad. Específicamente, el observador de propiedad elige una marcha que es el nuevo valor de velocidadActual dividido por 10, redondeado al entero más cercano, más 1. Una velocidad de 35.0 produce una marcha de 4:

let automatico = CarroAutomatico()
 
automatico.velocidadActual = 35.0
 
print("CarroAutomatico: \(automatico.descripcion)")
// CarroAutomatico: viajando a 35.0 kilómetros por hora en marcha 4

Prevención de sustituciones

Puedes prevenir que un método, propiedad o subíndice sea sustituido, marcándolo como final. Para ello, escribe el modificador final antes de la palabra clave de introducción del método, propiedad o subíndice (por ejemplo, final var, final func, final class func y final subscript).

Cualquier intento de sustituir un método, propiedad o subíndice final en una subclase se reportará como error de compilación. Los métodos, propiedades o subíndices que añadas a una clase en una extensión también pueden marcarse como finales dentro de la definición de la extensión.

Puedes marcar toda una clase como final escribiendo el modificador final antes de la palabra clave class en su definición de clase (final class). Cualquier intento de crear una subclase a partir de una clase final se reportará como error de compilación.