jueves, 5 de octubre de 2017

Un resumen de Scala for the Impatient, parte 31


Sobre escribir métodos abstractos en Traits

Del ejemplo anterior, podemos deducir que TimestampLogger y ShortLogger extienden de ConsoleLogger, la cual extiende del trait Logger el cual no tiene implementación para el método log

trait Logger {
    def log(msg: String) // This method is abstract
}

Por lo tanto

trait TimestampLogger extends Logger {
    override def log(msg: String) { // Overrides an abstract method
        super.log(s"${java.time.Instant.now()} $msg") // Is super.log defined?
    }
}

Esto no debería compilar porque super.log no fue definido pero scala lo toma como que la clase TimestampLogger es abstracta y solo se puede utilizar si se mixea con una clase que implemente este método

Traits para interfaces ricas.

Un trait puede tener muchos métodos útiles que dependan de unos pocos abstractos. Un ejemplo de esto es el Iterator que define docenas de métodos en términos de los métodos abstractos next y hasnext. Vamos a construir métodos que nos permitan imprimir diferentes tipos de logs     

trait Logger {
def log(msg: String)
def info(msg: String) { log(s"INFO: $msg") }
def warn(msg: String) { log(s"WARN: $msg") }
def severe(msg: String) { log(s"SEVERE: $msg") }
}

Note que estamos combinando métodos abstractos con implementaciones concretas. 

abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) severe("Fondos insuficientes")
else …
}
}

Un campo concreto en Traits

Un campo de un traits puede ser concreto o abstracto. Si se asigna un valor al campo este será concreto, 

trait ShortLogger extends Logger {
val maxLength = 15 // A concrete field
abstract override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg
else s"${msg.substring(0, maxLength – 3)}...")
}
}

 Una clase puede mixear con el campo maxLenght

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
     var interest = 0.0
     def withdraw(amount: Double) {
          if (amount > balance) log("Fondos insuficientes")
          else …
     }
}

Note que la clase SavingsAccount tiene la variable interest, pero no balance, balance viene de Account

class Account {
     var balance = 0.0
}

La maquina virtual java no permite heredar campos de diferentes interfaces o super clases, por lo que el compilador scala debe copiarlos. 

Campos abstractos en Traits

Los campos que no son inicializados son abstractos, y deben ser sobreescritos por la subclase concreta. 

Por ejemplo: 

trait ShortLogger extends Logger {
     val maxLength: Int // An abstract field
     
      abstract override def log(msg: String) { ... }

      super.log(
                if (msg.length <= maxLength) msg
                else s"${msg.substring(0, maxLength – 3)}...")
                // El campo maxLength es utilizado en la implementación. 
}

Cuando se utiliza este trait se debe asignar un valor a maxLength: 

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
     val maxLength = 20 // No es necesario escribir override
}

Ahora todos los mensajes de registro se truncan después de 20 caracteres.

Esta forma de proporcionar valores para los parámetros de rasgo es particularmente útil cuando se construyen objetos sobre la marcha. Volvamos a nuestra cuenta de ahorros original:

class SavingsAccount extends Account with Logger { ... }

Ahora, podemos truncar los mensajes en una instancia de la siguiente manera:

val acct = new SavingsAccount with ConsoleLogger with ShortLogger {
      val maxLength = 20
}