4.D Colecciones de objetos y miembros estáticos¶
Objetos que contienen objetos¶
Los objetos se pueden combinar: un curso tiene alumnos, una biblioteca tiene libros, una persona tiene cuentas. Es la relación de composición («tiene un»), y se resuelve guardando los objetos dentro de otro, normalmente en una lista.
Curso ◆────── 0..n ──────▶ Alumno
(guarda una lista) (nombre, nota)
class Alumno(val nombre: String, val nota: Int) {
init {
require(notaValida(nota)) { "nota fuera de rango" }
creados++
}
val aprobado: Boolean
get() = nota >= 5
override fun toString() = "$nombre ($nota)"
companion object {
var creados = 0
fun notaValida(nota: Int) = nota in 0..10
}
}
class Curso {
private val alumnos = mutableListOf<Alumno>()
val total: Int
get() = alumnos.size
fun matricular(alumno: Alumno) {
alumnos.add(alumno)
}
fun media(): Double {
var suma = 0
for (a in alumnos) {
suma += a.nota
}
return suma.toDouble() / alumnos.size
}
fun mejor(): Alumno {
var mejor = alumnos.first()
for (a in alumnos) {
if (a.nota > mejor.nota) {
mejor = a
}
}
return mejor
}
fun aprobados() = alumnos.filter { it.aprobado }
}
fun main() {
val curso = Curso()
curso.matricular(Alumno("Ana", 4))
curso.matricular(Alumno("Marta", 8))
curso.matricular(Alumno("Luis", 6))
println("Matriculados: ${curso.total}")
println("Media del curso: ${curso.media()}")
println("Mejor alumno: ${curso.mejor()}")
println("Aprobados: ${curso.aprobados().joinToString(", ") { it.nombre }}")
try {
Alumno("Error", 11)
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
}
println("Alumnos creados: ${Alumno.creados}")
}
Salida:
Matriculados: 3
Media del curso: 6.0
Mejor alumno: Marta (8)
Aprobados: Marta, Luis
Error: nota fuera de rango
Alumnos creados: 3
Qué conviene copiar de este diseño:
- El curso decide qué se puede hacer con su lista. Ofrece
matricular,media,mejoryaprobados, pero no entrega la lista para que cualquiera la modifique.for (a in alumnos)ofilter. La lista esprivate: desde fuera solo se puede matricular o consultar. - Cada clase se ocupa de lo suyo.
Alumnosabe si está aprobado;Cursosabe calcular la media y buscar al mejor.mainsolo orquesta. - Los recorridos son los de siempre (acumular una suma, buscar un máximo, filtrar), solo que ahora recorren objetos y consultan sus atributos y propiedades.
- Las reglas del objeto, en el objeto: la nota se valida en el constructor de
Alumno, así un alumno con nota 11 no llega a existir.
Miembros estáticos¶
Hasta ahora, cada atributo y cada método pertenecía a un objeto concreto. Un miembro estático pertenece a la clase, y es el mismo para todos los objetos. En el ejemplo hay dos:
creados: un contador compartido que cuenta cuántos alumnos se han creado (no tiene sentido guardarlo en cada alumno).notaValida: una función de utilidad que no necesita ningún alumno para funcionar.
Kotlin no tiene static: lo equivalente va dentro de un companion object: var creados = 0 y fun notaValida(...). Se usa con el nombre de la clase: Alumno.creados, Alumno.notaValida(5). Las funciones sueltas fuera de una clase (como main) tampoco necesitan objeto.
No abuses de lo estático
Un atributo estático es estado global: cualquiera puede cambiarlo y los resultados dependen de lo que haya ocurrido antes en el programa, lo que complica las pruebas. Úsalo para contadores sencillos, constantes y utilidades; no para guardar los datos de la aplicación.
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Devolver la lista interna para que otros la modifiquen | Ofrece métodos concretos, o devuelve una copia o una vista de solo lectura |
Buscar el máximo empezando con un valor «inventado» (como 0) |
Empieza con el primer elemento (y comprueba que la lista no esté vacía) |
| Dividir por el tamaño de una lista vacía | Comprueba antes que hay elementos |
| Poner datos de objetos concretos en un atributo estático | Un atributo estático es para lo que es común a todos |
Para practicar¶
Las colecciones de objetos y las relaciones entre clases se practican en U4.3 · POO II. Pasar de uno a otro muestra las mismas ideas en otro lenguaje.