Clases en Kotlin: herencia, implementación, abstractas y abiertas (open)

- Andrés Cruz

In english
Clases en Kotlin: herencia, implementación, abstractas y abiertas (open)

En la entrada anterior que hablamos sobre sobre los constructores, propiedades y métodos constructores principales y secundarios, además dimos ejemplo sobre cómo emplear las propiedades (atributos en Java), definir los métodos GET y SET de las propiedades establecidos por defecto sobre las clases en Kotlin; todo esto en la entrada anterior:

Clases en Kotlin: clases vacías, constructores y propiedades

Continuando la temática anterior, en esta entrada hablaremos sobre la herencia, implementación, abstractas y un nuevo tipo llamado abiertas (open) sobre las clases en Kotlin.

Clases abiertas (Open class) y herencia en Kotlin

Lo primero que trataremos aquí son como crear una clase en Kotlin y como son declaradas dichas clases en Kotlin; las clases en Kotlin son por defecto declaradas como finales, lo que significa que no podemos heredar de una clase que creemos como en este ejemplo:

class Padre
class Hija : Padre()

Porque el compilador de Kotlin nos daría un error; las clases en Kotlin heredan por defecto de una superclase llamada Any; por lo tanto, la clase llamada Padre hereda por defecto de la clase Any:

class Padre // de manera implicita hereda de la clase Any

Si queremos heredar de una clase que definamos, debemos declararla como una "open class" o clase abierta; esta anotación tiene un comportamiento opuesto que tienen los finals en las clases en Java y por ende nos permite realizar la herencia, empleando la anotación siguiente manera:

open class Padre
class Hija : Padre()

Esto es una diferencia importante con Java, en donde podemos heredar de una clase sin necesidad de incorporar una palabra reservada que especifique que queremos que podamos heredar de la misma; esto es un principio muy importante en Kotlin para ayudarnos a mantener organizados nuestras clases, ya que con agregar open en nuestras clases estamos indicando que queremos diseñar dicha clase para que pueda ser heredada, lo que a la final permite facilitar la lectura del código de alguna aplicación realizada con Kotlin, independientemente si es una aplicación para Android, o para otra plataforma; también es importante señalar que no podemos heredar de múltiples clases o herencia múltiple.

Declarando clases geométricas y creando objetos en Kotlin

Esto sería lo esencial aquí para heredar de clases, por lo demás podemos hacer lo mismo que hacemos en otros lenguajes de programación; por ejemplo, el siguiente esquema es válido para declarar una serie de clases para especificar formas geométricas que es lo que veremos a continuación:

open class Forma(val nombre: String) {
   open fun area() = 0.0
}

class Circulo(nombre: String, val radius: Double): Forma(nombre)

En el ejemplo anterior definimos una clase llamada Forma que tendría las primitivas para especificar las formas geométricas, que en nuestro caso solo es un nombre de la forma geométrica, y una función que sobreescribimos para especificar el área de la forma geométrica.

Luego creamos una clase Circulo y su constructor, que para el caso del círculo debemos agregar un parámetro extra que es el radio del mismo:

class Circulo(name: String, val radius: Double)

Luego, como queremos que nuestra clase Circulo herede las propiedades de Forma, especificamos la herencia indicando el nombre de la clase a heredar luego de indicar los dos puntos, en donde referenciamos al constructor de la clase Forma (vendría siendo como emplear la palabra reservada super en Java):

class Circulo(name: String, val radius: Double): Forma(nombre)

Ahora podemos sobrescribir el método área para nuestro circulo:

Para también sobrescribir cualquier propiedad o método de la clase heredada, debemos especificar la palabra reservada open sobre dicho propiedad o método.

open class Forma(val nombre: String) {
open fun area() = 0.0
}

class Circulo(nombre: String, val radio: Double): Forma(nombre) {
override fun area() = Math.PI * Math.pow(radio, 2.0)
}

fun main(args: Array<String>) {
val circulo = Circulo("Circulo", 4.0)
println(circulo.nombre)
println(circulo.radio)
println(circulo.area())
}

Clases abstractas en Kotlin

Las clases abstractas prácticamente siguen el mismo principio que en otros lenguajes de programación orientados a objetos como Java; las clases abstractas son aquellas que no tienen implementación y se usa la palabra reservada abstract en las clases para tal fin.

Cómo puedes recordar en este caso no es necesario especificar un valor de retorno para nuestro método area igual que hicimos anteriormente ya que el mismo va a ser sobrescrito en nuestra clase hija:

abstract class Forma(val nombre: String) {
   abstract fun area(): Double

   fun printName(){
       println("el nombre es: ${nombre}")
   }
}

class Circulo(nombre: String, val radio: Double): Forma(nombre) {
   override fun area() = Math.PI * Math.pow(radio, 2.0)
}

fun main(args: Array<String>) {
   val circulo = Circulo("Circulo", 4.0)
   println(circulo.nombre)
   println(circulo.radio)
   println(circulo.area())
   println(circulo.printName())
}

Podemos emplear la palabra reservada en los métodos (fun) para especificar que la clase que la definan sobreescriba dichos métodos, o podemos implementar algunos dentro de la clase abstracta y de esta forma, poder emplearlos en la clase que definen dicha clase abstracta.

Como en cualquier otro lenguaje, debemos sobrescribir cualquier método o propiedad que contenga la palabra reservada abstract en la clase hija.

Las interfaces en Kotlin

Como último concepto de programación orientada a objetos que trataremos en esta entrada, las interfaces se declaran con la palabra reservada interface; debes saber que todos los métodos son públicos y pueden ser sobrescritos; también podemos definir métodos con cuerpo o contenido, lo que es una gran ventaja, ya que no tenemos que estar repitiendo código en otras clases y podemos definirlo como base en la interfaz.

Una característica primordial de las interfaces es que no implementan métodos, ni constructores, lo que es una distinción con la herencia o clases abstractas que vimos anteriormente.

Las interfaces no encapsulan datos, solo definen cuales son los métodos que se deben implementar en las clases que la definan.

Por lo tanto nuestro ejemplo quedaría de la siguiente forma:

interface Forma {
   fun area(): Double
   fun printName();
}

class Circulo(val nombre: String, val radio: Double): Forma {
   override fun area() = Math.PI * Math.pow(radio, 2.0)
   override fun printName(){}
}

fun main(args: Array<String>) {
   val circulo = Circulo("Circulo", 4.0)
   println(circulo.nombre)
   println(circulo.radio)
   println(circulo.area())
}

También podemos definir una implementación por defecto en nuestra interfaz:

interface Forma {
   fun area(): Double = 0.0
   fun printName();
}

Puedes consultar la documentación oficial para más detalle: Kotlin: Classes and Inheritance; recuerda consultar otras entradas sobre Kotlin que tenemos en DesarrolloLibre desde la etiqueta Kotlin.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.