Flutter DART Clases
Sumario
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,
- Podéis consultar como se implementan en Java en Fundamentos de POO en Java y en la ampliación de la POO.
- 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.
- Debéis recordar lo que expliqué acerca de los valores nulos al emplear mapas.
- Debéis recordar el empleo de inicializadores visto anteriormente en esta sección.
- 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
- Más información: https://dart.dev/guides/language/language-tour#extending-a-class
- 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)}'); } }
- Relacionado con lo anterior, en DART no existen los modificadores de acceso public, private, protected como en otros lenguajes de programación orientada a objetos.
- 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:
- Y el mismo concepto podría ser aplicado tanto a métodos como a propiedades.
Getters y Setters
- Más información: https://dart.dev/guides/language/language-tour#getters-and-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).