Tipos anónimos
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/object-oriented-programming-kt
Puedes ejecutar el siguiente comando para crear el módulo
./gradlew setupAnonymousModule
Mientras se crean los archivos necesarios, puedes leer el código para saber qué está pasando.
import tasks.ModuleSetupTask
tasks.register<ModuleSetupTask>("setupAnonymousModule") {
description = "Creates the base module and files for the anonymous objects lesson"
module.set("anonymous")
doLast {
createFiles(
"anonymous",
test to "AnonymousObjectTest.kt"
)
createFiles(
"validation",
main to "Validator.kt",
test to "ValidatorTest.kt"
)
createFiles(
"people",
main to "Person.kt",
test to "AbstractPerson.kt",
test to "PersonTest.kt"
)
}
}
Preocúpate de que el plugin anonymous
esté aplicado en el archivo build.gradle.kts
de tu proyecto.
./gradlew setupAnonymousModule
Preocúpate de que el nuevo módulo esté incluido en el archivo settings.gradle.kts
.
En programación orientada a objetos, los tipos son representaciones de estructuras de datos que definen las propiedades y comportamientos de los objetos que los implementan. Generalmente, cuando hablamos de tipos en lenguajes como Kotlin, estamos acostumbrados a las clases e interfaces que tienen nombres y que se definen explícitamente en el código.
Sin embargo, existen situaciones en las que necesitamos un tipo temporal, que se utilice solo una vez o en un contexto muy específico. En lugar de crear una clase completa para esto, podemos utilizar tipos anónimos. En Kotlin, los tipos anónimos se implementan mediante object expressions, los cuales permiten crear instancias de clases sin tener que definirlas explícitamente en el código.
¿Qué es un tipo anónimo?
Un tipo anónimo es un tipo de objeto que no tiene un nombre asociado a él en el código fuente, pero que aún puede tener comportamiento y propiedades específicas. La principal ventaja de los tipos anónimos es que pueden ser definidos e instanciados en un solo paso, lo que evita la creación de clases innecesarias cuando el uso de un objeto es limitado a una pequeña porción de código.
En Kotlin, los object expressions permiten crear tipos anónimos. Un object expression es una expresión que crea un objeto único e instancia de una clase anónima, y puede tener propiedades y métodos, además de implementar interfaces o extender otras clases.
- Implementación rápida de interfaces o clases abstractas: Si necesitas implementar una interfaz o una clase abstracta pero solo una vez, un tipo anónimo es una buena opción para evitar definir clases adicionales en el proyecto.
- Modificación ligera de comportamiento: Si deseas modificar el comportamiento de una clase sin necesidad de crear una subclase explícita, puedes usar un tipo anónimo.
- Simplicidad: Los tipos anónimos son útiles cuando no tiene sentido crear una nueva clase que sea reutilizada en varias partes del programa.
Tipos anónimos con object expressions en Kotlin
Kotlin introduce una manera elegante de definir tipos anónimos a través de object expressions.
La sintaxis básica para un object expression es la siguiente:
- Código esencial
- Código completo
val anonymousObject = object {
val name = "Carol Danvers"
fun greet() = "Hello, $name"
}
anonymousObject.greet() shouldBe "Hello, Carol Danvers"
package com.github.username.anonymous
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class AnonymousObjectTest : FreeSpec({
"An anonymous object representing a person" - {
"when greeting" - {
"should return a greeting according to the person's name" {
val anonymousObject = object {
val name = "Carol Danvers"
fun greet() = "Hello, $name"
}
anonymousObject.greet() shouldBe "Hello, Carol Danvers"
}
}
}
})
En este ejemplo, anonymousObject
es una instancia de un tipo anónimo que tiene una propiedad name
y una función greet
. Este tipo no tiene un nombre, y no puede ser referenciado en otra parte del código.
Usar Object Expressions para Validaciones Personalizadas en Bibliotecas
Una de las razones más comunes para usar object expressions es crear implementaciones rápidas y reutilizables sin tener que definir una nueva clase completa.
Imagina que estás diseñando una biblioteca de software que permite la validación de datos de formularios. En lugar de definir una nueva clase para cada tipo de validador, puedes usar un object expression para implementar validadores personalizados de manera concisa.
- Código esencial
- Código completo
val emailValidator = object : Validator<String> {
override fun validate(value: String) =
value.contains("@") && value.contains(".")
}
val passwordValidator = object : Validator<String> {
override fun validate(value: String) =
value.length >= 8
}
package com.github.username.anonymous.validation
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
class ValidatorTest : FreeSpec({
"A validator" - {
"when validating an email" - {
val emailValidator = object : Validator<String> {
override fun validate(value: String) =
value.contains("@") && value.contains(".")
}
"should return true if the email is valid" {
emailValidator.validate("reachme@ravenhill.cl").shouldBeTrue()
}
"should return false if the email is invalid" {
emailValidator.validate("reachme@ravenhill").shouldBeFalse()
}
}
"when validating a password" - {
val passwordValidator = object : Validator<String> {
override fun validate(value: String) =
value.length >= 8
}
"should return true if the password is valid" {
passwordValidator.validate("password123").shouldBeTrue()
}
"should return false if the password is invalid" {
passwordValidator.validate("pass").shouldBeFalse()
}
}
}
})
package com.github.username.anonymous.validation
interface Validator<T> {
fun validate(value: T): Boolean
}
- Usamos object expressions para crear dos validadores: uno para correos electrónicos y otro para contraseñas.
- No es necesario crear clases separadas como
EmailValidator
oPasswordValidator
, lo que permite una implementación rápida y concisa.
Este patrón es útil cuando se necesita crear validaciones específicas para ciertos casos, pero no es necesario reutilizarlas más allá del contexto actual.
Usar object expressions para extender clases
Un uso interesante de las object expressions es cuando necesitamos crear prototipos rápidos en nuestros tests, especialmente cuando las subclases tienen comportamientos complejos que complican el proceso de prueba.
Imagina que tienes una clase abstracta que sigue un template pattern, y que el comportamiento de uno de los métodos depende de un método abstracto con lógica compleja. En lugar de crear una subclase real con toda su complejidad, podemos utilizar una object expression para generar una subclase anónima que implemente el método abstracto con un comportamiento simplificado para los tests.
- Código esencial
- Código completo
checkAll(arbPerson()) { person ->
person.greet() shouldBe
"Hello, ${person.name}. $WELCOME_MESSAGE"
}
private fun arbPerson(): Gen<Person> = Arb.name()
.map { name ->
object : AbstractPerson("$name") {
override fun someComplexBehavior() =
WELCOME_MESSAGE
}
}
package com.github.username.anonymous.people
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
import io.kotest.property.Arb
import io.kotest.property.Gen
import io.kotest.property.arbitrary.map
import io.kotest.property.arbs.name
import io.kotest.property.checkAll
private const val WELCOME_MESSAGE = "Welcome to the team!"
class PersonTest : FreeSpec({
"A person" - {
"when greeting" - {
"should return a greeting according to the person's name" {
checkAll(arbPerson()) { person ->
person.greet() shouldBe
"Hello, ${person.name}. $WELCOME_MESSAGE"
}
}
}
}
})
private fun arbPerson(): Gen<Person> = Arb.name()
.map { name ->
object : AbstractPerson("$name") {
override fun someComplexBehavior() =
WELCOME_MESSAGE
}
}
package com.github.username.anonymous.people
interface Person {
val name: String
fun greet(): String
}
package cl.ravenhill.anonymous.people
abstract class AbstractPerson(
override val name: String
) : Person {
override fun greet() =
"Hello, $name. ${someComplexBehavior()}"
protected abstract fun someComplexBehavior(): String
}
En este ejemplo, usamos una object expression para crear una subclase anónima de AbstractPerson
con un comportamiento simplificado para los tests.
Este enfoque es especialmente útil en la creación de bibliotecas de software, donde la capacidad de hacer prototipos y probar diferentes partes de una API sin depender de implementaciones reales reduce la complejidad y mejora la velocidad de desarrollo.
Beneficios y limitaciones de los tipos anónimos
Beneficios
- Prototipado rápido: Los tipos anónimos permiten crear implementaciones rápidas y temporales sin la necesidad de definir una clase formal. Esto es especialmente útil en pruebas y escenarios donde se requiere una implementación limitada o aislada.
- Simplicidad y ahorro de tiempo: Evita la sobrecarga de crear clases completas que solo se utilizarán en una pequeña porción del código, mejorando la legibilidad y reduciendo la cantidad de archivos necesarios.
- Flexibilidad en tests y validaciones: Los tipos anónimos permiten modificar y sobrescribir comportamientos en tests o validaciones sin alterar el diseño general del código, lo que facilita probar partes específicas del sistema.
- Encapsulación de comportamiento temporal: Facilita la encapsulación de comportamientos específicos en un contexto reducido, sin necesidad de hacer modificaciones a clases principales o reutilizar código innecesario.
Limitaciones
- Dificultad para depurar: Al no tener un nombre explícito, los tipos anónimos pueden dificultar la depuración, ya que rastrear la fuente del comportamiento específico puede ser más complicado, sobre todo en sistemas más grandes.
- Menor reutilización: Los tipos anónimos, al estar diseñados para usos únicos o contextos específicos, no pueden ser fácilmente reutilizados en otros lugares, lo que puede resultar en código duplicado si se requiere una funcionalidad similar en varias partes del programa.
- Desorden en código extenso: El uso excesivo de tipos anónimos puede generar desorden en el código, ya que la falta de nombres y estructura clara puede reducir la mantenibilidad a largo plazo.
¿Qué aprendimos?
En esta lección, exploramos el uso de tipos anónimos en Kotlin mediante object expressions, lo cual ofrece una forma poderosa de crear instancias rápidas y concisas sin necesidad de definir clases formales. Aprendimos cómo esta técnica puede ser especialmente útil en contextos de pruebas y validaciones, donde se requiere una implementación limitada o temporal. También revisamos cómo los tipos anónimos permiten encapsular comportamientos específicos en pequeños contextos, evitando la sobrecarga de clases innecesarias.
Puntos clave:
- Los tipos anónimos son ideales para implementaciones rápidas de interfaces o clases abstractas cuando solo se necesita una instancia en un contexto específico.
- Object expressions en Kotlin permiten crear objetos con propiedades y métodos sin necesidad de una clase formal, facilitando la creación de prototipos.
- Los tipos anónimos son útiles en situaciones donde se necesita modificar comportamientos temporalmente, como en validaciones o pruebas de software.
- Si bien proporcionan flexibilidad y simplicidad, su uso excesivo puede complicar la depuración y reducir la reutilización del código.
Este enfoque es una herramienta valiosa para situaciones donde la creación de una clase completa sería innecesaria, permitiendo que el código sea más eficiente y focalizado en resolver el problema inmediato. Sin embargo, como vimos en las limitaciones, es importante utilizarlos con moderación para evitar complicaciones en el mantenimiento del código a largo plazo.
Bibliografías Recomendadas
- 📚 "Classes, objects, and interfaces". (2017). Dmitry Jemerov & Svetlana Isakova, en Kotlin in action, (pp. 67–102.) Manning Publications Co..