martes, 5 de diciembre de 2017

Un resumen de Scala for the Impatient, parte 32

Orden de construcción de trait

Como las clases, los traits pueden ser construidos y estos pueden construir sus propiedades y ejecutar métodos:

trait FileLogger extends Logger {
val out = new PrintWriter("app.log") // Part of the trait’s constructor
out.println(s"# ${java.time.Instant.now()}") // Also part of the constructor
def log(msg: String) { out.println(msg); out.flush() }
}


Esto se ejecutara en el constructor de cualquier objeto que incorpore este Trait.

El orden sera el siguiente: 
  1. El constructor de la super clase se ejecutara primero. 
  2. Luego el constructor de los traits, de izquierda a derecha. 
  3. Por cada traits, el constructor del padre (si lo tuviera) primero. 
  4. Si los traits tienen un constructor común, no ejecuta 2 veces el mismo constructor. 
  5. Luego de que todos los traits son construidos, la subclase es construida. 

Por ejemplo: 

class SavingsAccount extends Account with FileLogger with ShortLogger

El orden de construcción sería: 
  1. Account
  2. Logger (padre de FileLogger)
  3. FileLogger
  4. ShortLogger (tiene el padre en comun con FileLogger pero no vuelve a construirlo)
  5. SavingsAccount

Inicializando campos en los Traits

Los traits no pueden tener constructores con parámetros. Cada trait tiene un constructor sin parámetros. Esta limitación puede ser un problema para traits que necesitan ser adaptados para ser útiles. Por ejemplo el trait FileLogger seria útil poder definir el nombre del archivo.

val acct = new SavingsAccount with FileLogger("myapp.log")

Una posible solución es que el nombre de archivo sea un campo abstracto.

trait FileLogger extends Logger {
val filename: String
val out = new PrintStream(filename)
def log(msg: String) { out.println(msg); out.flush() }
}

La clase puede sobreescribir el filename pero el problema es que el constructor del trait se ejecuta antes por lo tanto no va funcionar. Esto se puede resolver con lo que se indico en el siguiente post

Aplicando esto nuestra clase sería:

val acct = new { // Early definition block after new
val filename = "myapp.log"
} with SavingsAccount with FileLogger

Otra alternativa es hacer que out sea lazy (es decir que se llame al constructor de out la primera vez que se utilice)

trait FileLogger extends Logger {
val filename: String
lazy val out = new PrintStream(filename)
def log(msg: String) { out.println(msg) } // No override needed
}

Cuando se llame al constructor de out el campo filename ya va estar seteado. Pero los valores lazy son un poco más ineficientes dado que se deben chequear al ser utilizados.

 Traits que extienden clases :

Como se puede ver un trait puede extender otro trait de esta manera podremos tener nuestra jerarquía de traits. Menos común un traits puede extender una clase. Esa clase se convierte en una superclase de cualquier clase que se mezcle con el trait.

Veamos un ejemplo: 

trait LoggedException extends Exception with ConsoleLogger {
    def log() { log(getMessage()) }
}

Una LoggerException tiene un método log que logea el mensaje. Por lo que vemos puede acceder al método getMessage que es de la clase Exception. 

Ahora podemos mix esto con el trait: 

class UnhappyException extends LoggedException { // This class extends a trait
    override def getMessage() = "arggh!"
}

¿Qué pasa si nuestra clase ya extiende otra clase? Eso está bien, siempre que sea una subclase de la superclase del trait. Por ejemplo,

class UnhappyException extends IOException with LoggedException

Pero si nuestra clase extiende de otra clase que no es hija o no es la clase padre del trait, esta clase no puede mezclarse con este trait. Por ejemplo: 

class UnhappyFrame extends JFrame with LoggedException
// Error: Unrelated superclasses

La clase no puede extender de Jframe y de Exception.