TypeScript Mixins, herencia sin excesos

Uno de los mejores aspectos que trae Typescript, es el acercamiento al mundo de OOP. Pero esto no implica que Typescript no pueda traer patrones de otras herramientas, y es esta la oportunidad para hablar de Mixins.

Que es un Mixin?

La idea general de un Mixin, es la de extender una clase mediante clases mas pequeñas y reusables. Para lograr esto hace uso de la clasica herencia de clases, en combinacion con el uso de generics.

Entendiendo el patron

Supongamos que tenemos la siguiente clase que representa a un vehículo:

class Vehiculo {
  id: string;
  propietario: Persona;
}

Sabemos que existen diferentes clases de vehiculos y que sobre dichos vehiculos se pueden realizar diferentes acciones, como abrir puertas, reparar llantas, ingresar pasajeros, etc.

class Vehiculo {
  // ... id, nombre
  
  abrirPuertaConductor() {
    //
  }
  
  despegar() {
    //
  }
  
  contarCascos() {
    //
  }
}

Pero este planteamiento nos supone algunas preguntas, sobre todo si recurrimos a los principios SOLID, por ejemplo hablemos de Segregacion de Interfaces.

Segregación de Interfaz: Los clientes de un programa dado, solo deberían conocer de aquellos métodos que realmente usan, y no de aquellos que no necesiten.

Basados en este precepto, por ejemplo, para una motocicleta no aplica el método despegar(), o para un Yate no aplica el método contarCascos().

Así que pongámonos manos a la obra, y llevemos la teoría a la práctica.

Ya sabemos que tenemos una clase principal, Vehiculo, que define nuestra base de trabajo, y contiene lo realmente común para nuestra tarea.

class Vehiculo {
  id: string;
  propietario: Persona;
}

Y ahora viene la parte interesante, la definicion de un mixing:

// Constructor genérico
type Constructor = new (...args[]: any[]) => any;

// Añadir métodos de un vehiculo volador
function VehiculoVolador<T extends Constructor> (Base: T) {
  return class VehiculoVolador extends Base {
    despegar() {
      console.log('Vehiculo que Despegar');
    }
    
    aterrizar() {
      console.log('Vehiculo que Aterriza');
    }
  }
}


// Obtiene una clase derivada que incluye metodos para un vehiculo de carga
function VehiculoDeCarga<T extends Constructor> (Base: T) {
  return class VehiculoDeCarga extends Base {
    obtenerCapacidadCarga() {
      console.log('Obtener Capacidad de Carga');
    }
    
    cargar() {
      console.log('Cargar Mercancia');
    }
    
    descargar() {
      console.log('Descargar Mercancia');
    }
  }
}

// Obtiene una clase derivada que incluye metodos para un vehiculo de pasajeros
function VehiculoDePasajeros<T extends Constructor> (Base: T) {
  return class VehiculoDePasajeros extends Base {
    obtenerCapacidadPasajeros() {
      console.log('Obtener Capacidad de Pasajeros');
    }
    
    ascenderPasajeros() {
      console.log('Ascender Pasajeros');
    }
    
    descenderPasajeros() {
      console.log('Descender Pasajeros');
    }
  }
}

// El Mixing devuelve una nueva clase que comparte los metodos y propiedades

// Obtener un vehiculo volador que a su vez sea un vehiculo de carga
const AvionDeCarga = VehiculoDeCarga(VehiculoVolador(Vehiculo));

// Obtener un vehiculo volador que a su vez sea un vehiculo de pasajeros
const AvionDePasajeros = VehiculoDePasajeros(VehiculoVolador(Vehiculo));

// Instanciamos normalmente la clase resultante
const avionCarga = new AvionDeCarga();
avionCarga.id = 'NH247';
avionCarga.obtenerCapacidadCarga();

const avionPasajeros = new AvionDePasajeros();
avionPasajeros.id = 'AV1245';
avionPasajeros.ascenderPasajeros();

Y de esta manera es posible definir nuevas clases con metodos comunes, de forma más granular y sin violar el principio de segregacion de interfaz, sin dar más informacion que la necesaria en cada objeto.

También te podría gustar...

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *