Flutter DART Clases

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • Como comenté anteriormente este curso está pensado para alumnos que ya tienen conocimientos previos en algún lenguaje de programación orientado a objetos.
En esta sección se va a explicar las diferencias y características con respecto a otros lenguajes orientados a objetos en lo que se refiere al uso de clases y objetos,


  • En Dart no es necesario realizar el new cuando instanciamos una clase.
Esto se emplea mucho en Flutter ya que todo en él se basa en crear widgets (Clases) y por tanto aparecería de forma continua new´s por todos lados.
Sin embargo, si vamos a emplear clases creadas por nosotros, se recomienda hacer uso de new para diferenciarlo de la llamada a una función.


Veamos un ejemplo, con conceptos de otros lenguajes de programación orientado a objetos como el empleo del método toString:
class Vehiculo{
  
  int? anoCompra; // Necesitamos indicar que admite valores nulos, ya que al instanciar no le damos un valor por defecto
  String? matricula;
  String? marca;
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
}
void main() {

  final veh1 = new Vehiculo();
  veh1.marca='Opel';
  veh1.matricula='1234GHZ';
  veh1.anoCompra = 2018;
  
  print(veh1);
  
  final veh2 = Vehiculo(); // No necesitamos new, pero se recomienda si son clases nuestras
  veh2.marca='Peugot';
  veh2.matricula='6543HZZ';
  veh2.anoCompra = 2020;
  
  print(veh2);
  
}



Constructores

  • Funcionan igual que en el lenguaje Java.
Veamos un ejemplo de uso:


class Vehiculo{
  
  int? anoCompra; // Necesitamos indicar que admite valores nulos, ya que al instanciar no le damos un valor por defecto
  String? matricula;
  String? marca;
  
  Vehiculo(int anoCompra, String matricula, String marca){
    this.anoCompra = anoCompra;
    this.marca = marca;
    this.matricula = matricula;
  }
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
}
void main() {

  final veh1 = new Vehiculo(2018,'Opel','1234GHZ');  
  print(veh1);
  
  final veh2 = Vehiculo(2020,'Peugot','6543HZZ'); 
  print(veh2);
  
}
Fijarse que un constructor no es más que un método y por tanto podemos emplear todo lo aprendido con los parámetros (opciones, posicionales,...)
Ejercicio propuesto: Modifica el ejercicio anterior y haz con los parámetros puedan ser enviados en cualquier orden especificando el nombre del parámetro en la llamada. Haz que todos ellos sean obligatorios.



  • En el ejemplo anterior vimos que al definir las propiedades de la clase, éstas deberían admitir valores nulos, ya que cuando instanciamos no les damos unos valores iniciales.
Para solucionarlo.
class Vehiculo{
  
  int anoCompra; 
  String matricula;
  String marca;
  
  Vehiculo(this.anoCompra,this.marca, this.matricula); // Esto hace que automaticamente asigne los valores enviados a las propiedades y obliga a instanciar con los 3 parámetros.
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
  
}
void main() {

  final veh1 = new Vehiculo(2018,'Opel','1234GHZ');  
  print(veh1);
  
  final veh2 = Vehiculo(2020,'Peugot','6543HZZ'); 
  print(veh2);
  
  
}


  • Si quisiéramos emplear el paso de parámetros empleando sus nombres, debemos de hacer uso de las { }, pero haciendo obligatorios el paso de datos con la palabra reservada required como ya vimos anteriormente.
class Vehiculo{
  
  int anoCompra; 
  String matricula;
  String marca;
  
  Vehiculo({
      required this.anoCompra,
      required this.marca, 
      required this.matricula
    }); 
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
  
}
void main() {

  final veh1 = new Vehiculo(anoCompra: 2018 ,marca: 'Opel',matricula: '1234GHZ');  
  print(veh1);
  
  final veh2 = Vehiculo(anoCompra: 2020,marca: 'Peugot',matricula: '6543HZZ'); 
  print(veh2);
  
  
}


NOTAS IMPORTANTES:

  • En DART los constructores no se heredan.
  • Para llamar a un constructor que esté definido en una clase superior, debemos emplear la palabra clave super. Ejemplos de uso en este enlace.



Mútliples constructores. Constructores con nombre

  • En DART no existe la sobrecarga de constructores y por tanto no podemos definir múltiples constructores en base a diferente tipo o número de parámetros.
Para solucionarlo, DART permite crear múltiples constructores con diferentes nombres de la forma: nombre_clase.nombre_constructor(parámetros).
class Vehiculo{
  
  int anoCompra; 
  String matricula;
  String marca;
  
  Vehiculo({required this.anoCompra,required this.marca, required this.matricula}); 
  
  Vehiculo.sinAnho({required this.marca, required this.matricula}) : anoCompra = 2000;

  Vehiculo.sinAnhoMarca({required this.matricula}) : anoCompra = 2000, marca='Opel';
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
  
}
void main() {

  final veh1 = new Vehiculo(anoCompra: 2018,marca: 'Opel',matricula: '1234GHZ');  
  print(veh1);
  
  final veh2 = new Vehiculo.sinAnho(marca: 'Opel',matricula: '2234GHZ');  
  print(veh2);
  
  final veh3 = new Vehiculo.sinAnhoMarca(matricula: '3234GHZ');  
  print(veh3);
  
}
  • Nota importante: Fijarse que estoy haciendo uso de otro constructor en el que indico los valores que van a guardar los atributos de la clase Coche. Como no envío el año de compra ni la marca, tendría que indicar en la definición del atributo, que dichos atributos puede recibir valores nulos o que tienen un valor por defecto.
Pero he empleado otra característica de DART que consiste en poner, después de la definición de los parámetros, dos puntos seguido de las variables que queremos inicializar. Este código se ejecuta antes del constructor. Es lo que se conoce como inicializadores.
Más información: https://dart.dev/guides/language/language-tour#constructors



Empleando Mapas

  • En esta sección voy a explicar como, teniendo como fuente de datos un mapa, crear un objeto.
Partimos de un mapa que va a guardar la información de un Coche.
Normalmente los datos que tengan como fuente de datos Internet vendrán en formato JSON:



class Vehiculo{
  
  int anoCompra; 
  String matricula;
  String marca;
  
  Vehiculo({
      required this.anoCompra,
      required this.marca, 
      required this.matricula
    }); 
 
  Vehiculo.fromJson(Map<String,String>dato) :
    this.anoCompra = int.parse(dato['anoCompra']!),
    this.matricula = dato['matricula']!,
    this.marca = dato['marca']!;
  
  @override
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; 
  
 
}
void main() {

  final vehiculoJson = {
    'anoCompra' : '2020',
    'matricula' : '1234GHZ',
    'marca' : 'Opel'
  };
  
  Vehiculo veh1 = Vehiculo.fromJson(vehiculoJson);
  print(veh1);
  
}
Fijarse que los datos del mapa que son asignados a los atributos llevan una admiración, indicando a DART que no debe verificar que puedan ser nulos. Tendremos que estar seguros que nunca va a recibir un valor nulo en el mapa, ya que sino, daría una excepción.


Herencia

  • La palabra clave para indicar que una clase deriva de otra es extends y para referencia a un método o propiedad de la clase padre expleamos la palabra clave super.
Veamos un ejemplo de uso:
class Personal {
  String nombreCompleto;

  Personal(this.nombreCompleto);

}
class Alumno extends Personal{
  String numCarnet;

  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
}

class Profesor extends Personal{
  String nrp;

  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
}
void main(){

  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');

  print(alum1);
  print(prof1);

}



Librerías. Privado/Público

  • En DART cada archivo .dart puede estar formado por múltiples clases y conformar una librería y podemos agrupar un conjunto de librerías y herramientas en forma de paquetes.
Más información sobre librerías y paquetes: https://dart.dev/guides/libraries/create-library-packages
  • Para poder hacer uso de las clases que se encuentran definiadas en una librería tenemos que hacer uso de la palabra clave import al principio del archivo.


  • Este ejemplo será necesario realizar en el Android Studio:
Supongamos que tenemos un archivo de nombre Centro.dart con un conjunto de clases definidas:
class Personal {
  String nombreCompleto;

  Personal(this.nombreCompleto);

}
class Alumno extends Personal{
  String numCarnet;

  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
}

class Profesor extends Personal{
  String nrp;

  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
}
Y ahora queremos hacer uso de estas clases desde otro archivo DART.
Para ello empleamos la orden import:
import 'Centro.dart';

void main(){

  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');

  print(alum1);
  print(prof1);

}



  • En Dart existen una serie de librerías ya creadas y que podemos usar en cualquier momento, haciendo el import correspondiente.
Por ejemplo, la librería math tiene funciones y clases que nos pueden ser útiles a nivel matemático.
import 'dart:math';

void main() {
  
  final semilla = Random();
  for(int cont=0; cont<10;cont++){
    print('Aleatorio generado:${semilla.nextInt(50)}');
  }

}
  • Podemos establecer un 'alias' a la librería con la palabra clave as al hacer el import de la forma:
import 'dart:math' as math;

void main() {
  
  final semilla = math.Random();
  for(int cont=0; cont<10;cont++){
    print('Aleatorio generado:${semilla.nextInt(50)}');
  }

}



Solamente tenemos la posibilidad de hacer que una propiedad, método o clase sea privada anteponiendo un guión bajo a su nombre.
De esta forma, dicha clase, método o propiedad sólo será visible para todo lo que se encuentra dentro del archivo DART.
  • Siguendo el ejemplo anterior, yo no quiero que se pueda instanciar un objeto de la clase Personal.
Para ello puedo definir la clase Personal como privada de la forma (fijarse en los guiones bajos):
class _Personal {
  String nombreCompleto;

  _Personal(this.nombreCompleto);

}
class Alumno extends _Personal{
  String numCarnet;

  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
}

class Profesor extends _Personal{
  String nrp;

  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
}
Ahora, desde el archivo DART donde hacemos uso de estas clases, ya no me estará permitido crear una instancia de la clase Personal:
Flutter dart private 1.JPG


  • Y el mismo concepto podría ser aplicado tanto a métodos como a propiedades.



Getters y Setters

  • Los getters y setters son unos métodos especiales que se llaman sin emplear paréntesis y aparentan ser propiedades de una clase, ya que se llaman de la misma forma: objeto.método (pero sin paréntesis).
Por lo tanto, a nivel de programación van a aparentar que estamos obteniendo o asignando un valor a una propiedad, pero realmente dicha propiedad no existe.
  • Si empleamos el método para obtener información (es decir, el método tiene un return) se denomina getter.
  • Si empleamos el método para asignar un valor a una propiedad que sí exista se denomina setter.


Este punto está relacionado con lo visto anteriormente sobre public/private.
Normalmente los métodos getters y setters se aplican a propiedades definidas como privadas. Empleando estos métodos no accedemos directamente a las propiedades y esto nos va a permitir:
  • Definir propiedades de 'sólo lectura' en la que solamente definimos el método getter.
  • Controlar los valores que se asignan a propiedades para que cumplan unos criterios determinados.
  • Definir propiedades 'ficticias' que realmente no existen, sino que se calculan en base a datos que ya tenemos guardados en la clase. Por ejemplo, si tenemos guardada la propiedad fechaNacimiento, puede crear una propiedad edad que sea igual a la resta de la fecha actual menos la fecha de nacimiento, y que realice dicho cálculo cada vez que acceda a la propiedad.
  • Veamos un ejemplo.
Modificamos el ejemplo anterior y vamos a crear una nueva propiedad en la clase Profesor del ejemplo anterior.
Vamos a añadir una propiedad sueldo de tipo double, pero voy a hacerla privada y voy a crear un método getter y setter para acceder y modificar dicha propiedad.
void main() {

  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');

  print(prof1);
  print('Sueldo:${prof1.sueldo}'); // Podemos acceder a _sueldo porque estamos en el mismo fichero
  prof1.sueldo = 100.34;  // Llama al método set
  print('Nuevo Sueldo:${prof1.sueldo}'); 
  
  prof1.sueldo = -50;  // El assert lanzará una excepción
  print('Nuevo Sueldo:${prof1.sueldo}'); 
}

class _Personal {
  String nombreCompleto;

  _Personal(this.nombreCompleto);

}

class Profesor extends _Personal{
  String nrp;
  double _sueldo=0;  // Necesitamos que tenga un valor por defecto ya que sino el parámetro tendría que poder guardar valores nulos
  
  double get sueldo => _sueldo;
  set sueldo(double nuevoSueldo){
    assert(nuevoSueldo <= 0,'El sueldo debe ser mayor que 0');  // Lanza una excepción si cumple la condición pero sólo en Debugger. En Web no tiene efecto
    _sueldo = nuevoSueldo <= 0 ? 0 : nuevoSueldo;  
  }
  
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);

  @override
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
}



  • Si intentamos aplicar el concepto de propiedad privada a los atributos que conforman el constructor, el código se complica un poco debido a los valores nulos.
Veamos un ejemplo con el número de carnet del alumno.
void main() {

  final alum1 = new Alumno(num: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');

  print(alum1);

}

class _Personal {
  String nombreCompleto;

  _Personal(this.nombreCompleto);

}
class Alumno extends _Personal{
  String _numCarnet;
  
  String get numCarnet => _numCarnet;
  set numCarnet(String numero) { numero.isNotEmpty ? _numCarnet = numero : _numCarnet = '1'; } // Aquí le damos un valor por defecto pero podríamos tener otra lógica diferente

  Alumno({required String num, required String nombreCompleto}) : 
       _numCarnet = num,super(nombreCompleto) {
    numCarnet = num;  // Esto se ejecuta después de asignar el valor a _numCarnet. Lo llamo por si queremos que pase algún tipo de filtro por el valor enviado.     
  }

  @override
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
}





Enlace a la página principal de la UD2

Enlace a la página principal del curso




-- Ángel D. Fernández González -- (2021).