Supongamos que tenemos que conocer si Helmut es una persona instruida, esto ocurre cuando leyó más de 20 libros y conoce más de 3 idiomas.
Tenemos esta implementación:
object helmut {
var librosLeidos = 0
const idiomasQueConoce = []
method leerLibro() { librosLeidos = librosLeidos + 1 }
method conocerIdioma(idioma) { idiomasQueConoce.add(idioma) }
method esInstruido() ...
}
Es un patrón frecuente la resolución del ejemplo agregando un nuevo atributo en helmut
:
object helmut {
...
var instruido = false
method esInstruido() = instruido
}
Una solución similar puede ser definir instruido como una property, el efecto es el mismo. Esto tiene algunas desventajas:
Para mantener consistente el estado de Helmut, deberíamos codificar su comportamiento de la siguiente manera:
object helmut {
var librosLeidos = 0
const idiomasQueConoce = []
var instruido = false
method leerLibro() {
librosLeidos = librosLeidos + 1
// sincronizo esInstruido...
instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
}
method conocerIdioma(idioma) {
idiomasQueConoce.add(idioma)
// sincronizo esInstruido...
instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
}
method esInstruido() = instruido
}
Por supuesto, podemos extraer un método aparte, igualmente estaremos enviando el mismo mensaje en ambos casos:
object helmut {
...
method leerLibro() {
librosLeidos = librosLeidos + 1
self.actualizoInstruido()
}
method conocerIdioma(idioma) {
idiomasQueConoce.add(idioma)
self.actualizoInstruido()
}
method actualizoInstruido() {
instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
}
...
}
Otra variante similar consiste en que el método instruido haga algo como:
method leerLibro() {
librosLeidos = librosLeidos + 1
self.esInstruido()
}
method conocerIdioma(idioma) {
idiomasQueConoce.add(idioma)
self.esInstruido()
}
method esInstruido() {
instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
return instruido
}
lo cual agrega más desventajas:
instruido
da lo mismo si es una variable de instancia (atributo) o una variable local del método esInstruido
, solo se asigna para ser retornadaLa alternativa más sencilla es descartar todos los atributos que pueden calcularse, de esa manera evitamos sincronizar el estado de Helmut:
object helmut {
var librosLeidos = 0
const idiomasQueConoce = []
method leerLibro() { librosLeidos = librosLeidos + 1 }
method conocerIdioma(idioma) { idiomasQueConoce.add(idioma) }
method esInstruido() = librosLeidos > 20 && idiomasQueConoce.size() > 3
}
Aquí vemos cómo tenemos
leerLibro
, conocerIdioma
esInstruido
En general, intentar guardarse cosas de antemano para “optimizar” es un smell. Como dijo Donald Knuth, premature optimization is the root of all evil.
Sin embargo, sí hay ocasiones donde guardarse un resultado de antemano es aceptable:
son señales en los que tener un cálculo como atributo es justificable, algo que es improbable que ocurra en cursos iniciales de programación OO.