Mixins y Traits

Qué son, en qué se parecen y en qué se diferencian

En este post vamos a ver qué son y en qué consisten los mixins y los traits, dos conceptos muy parecidos entre ellos y que algunas veces se confunden. Primero comentar que no todos los lenguajes los soportan. P. ej. Java (a partir de la versión 8) permite implementarlos en parte, lo mismo que C# usando métodos de extensión. Otros lenguajes como JavaScript, Swift, C++ o Ruby (entre muchos otros) permiten una implementación más completa, ya sea de forma más o menos directa. Pero antes de ver posibles implementaciones… ¿qué son y en qué se diferencian?

Mixins y traits

Ambos son un tipo de herencia múltiple. Es decir, si tienes un lenguaje que soporte herencia múltiple de por sí (como C++) entonces ambos conceptos vienen soportados por el lenguaje, aunque sea de forma implícita. Tanto mixins como traits son un mecanismo para añadir (“enchufar”) comportamiento a otras clases (u objetos). Veamos como podríamos definir un mixin en JavaScript:

var circleMixin = function() {
	this.area = function() { return this.radius * this.radius * Math.PI};
}

Un mixin no tiene sentido por si mismo, lo tiene cuando lo aplicamos a un objeto:

var foo = {radius: 20};
circleMixin.call(foo);
console.log(foo.area());

En este caso hemos aplicado el mixin circleMixin al objeto foo. Realmente JavaScript no tiene un mecanismo para definir mixins (hay otras posibles maneras de hacerlo), pero lo importante es la idea: el mixin circleMixin añade comportamiento al objeto (en este caso el método area()). Un mixin es pues un paso más que una interfaz: una interfaz define comportamiento y quien la implementa debe implementar dicho comportamiento, mientras que un mixin permite añadir un comportamiento a un objeto o una clase.

Veamos otra implementación, ahora usando Java 8 y los default methods en interfaces:

public interface CircleMixin  extends IRadius {
    default double getArea()  {
        return Math.PI * this.getRadius() * this.getRadius();
    }
}

Esta interfaz extiende la interfaz IRadius que proporciona el método getRadius. Esa extensión es necesaria para que this dentro de la interfaz sea un IRadius y por lo tanto podamos usar sus métodos. La interfaz IRadius podría ser como la siguiente:

public interface IRadius {
    double getRadius();
    void setRadius(double radius);
}

Y luego podríamos definir una clase Foo que implemente la interfaz IRadius y aplicarle el mixin:

public class Foo implements IRadius, CircleMixin {
    private double _radius;
    public double getRadius() {return _radius;}
    public void setRadius(double radius) { _radius = radius;}
}

Y para usar el mixin, nada más sencillo:

Foo foo = new Foo();
foo.setRadius(100.0);
double a = foo.getArea();

Bueno, supongo que ya véis cual es la idea de un mixin: ofrecer un comportamiento que es “enchufable” a varias clases (u objetos). Como decía antes es un tipo de herencia múltiple porque a una misma clase se le pueden enchufar varios mixins distintos y la clase adquiere el comportamiento definidos en todos ellos.

¿Y los traits? Pues los traits son muy parecidos a los mixins. El concepto es básicamente el mismo, esas son las diferencias fundamentales: 1. Un mixin puede tener estado, mientras que un trait no. Es decir los mixins además de métodos pueden definir campos. Los traits no. Sí, técnicamente hablando los default methods de Java 8 permiten implementar traits pero no mixins (¡Bien visto!). 2. Los mixins usan “resolución de conflictos implícita”, mientras que en los traits es explícita. Imagina dos mixins que definen el mismo método. ¿Qué es lo qué debería ocurrir cuando aplicamos ambos a la misma clase (u objeto)? Pues que uno de los dos debería “ganar” (habitualmente el primero o el último que se aplica(. Lo que siginifica que el orden en el que los mixins se aplican a una clase (u objeto) tiene importancia. En cambio este mismo escenario en traits, obligaría al desarrollador a seleccionar cual de los dos métodos es el que usa la clase.

Esas dos serían las diferencias fundamentales entre ambos conceptos. Fíjate que son “conceptos”: hemos visto un ejemplo de mixin en JavaScript y otro en Java 8 (realmente un trait), pero ni Java ni JavaScript tienen explícitamente estos conceptos. Hay otros lenguajes que sí que los definen explícitamente.

Ventajas generales de Mixins y traits

Básicamente la ventaja principal es que permiten aumentar la reutilización de código. Podemos enchufar el comportamiento definido en varias clases distintas, evitando así duplicidades de código. Permiten añadir fácilmente características adicionales a una clase sin modificar esta, con lo que ayudan a cumplir la O de SOLID.

Ventajas de Mixins y traits sobre la herencia múltiple

La herencia múltiple es poderosísima pero trae consigue un buen número de problemas, todos ellos relacionados con la ambigüedad. El principal (aunque no único) de esos problemas tiene que ver con la “herencia en diamante”, que se produce cuando tenemos dos clases B y C que heredan de A y luego creamos una clase D que hereda de B y C a la vez. La clase A puede definir un método foo, método que B puede redefinir y C no. En este caso D, el método foo que recibe por herencia la clase D, ¿cuál es? ¿El redefinido de B o el original de A (que recibe via C)? Otra pregunta a realizarse es que pasa con los constructores: El constructor de D llamará al de B y C y esos dos llamarán al de A. ¿En qué orden? ¿Como evitamos que el constructor de A se llame dos veces? Y todavía hay más problemas que pueden surgir con la herencia múltiple. Los traits evitan todos esos problemas de golpe: no tienen estado, por lo que no hay problemas de campos duplicados, no hay constructores a llamar y no hay posibles métodos duplicados. Y si los hay, ya hemos comentado antes que la resolución es explícita. Los mixins los evitan también, porque a pesar de tener estado, al ser la resolución implícita, en caso de métodos duplicados tan solo uno será añadido al comportamiento de la clase y lo mismo ocurriría en los campos que el mixin pudiese añadir.

En resúmen: los traits y los mixins ayudan a resolver los problemas de la herencia múltiple, evitando aquellos usos de ésta que puedan generar conflictos. En todo caso recuerda: son un tipo de herencia múltiple. Si el lenguaje soporta herencia múltiple, vas a poder implementar mixins o traits en él.

Y para finalizar: ambos conceptos (mixins y traits) son eso… conceptos. La mayoría de lenguajes no los incorporan de forma directa, pero en muchos de ellos se pueden “simular”, ya sea de forma total o parcial!