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:
- El constructor de la super clase se ejecutara primero.
- Luego el constructor de los traits, de izquierda a derecha.
- Por cada traits, el constructor del padre (si lo tuviera) primero.
- Si los traits tienen un constructor común, no ejecuta 2 veces el mismo constructor.
- 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:
- Account
- Logger (padre de FileLogger)
- FileLogger
- ShortLogger (tiene el padre en comun con FileLogger pero no vuelve a construirlo)
- 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.