top of page

7 Mixins

 


Aparte de heredar código de una super-clase, una clase de Scala puede también importar código de uno o varios mixins. Para un programador Java quizá la manera más fácil de entender lo que son los mixins es verlos como interfaces que también pueden contener código. En Scala, cuando una clase hereda de un mixin, implementa el interfaz de ese mixin, y hereda todo el código contenido en el mixin.

 

Para ver la utilidad de los mixins, echemos un vistazo a un ejemplo clásico: objetos ordenados. A menudo es útil poder comparar objetos de una clase dada entre ellos, por ejemplo para ordenarlos. En Java, los objetos que que son comparables implementan
el interfaz Comparable. En Scala, podemos hacerlo algo mejor que en Java definiendo nuestro equivalente a Comparable como un mixin. que llamaremos Ord.

 

Cuando se comparan objetos pueden ser útiles seis predicados diferentes: menor, menor o igual, igual, no igual, mayor o igual y mayor. Pero definir todos ellos es fastidioso, especialmente cuando cuatro de esos seis se pueden expresar usando los otros dos. Es decir dados los predicados igual y menor(por ejemplo), uno puede expresar los demás. En Scala todas estas observaciones se pueden plasmar de manera sencilla declarando el siguientemixin(mediante la palabra clave trait):


trait Ord {
    def < (that: Any): boolean
    def <=(that: Any): boolean = (this < that) || (this == that)
    def > (that: Any): boolean = !(this <= that)
    def >=(that: Any): boolean = !(this < that)
}

 


Esta definición crea tanto un nuevo tipo llamado Ord, que juega el mismo papel que el interfaz Comparable de Java, como implementación por defecto para tres predicados en términos de un cuarto, abstracto. Los predicados para la igualdad y la
desigualdad no aparecen aquí puesto que por defecto están presentes en todos los objetos.

 

El tipo de dato Any usado en el ejemplo anterior es el tipo que es super-tipo de todos los demás tipos en Scala. Puede verse como una versiónmás general del tipo de Java Object, puesto que también es un super-tipo de los tipos básicos como int, float, etc.

 

Para hacer comparables los objetos de una clase, es por tanto suficiente con definir los predicados que comprueban la igualdad y la inferioridad, y añadir la clase Ord anterior. Como ejemplo, vamos a definir una clase Date que represente fechas en el calendario Gregoriano. Estas fechas se componen de un día, un mes y un año, que representaremos en los tres casos como enteros. Así pues empezamos con la definición de la clase Date como sigue:


class Date(y: int, m: int, d: int) extends Ord {
    def year = y
    def month = m
    def day = d
    override def toString(): String = year + ""
        + month + ""
        + day


La parte importante aquí es la declaración extends Ord que sigue al nombre y a los parámetros de la clase. Declara que la clase Date hereda de la clase Ord como un mixin.


Ahora redefinimos el método equals, heredado de Object, para que compare correctamente fechas comparando individualmente sus campos. La implementación por defecto de equals no es válida porque al igual que en Java compara los objetos físicamente. Obtenemos la siguiente definición:


override def equals(that: Any): boolean =
    that.isInstanceOf[Date] && {
    val o = that.asInstanceOf[Date]
    o.day == day && o.month == month && o.year == year
}

 


Este método hace uso de los métodos predefinidos isInstanceOf y asInstanceOf. El primero de ellos, isInstanceOf, corresponde al operador instanceof de Java, y devuelve verdadero si y sólo si el objeto sobre el que se aplica es una instancia del tipo dado.

 

El segundo, asInstanceOf, corresponde al operador de conversión de tipos de Java (casting): Si el objeto es una instancia del tipo dado, se ve como tal, en caso contrario se lanza una excepción ClassCastException. Finalmente, el último método por definir es el que comprueba la inferioridad. Hace uso de otro método predefinido, error, que lanza una excepción con el mensaje de
error dado.


def <(that: Any): boolean = {
    if (!that.isInstanceOf[Date])
    error("cannot compare " + that + " and a Date")
    val o = that.asInstanceOf[Date]
    (year < o.year) ||

    (year == o.year && (month < o.month ||
    (month == o.month && day < o.day)))
}


Esto completa la definición de la clase Date. Las instancias de esta clase se pueden ver como fechas o como objetos comparables. Es más, todas ellas definen los seis tipos de comparación mencionados antes: equals y < porque aparecen directamente en la definición de la clase Date y los demás porque se heredan del mixin Ord.


Por supuesto los mixins son útiles en otras situaciones distintas de la mostrada aquí, pero discutir en profundidad todas sus aplicaciones está fuera del alcance de este documento.

bottom of page