1. Información general
En este tutorial, discutiremos cómo definir e implementar interfaces en Kotlin.
También veremos cómo una clase puede implementar múltiples interfaces. Esto ciertamente puede causar conflictos, y aprenderemos el mecanismo que tiene Kotlin para resolverlos.
2. Interfaces en Kotlin
Una interfaz es una forma de proporcionar una descripción o contrato para clases en programación orientada a objetos. Pueden contener propiedades y funciones de forma abstracta o concreta, según el lenguaje de programación. Repasaremos los detalles de las interfaces en Kotlin.
Las interfaces en Kotlin son similares a las interfaces en muchos otros lenguajes como Java. Pero tienen una sintaxis específica, revisémoslas en las siguientes subsecciones.
2.1. Definición de interfaces
Comencemos por definir nuestra primera interfaz en Kotlin:
interface SimpleInterface
Esta es la interfaz más simple que está completamente vacía. También se conocen como interfaces de marcador .
Agreguemos ahora algunas funciones a nuestra interfaz:
interface SimpleInterface { fun firstMethod(): String fun secondMethod(): String { return("Hello, World!") } }
Hemos agregado dos métodos a nuestra interfaz previamente definida:
- Uno de ellos llamado f irstMethod es un método abstracto
- Mientras que el otro llamado s econdMethod tiene una implementación predeterminada.
Sigamos adelante y agreguemos algunas propiedades a nuestra interfaz ahora:
interface SimpleInterface { val firstProp: String val secondProp: String get() = "Second Property" fun firstMethod(): String fun secondMethod(): String { return("Hello, from: " + secondProp) } }
Aquí hemos agregado dos propiedades a nuestra interfaz:
- Uno de ellos llamado firstProp es de tipo String y es abstracto
- El segundo llamado secondProp también es del tipo cadena pero define una implementación para su descriptor de acceso.
Tenga en cuenta que las propiedades de una interfaz no pueden mantener el estado . Entonces, la siguiente es una expresión ilegal en Kotlin:
interface SimpleInterface { val firstProp: String = "First Property" // Illegal declaration }
2.2. Implementación de interfaces
Ahora que hemos definido una interfaz básica, veamos cómo podemos implementar eso en una clase en Kotlin:
class SimpleClass: SimpleInterface { override val firstProp: String = "First Property" override fun firstMethod(): String { return("Hello, from: " + firstProp) } }
Tenga en cuenta que cuando definimos SimpleClass como una implementación de SimpleInterface , solo tenemos que proporcionar la implementación de propiedades y funciones abstractas . Sin embargo, también podemos anular cualquier propiedad o función previamente definida.
Anulemos ahora todas las propiedades y funciones definidas previamente en nuestra clase:
class SimpleClass: SimpleInterface { override val firstProp: String = "First Property" override val secondProp: String get() = "Second Property, Overridden!" override fun firstMethod(): String { return("Hello, from: " + firstProp) } override fun secondMethod(): String { return("Hello, from: " + secondProp + firstProp) } }
Aquí, hemos anulado la propiedad secondProp y la función secondFunction que se definieron previamente en la interfaz SimpleInterface .
2.3 Implementación de interfaces mediante delegación
La delegación es un patrón de diseño en la programación orientada a objetos para lograr la reutilización del código a través de la composición en lugar de la herencia . Si bien esto es posible de implementar en muchos lenguajes, como Java, Kotlin tiene soporte nativo para la implementación a través de la delegación .
Si comenzamos con una interfaz y una clase básicas:
interface MyInterface { fun someMethod(): String } class MyClass() : MyInterface { override fun someMethod(): String { return("Hello, World!") } }
Hasta ahora nada nuevo. Pero ahora, podemos definir otra clase que implemente MyInterface a través de la delegación:
class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface
MyDerivedClass espera un delegado como argumento que realmente implementa la interfaz MyInterface .
Veamos cómo podemos llamar a una función de la interfaz mediante delegado:
val myClass = MyClass() MyDerivedClass(myClass).someMethod()
Aquí hemos creado una instancia de MyClass y lo usamos como el delegado para llamar a funciones de la interfaz en MyDerivedClass, que en realidad nunca implementó estas funciones directamente.
3. Herencia múltiple
La herencia múltiple es un concepto clave en el paradigma de la programación orientada a objetos. Esto permite que una clase herede características de más de un objeto padre, como una interfaz, por ejemplo .
Si bien esto proporciona más flexibilidad en el modelado de objetos, viene con su propio conjunto de complejidades. Uno de ellos es el "problema de los diamantes".
Java 8 tiene sus propios mecanismos para abordar el problema del diamante, al igual que cualquier otro lenguaje que permita la herencia múltiple.
Veamos cómo lo aborda Kotlin a través de interfaces.
3.1. Heredar múltiples interfaces
Comenzaremos definiendo dos interfaces simples:
interface FirstInterface { fun someMethod(): String fun anotherMethod(): String { return("Hello, from anotherMethod in FirstInterface") } } interface SecondInterface { fun someMethod(): String { return("Hello, from someMethod in SecondInterface") } fun anotherMethod(): String { return("Hello, from anotherMethod in SecondInterface") } }
Tenga en cuenta que ambas interfaces tienen métodos con el mismo contrato.
Ahora definamos una clase que hereda de estas dos interfaces:
class SomeClass: FirstInterface, SecondInterface { override fun someMethod(): String { return("Hello, from someMethod in SomeClass") } override fun anotherMethod(): String { return("Hello, from anotherMethod in SomeClass") } }
Como podemos ver, SomeClass implementa tanto FirstInterface como SecondInterface . Aunque sintácticamente esto es bastante simple, hay un poco de semántica que requiere atención aquí. Repasaremos esto en la siguiente subsección.
3.2. Resolviendo conflictos
When implementing multiple interfaces, a class may inherit a function which has a default implementation for the same contract in multiple interfaces. This raises the problem of invocation for this function from an instance of the implementing class.
To resolve this conflict, Kotlin requires the subclass to provide an overridden implementation for such functions to make the resolution explicit.
For example, SomeClass above implements anotherMethod. But, if it didn't, Kotlin wouldn't know whether to invoke First or SecondInterface's default implementation of anotherMethod. SomeClass must implement anotherMethod for this reason.
However, someMethod is a bit different since there is actually no conflict. FirstInterface doesn't provide a default implementation for someMethod. That said, SomeClass still must implement it because Kotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.
3.3. Resolving the Diamond Problem
A “diamond problem” occurs when two child objects of a base object describe a particular behavior defined by the base object. Now an object inheriting from both these child objects has to resolve which inherited behavior it subscribes to.
Kotlin's solution to this problem is through the rules defined for multiple inheritance in the previous sub-section. Let's define a few interfaces and an implementing class to present the diamond problem:
interface BaseInterface { fun someMethod(): String } interface FirstChildInterface: BaseInterface { override fun someMethod(): String { return("Hello, from someMethod in FirstChildInterface") } } interface SecondChildInterface: BaseInterface { override fun someMethod(): String { return("Hello, from someMethod in SecondChildInterface") } } class ChildClass: FirstChildInterface, SecondChildInterface { override fun someMethod(): String { return super.someMethod() } }
Here we have defined BaseInterface which declared an abstract function called someMethod. Both the interfaces FirstChildInterface and SecondChildInterface inherits from BaseInterface and implement the function someMethod.
Now as we implement ChildClass inheriting from FirstChildInterface and SecondChildInterface, it's necessary for us to override the function someMethod. However, even though we must override the method, we can still simply call super as we do here with SecondChildInterface.
4. Interfaces Compared to Abstract Classes in Kotlin
Abstract classes in Kotlin are classes which cannot be instantiated. This may contain one or more properties and functions. These properties and functions can be abstract or concrete. Any class inheriting from an abstract class must implement all inherited abstract properties and functions unless that class itself is also declared as abstract.
4.1. Differences Between Interface and Abstract Class
Wait! Doesn't that sound exactly like what an interface does?
Actually, at the outset, an abstract class is not very different from the interface. But, there are subtle differences which govern the choice we make:
- A class in Kotlin can implement as many interfaces as they like but it can only extend from one abstract class
- Properties in the interface cannot maintain state, while they can in an abstract class
4.2. When Should We Use What?
An interface is just a blueprint for defining classes, they can optionally have some default implementations as well. On the other hand, an abstract class is an incomplete implementation which is completed by the extending classes.
Typically interfaces should be used to define the contract, which elicits the capabilities it promises to deliver. An implementing class holds the responsibility of delivering those promises. An abstract class, however, should be used to share partial characteristics with extending classes. An extending class can take it further to complete it.
5. Comparison With Java Interfaces
With the changes to Java interface in Java 8, they have come very close to Kotlin interfaces. One of our previous articles captures the new features introduced in Java 8 including changes to the interface.
There are mostly syntactic differences between Java and Kotlin interfaces now. One difference which stands out is related to the keyword “override”. In Kotlin, while implementing abstract properties or functions inherited from an interface, it is mandatory to qualify them with the keyword “override“. There is no such explicit requirement in Java.
6. Conclusion
In this tutorial, we discussed Kotlin interfaces, how to define and implement them. Then we talked about inheriting from multiple interfaces and the conflict they may create. We took a look at how Kotlin handles such conflicts.
Finalmente, discutimos las interfaces en comparación con las clases abstractas en Kotlin. También hablamos brevemente sobre cómo se compara la interfaz de Kotlin con la interfaz de Java.
Como siempre, el código de los ejemplos está disponible en GitHub.