latest

Introducción al lenguaje Dart. Avanzando

En la sesión anterior sobre aspectos de nivel intermedio del lenguaje Dart aprendimos como implementar clases con jerarquías de herencia simple y como trabajar con objetos polimórficos. Pudimos comprobar que salvo algún cambio de sintaxis no hay variaciones respecto de Java.

Vamos a continuar el curso de Flutter con la implementación de clases abstractas e interfaces que nos permitirá crear jerarquías con herencia múltiple en Dart. Comencemos pues...

Clases Abstractas en Dart

Tendremos ocasiones en las que nuestros esquemas de herencia posean funcionalidades comunes para todos los objetos (subclases). Si bien, en los casos en los que cada una de estas funcionalidades reciba una implementación distinta dependiente de la subclase, de modo que NO podamos definir una implementación base común desde la clase padre, en esas circunstancias, los métodos abstractos son el recurso propicio.

Si no te ha quedado claro lo que acabamos de contar en el párrafo anterior vamos a tratar de explicarlo con un ejemplo sencillo. Regresemos al esquema de clases que presentamos en la sesión anterior, en el que teníamos una clase padre Empleado y dos clases heredadas para caracterizar dos empleados diferentes, Operario y Directivo.  Tanto los operarios como los directivos cobran un salario mensual como empleados de nuestra hipotética compañía, sin embargo, los criterios para calcular el importe de dicho salario son completamente diferentes para unos y otros y NO podemos generalizar como se obtiene dicha cuantía para un objeto Empleado. Si bien, en este punto tenemos claro que todos los empleados sean del tipo que sean tienen que cobrar un salario por su trabajo, por tanto, cobrarSalario() es una funcionalidad declarada de la clase Empleado, aunque sus definiciones / implementaciones concretas se realicen en las subclases Operario y Directivo.

Concretamente, en nuestro ejemplo el Operario tiene un sueldo base mensual (fijo) que se calcula como el 70% de su base salarial, a esto se suman tres conceptos variables:

  1. Un 10% de su base salarial añadido como "plus de nocturnidad" si el Operario tiene turno de noche.
  2. Un 20% de su base salarial si el Operario lleva 10 o más años trabajando en la empresa.
  3. Un plus de 25€ por cada hora extra contabilizada fuera de la jornada.

En el caso del Directivo tiene un sueldo base mensual(fijo) que se calcula como el 80% de su base salarial:

  1. Un 20% de su base salarial si el Directivo lleva 6 o más años trabajando en la empresa.
  2. Una cantidad en concepto de "incentivos mensuales", variable y que depende del volumen de venta y del margen mensual de ventas logrado.

Vamos a reimplementar el ejemplo en Dart.

void main(){
  	/* 
  	 * La siguiente sentencia provocaría un error de compilación 
  	 * porque no podemos instanciar objetos de clases abstractas.
  	 * 
  	 * var persona1 = new Empleado("Juan");
  	 */
  	Operario persona2 = new Operario("Alicia",1700,true,false);
  	persona2.horasExtraUltimoMes = 10;
  	persona2.plusAntiguedad = true;
  	print("\n$persona2");
  
  	var persona3 = new Operario("Rafael",1600,false,true);
  	persona3.horasExtraUltimoMes = 8;
  	persona3.plusAntiguedad = true;
  	print("\n$persona3");
  
  	Empleado persona4 = new Operario("Luis",1800,true,false);
  	print("\n$persona4");
  	// La siguiente sentencia provocaría un error de compilación
  	// print(persona4.altoRiesgoLaboral);
  	print( (persona4 as Operario).altoRiesgoLaboral );
  
  	var persona5 = new Directivo("Manuel",2400);
  	persona5.incentivosUltimoMes = 350;
  	print("\n$persona5");
}
abstract class Empleado{
	// Propiedades comunes de un empleado...
  	// El guíón bajo es un modificador de acceso 
  	// (indica que es una propiedad privada) 
  	String _nombre;
  	double _baseSalarial;
    bool _plusAntiguedad;

  	// Constructor
  	Empleado(this._nombre,this._baseSalarial){
      _plusAntiguedad = false;
    }
  
  	// Método abstracto
  	double calcularSalario();
  	
  	// Métodos
  	//Getters & Setters
  	String get nombre => _nombre;
  	set nombre(String nombre) => _nombre = nombre;
  	
    double get baseSalarial => _baseSalarial;
  	set baseSalarial(double baseSalarial) => _baseSalarial = baseSalarial;
  
  	bool get plusAntiguedad => _plusAntiguedad;
  	set plusAntiguedad(bool plusAntiguedad) => 
      _plusAntiguedad = plusAntiguedad;
  
  	@override  	
  	String toString(){
      return "Soy $_nombre y mi último sueldo mensual fue " 
      "de ${calcularSalario()}€. Soy un empleado"; 
    }
}
class Operario extends Empleado{
  	bool _altoRiesgoLaboral;
    bool _plusNocturnidad;
    int _horasExtraUltimoMes;
  
  	// Constructor
  	Operario(String _nombre, double _baseSalarial, 
             this._altoRiesgoLaboral,
             this._plusNocturnidad) : super(_nombre,_baseSalarial){
      _horasExtraUltimoMes = 0;
    }
  
  	// Método abstracto (implementación)
  	@override
  	double calcularSalario(){
      return _baseSalarial * .7 + _horasExtraUltimoMes * 25.0 
        + (_plusNocturnidad==true ? _baseSalarial * .1: 0)
        + (_plusAntiguedad==true ? _baseSalarial * .2: 0);
    }
  
    // Métodos
  	// Getters & Setters
  	bool get altoRiesgoLaboral => _altoRiesgoLaboral;
  	set altoRiesgoLaboral(bool altoRiesgoLaboral) => 
      _altoRiesgoLaboral = altoRiesgoLaboral;
  
  	bool get plusNocturnidad => _plusNocturnidad;
  	set plusNocturnidad(bool plusNocturnidad) => 
      _plusNocturnidad = plusNocturnidad;
  
  	int get horasExtraUltimoMes => _horasExtraUltimoMes;
  	set horasExtraUltimoMes(int horasExtraUltimoMes) => 
      _horasExtraUltimoMes = horasExtraUltimoMes;
  
  	@override
  	String toString(){
      String riesgo = _altoRiesgoLaboral == true ? 
        "uso herramientas peligrosas.": "NO uso herramientas peligrosas.";
      return super.toString() + " de tipo operario y " + riesgo;
    }
}
class Directivo extends Empleado{
	  // Propiedades de un directivo...
    double _incentivosUltimoMes;
  	// Constructor
  	Directivo(String _nombre, 
              double _baseSalarial) : super(_nombre,_baseSalarial){
      _incentivosUltimoMes = .0;
    }
  
   	// Método abstracto (implementación)
  	@override
  	double calcularSalario(){
      return _baseSalarial * .8 + _incentivosUltimoMes 
        + (_plusAntiguedad==true ? _baseSalarial * .2: 0);
    }
  
	  // Métodos  
  	// Getters & Setters
  	double get incentivosUltimoMes => _incentivosUltimoMes;
  	set incentivosUltimoMes(double incentivosUltimoMes) => 
      _incentivosUltimoMes = incentivosUltimoMes;
  
  	@override
  	String toString(){
      return super.toString() + " de tipo directivo.";
    }
}

Como ya os aconsejamos con anterioridad, os animamos a probar los ejemplos de código en DartPad. En este caso la salida por consola del código anterior es la siguiente.

Soy Alicia y mi último sueldo mensual fue de 1780€. Soy un empleado de 
tipo operario y uso herramientas peligrosas.

Soy Rafael y mi último sueldo mensual fue de 1800€. Soy un empleado de 
tipo operario y NO uso herramientas peligrosas.

Soy Luis y mi último sueldo mensual fue de 1260€. Soy un empleado de 
tipo operario y uso herramientas peligrosas.
true

Soy Manuel y mi último sueldo mensual fue de 2270€. Soy un empleado de 
tipo directivo.

Como podéis apreciar en el código, Empleado se ha implementado como una clase abstracta anteponiendo la palabra clave abstract a class Empleado. La clase debe declararse de tipo abstracto en el momento que integra al menos un método abstracto, en este caso, calcularSalario().

Por tanto, se expone la firma del método abstracto double calcularSalario(); en la clase padre abstracta pero no se define su implementación, que se hará de manera particular en las subclases. En este caso concreto, Operario tendrá su propia implementación de calcularSalario()en base a las condiciones anteriormente comentadas; y de manera parecida, Directivo también tendrá sobreescrito el método calcularSalario()como indica la anotación @overridepertinente.

Se ha declarado una serie de propiedades nuevas (en las tres clases) para poder acometer el cálculo salarial siguiendo lo expuesto en el ejemplo. Del mismo modo que en Java, el compilador impide que podamos crear instancias de clases abstractas.

Interfaces en Dart

Las interfaces son el mecanismo que tienen los lenguajes orientados a objetos como Java o Dart para implementar la herencia múltiple, dado que no permiten "extender" de más de una clase padre. Podemos entender una interfaz como "un contrato de funcionalidades" que ha de cumplir la clase que lo implementa. Una clase puede adquirir más de un contrato de funcionalidades, es decir, implementar más de una interfaz. Expliquémoslo con el ejemplo anterior, concretamente centrándonos en los directivos de nuestra hipotética compañía.

Los directivos pueden asumir uno o más Planes Estratégicos como partes de sus funciones. Así pues, un directivo del Departamento de Recursos Humanos (DirectivoRRHH) va a implementar un PlanEstrategicoRRHH focalizado en realizar funciones de control de los empleados (controlar()), motivación (motivar()) y contratación de talento (contratar()).  Además, va a seguir un PlanEstrategicoOrganizacion que dicta el CEO de la compañía para todos sus directivos y que recoge estrategias de organización en sus respectivos departamentos. Nuestro DirectivoRRHHaplicará las estrategias de organización para organizar() su departamento de Recursos Humanos.

Por poner otro ejemplo, puede haber un directivo del departamento de Compras (DirectivoCompras) que implementará el PlanEstrategicoOrganizacion para organizar() el equipo de empleados encargado de las compras a proveedores; y también, implementará un PlanEstrategicoCompras focalizado en planificarCompras() y realizar las operaciones de compra (comprar()).

Vamos a ver como implementaríamos lo dicho hasta ahora en el código de ejemplo.

void main(){
  	DirectivoRRHH directivoRRHH = new DirectivoRRHH("Manuel",2400);
  	directivoRRHH.incentivosUltimoMes = 350;
  	print("\n$directivoRRHH");
  
  	Directivo directivoCompras = new DirectivoCompras("Jesús",2800);
  	directivoCompras.incentivosUltimoMes = 450;
  	print("\n$directivoCompras");
}
abstract class Empleado{
	// Propiedades comunes de un empleado...
  	// El guíón bajo es un modificador de acceso 
  	// (indica que es una propiedad privada) 
  	String _nombre;
  	double _baseSalarial;
    bool _plusAntiguedad;

  	// Constructor
  	Empleado(this._nombre,this._baseSalarial){
      _plusAntiguedad = false;
    }
  
  	// Método abstracto
  	double calcularSalario();
  	
  	// Métodos
  	//Getters & Setters
  	String get nombre => _nombre;
  	set nombre(String nombre) => _nombre = nombre;
  	
    double get baseSalarial => _baseSalarial;
  	set baseSalarial(double baseSalarial) => _baseSalarial = baseSalarial;
  
  	bool get plusAntiguedad => _plusAntiguedad;
  	set plusAntiguedad(bool plusAntiguedad) => 
      _plusAntiguedad = plusAntiguedad;
  
  	@override  	
  	String toString(){
      return "Soy $_nombre y mi último sueldo mensual fue " 
      "de ${calcularSalario()}€. Soy un empleado"; 
    }
}
class Directivo extends Empleado{
	  // Propiedades de un directivo...
    double _incentivosUltimoMes;
  	// Constructor
  	Directivo(String _nombre, 
              double _baseSalarial) : super(_nombre,_baseSalarial){
      _incentivosUltimoMes = .0;
    }
  
   	// Método abstracto (implementación)
  	@override
  	double calcularSalario(){
      return _baseSalarial * .8 + _incentivosUltimoMes 
        + (_plusAntiguedad==true ? _baseSalarial * .2: 0);
    }
  
	  // Métodos  
  	// Getters & Setters
  	double get incentivosUltimoMes => _incentivosUltimoMes;
  	set incentivosUltimoMes(double incentivosUltimoMes) => 
      _incentivosUltimoMes = incentivosUltimoMes;
  
  	@override
  	String toString(){
      return super.toString() + " de tipo directivo.";
    }
}
abstract class PlanEstrategicoRRHH{
  	void controlar(List<Empleado> empleados);
  	void motivar(List<Empleado> empleados);
  	void contratar(Empleado empleado);
}
abstract class PlanEstrategicoCompras{
  	void planificarCompras();
  	void comprar();
}
abstract class PlanEstrategicoOrganizacion{
  	void organizar(List<Empleado> empleadosDepartamento);
} 
class DirectivoRRHH extends Directivo 
  implements PlanEstrategicoRRHH, PlanEstrategicoOrganizacion{
  	// Propiedades de un directivo de RRHH...
  	// Constructor
  	DirectivoRRHH(String _nombre, 
              double _baseSalarial) : super(_nombre,_baseSalarial);
  
  	// Métodos:
  	@override
  	void controlar(List<Empleado> empleados){
      print("Funciones de control de un grupo "
      	"de empleados");
    }
  	@override
  	void motivar(List<Empleado> empleados){
      print("Funciones de motivación de un grupo "
      	"de empleados");
    }
  	@override
  	void contratar(Empleado empleado){
      print("Funciones de contratación de un empleado");
    }
  	@override
  	void organizar(List<Empleado> empleadosDepartamento){
      print("Funciones de organización de empleados "
        "de un departamento de RRHH");
    }
} 
class DirectivoCompras extends Directivo 
  implements PlanEstrategicoCompras, PlanEstrategicoOrganizacion{
  	// Propiedades de un directivo de RRHH...
  	// Constructor
  	DirectivoCompras(String _nombre, 
              double _baseSalarial) : super(_nombre,_baseSalarial);
  
  	// Métodos:
  	@override
  	void planificarCompras(){
      print("Funciones relacionadas con la planificación "
      	"de compras a proveedores");
    }
  	@override
  	void comprar(){
      print("Funciones relacionadas con la compra a proveedrores");
    }
  	@override
  	void organizar(List<Empleado> empleadosDepartamento){
      print("Funciones de organización de empleados "
        "de un departamento de compras");
    }
} 

Como podéis comprobar tenemos dos nuevas subclases DirectivoRRHH y DirectivoCompras ambas heredan del Empleado de tipo Directivo, además ambas implementan la interfaz PlanEstrategicoOrganizacion. Además, DirectivoRRHH asume estrategias del PlanEstrategicoRRHH, mientras que DirectivoCompras asume estrategias del PlanEstrategicoCompras.

A diferencia de Java no hay una palabra clave interface , sino que las interfaces se declaran como abstract class. Las clases que implementan la/s interfaz/ces utilizan la palabra clave implements (igual que en Java). Si no sobrescriben todos los métodos abstractos de las interfaces que implementan estas clases tendrán que ser abstractas al no cumplir por completo su "contrato de funcionalidades".

Un pequeño apunte final sobre el ejemplo, fijaros como las estructuras de datos de tipo List que hemos usado en algunos de los argumentos de los métodos de las interfaces soportan tipos genéricos. En este caso, simplemente se ha forzado a que las listas contengan objetos de tipo Empleado con List<Empleado>.

En este punto damos por finalizada la breve introducción al lenguaje Dart (aspectos básicos, intermedios y avanzados). En la siguiente sesión de este curso de Flutter empezaremos "Conociendo Flutter", primero realizando una descripción técnica del SDK y, posteriormente, detallando los pasos para su instalación y configuración en Windows.

Author image
Iván González is postdoctoral researcher at the Castilla-La Mancha University.
Ciudad Real (Spain)