Diferencia entre revisiones de «Flutter DART Clases»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 49 ediciones intermedias del mismo usuario)
Línea 1: Línea 1:
 
==Introducción==
 
==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.
+
* '''Información:''' En VS Code, para que aparezcan las sugerencias de métodos, propiedades y otros miembros disponibles podemos emplear la combinación de teclas: Control + Barra Espaciadora.
−
: 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 [https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_Java#Fundamentos_Programaci.C3.B3n_Orientada_a_Objetos_.28POO.29 Fundamentos de POO en Java] y [https://wiki.cifprodolfoucha.es/index.php?title=Prog._Ampliaci%C3%B3n_POO en la ampliación de la POO].
+
<br />
 +
 
 +
* 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 van a explicar las diferencias y características de Dart con respecto a otros lenguajes orientados a objetos en lo que se refiere al uso de clases y objetos.
  
 +
* Podéis consultar cómo se implementan estos conceptos en Java en [https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_Java#Fundamentos_Programaci.C3.B3n_Orientada_a_Objetos_.28POO.29 Fundamentos de POO en Java] y [https://wiki.cifprodolfoucha.es/index.php?title=Prog._Ampliaci%C3%B3n_POO en la ampliación de la POO].
  
−
* En Dart no es necesario realizar el new cuando instanciamos una clase.  
+
* En Dart no es necesario utilizar `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.
+
  : Aunque la palabra clave `new` sigue siendo válida al invocar un constructor se aconseja omitirla.
−
: 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.
+
  : Esto resulta especialmente útil en Flutter, donde se crean objetos y widgets de forma continua y el uso repetido de `new` haría el código más redundante.
  
 +
: Veamos un ejemplo, empleando conceptos habituales de otros lenguajes de programación orientados a objetos, como la redefinición del método `toString`:
  
−
: Veamos un ejemplo, con conceptos de otros lenguajes de programación orientado a objetos como el empleo del método toString:
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1-9">
 +
class Vehiculo {
 +
int? anoCompra; // Admite null porque inicialmente no le asignamos ningún valor.
 +
String? matricula;
 +
String? marca;
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="1-9" >
+
@override
−
class Vehiculo{
+
String toString() =>
−
 
+
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
−
  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() {
 
void main() {
 +
final veh1 = Vehiculo();
 +
veh1.marca = 'Opel';
 +
veh1.matricula = '1234GHZ';
 +
veh1.anoCompra = 2018;
  
−
  final veh1 = new Vehiculo();
+
print(veh1);
−
  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);
 
−
 
 
−
}
 
−
</syntaxhighlight>
 
  
 +
final veh2 = Vehiculo();
 +
veh2.marca = 'Peugeot';
 +
veh2.matricula = '6543HZZ';
 +
veh2.anoCompra = 2020;
  
 +
print(veh2);
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
Línea 49: Línea 47:
 
==Constructores==
 
==Constructores==
  
−
* Funcionan igual que en el lenguaje Java.
+
* Los constructores tienen una finalidad similar a los de Java: permiten inicializar un objeto en el momento de crearlo.
−
: Veamos un ejemplo de uso:
+
  Sin embargo, Dart incorpora algunas características propias, como los constructores con nombre, las listas de inicialización, los constructores `factory` y los súper parámetros.
 +
  Veamos un ejemplo de uso:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="7-11">
 +
class Vehiculo {
 +
int? anoCompra;
 +
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 = Vehiculo(2018, '1234GHZ', 'Opel');
 +
print(veh1);
 +
 
 +
final veh2 = Vehiculo(2020, '6543HZZ', 'Peugeot');
 +
print(veh2);
 +
} </syntaxhighlight>
 +
 
 +
: Un constructor es una función especial asociada a la creación e inicialización de objetos. No debe confundirse con un método ordinario.
 +
: Los constructores pueden recibir parámetros posicionales, opcionales o con nombre, al igual que otras funciones de Dart.
 +
 
 +
:'''Ejercicio propuesto:''' Modifica el ejercicio anterior y haz que los parámetros puedan ser enviados en cualquier orden especificando el nombre del parámetro en la llamada. Haz que todos ellos sean obligatorios.
 +
 
  
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="7-11" >
+
 
−
class Vehiculo{
+
<br />
−
 
+
* En el ejemplo anterior las propiedades admiten valores nulos porque no se inicializan en el momento de declararlas.
−
  int? anoCompra; // Necesitamos indicar que admite valores nulos, ya que al instanciar no le damos un valor por defecto
+
  : Sin embargo, si siempre vamos a proporcionar sus valores al crear el objeto, podemos hacer que sean no anulables e inicializarlas directamente desde el constructor.
−
  String? matricula;
+
  : Dart proporciona una sintaxis abreviada para realizar esta asignación:
−
  String? marca;
+
 
−
 
+
:<syntaxhighlight lang="dart" enclose="div" highlight="6">
−
  Vehiculo(int anoCompra, String matricula, String marca){
+
class Vehiculo {
−
    this.anoCompra = anoCompra;
+
int anoCompra;
−
    this.marca = marca;
+
String matricula;
−
    this.matricula = matricula;
+
String marca;
−
  }
+
 
−
 
+
Vehiculo(this.anoCompra, this.marca, this.matricula);
−
  @override
+
 
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
@override
 +
String toString() =>
 +
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
}
 
}
 +
 
void main() {
 
void main() {
 +
final veh1 = Vehiculo(2018, 'Opel', '1234GHZ');
 +
print(veh1);
 +
 +
final veh2 = Vehiculo(2020, 'Peugeot', '6543HZZ');
 +
print(veh2);
 +
} </syntaxhighlight>
 +
 +
: En `Vehiculo(this.anoCompra, this.marca, this.matricula)`, Dart asigna automáticamente cada parámetro recibido a la propiedad correspondiente del objeto.
 +
  
−
  final veh1 = new Vehiculo(2018,'Opel','1234GHZ');
+
 
−
  print(veh1);
+
 
−
 
+
* Si queremos emplear parámetros con nombre debemos utilizar las llaves '''{ }'''.
−
  final veh2 = Vehiculo(2020,'Peugot','6543HZZ');  
+
: Si además queremos que sea obligatorio indicar esos parámetros al llamar al constructor, emplearemos la palabra reservada '''required''', [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_Funciones#Par.C3.A1metros_obligatorios.2Fopcionales como ya vimos anteriormente].
−
  print(veh2);
+
 
−
 
+
:<syntaxhighlight lang="dart" enclose="div" highlight="7-9,19-21">
 +
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';
 
}
 
}
−
</syntaxhighlight>
 
  
−
: 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,...)
+
void main() {
−
:'''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.
+
final veh1 = Vehiculo(
 +
anoCompra: 2018,
 +
marca: 'Opel',
 +
matricula: '1234GHZ',
 +
);
 +
print(veh1);
  
 +
final veh2 = Vehiculo(
 +
anoCompra: 2020,
 +
marca: 'Peugeot',
 +
matricula: '6543HZZ',
 +
);
 +
print(veh2);
 +
} </syntaxhighlight>
  
  
  
−
* 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.
+
<br />
−
: Para solucionarlo.
+
'''<u>NOTAS IMPORTANTES:</u>'''
−
:<syntaxhighlight lang="java" enclose="div" highlight="7" >
+
:* En Dart los constructores no se heredan.
−
class Vehiculo{
+
:* Para inicializar la parte correspondiente a una clase superior podemos llamar a uno de sus constructores mediante `super`.
−
 
+
 
−
   int anoCompra;  
+
 
 +
 
 +
 
 +
<br />
 +
===Múltiples constructores. Constructores con nombre===
 +
 
 +
* Dart no permite la sobrecarga tradicional de métodos o constructores, es decir, no podemos declarar varias veces un método o constructor con el mismo nombre y diferenciarlos únicamente por el número o tipo de sus parámetros.
 +
  : Para proporcionar diferentes formas de crear un objeto, Dart permite utilizar '''constructores con nombre'''.
 +
  : Su forma general es:
 +
 
 +
NombreClase.nombreConstructor(parámetros)
 +
 
 +
: Veamos un ejemplo:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="">
 +
class Vehiculo {
 +
   int anoCompra;
 
   String matricula;
 
   String matricula;
 
   String marca;
 
   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.
+
   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
 
   @override
−
   String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
   String toString() =>
−
 
+
      'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
}
 
}
 +
 
void main() {
 
void main() {
 +
  final veh1 = Vehiculo(anoCompra: 2018, marca: 'Opel', matricula: '1234GHZ');
 +
  print(veh1);
  
−
   final veh1 = new Vehiculo(2018,'Opel','1234GHZ'); 
+
   final veh2 = Vehiculo.sinAnho(marca: 'Opel', matricula: '2234GHZ');
−
  print(veh1);
 
−
 
 
−
  final veh2 = Vehiculo(2020,'Peugot','6543HZZ');  
 
 
   print(veh2);
 
   print(veh2);
−
    
+
 
−
    
+
   final veh3 = Vehiculo.sinAnhoMarca(matricula: '3234GHZ');
 +
   print(veh3);
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
 +
 
 +
* '''<u>Nota importante:</u>''' En los constructores anteriores no recibimos todos los valores necesarios para inicializar las propiedades del objeto.
 +
  : Como las propiedades `anoCompra` y `marca` no admiten valores nulos, debemos inicializarlas antes de que finalice la construcción del objeto.
 +
  : Para hacerlo hemos empleado una característica de Dart denominada '''lista de inicialización'''.
 +
 
 +
: La lista de inicialización aparece después de los parámetros del constructor, precedida por dos puntos (`:`):
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
Vehiculo.sinAnhoMarca({required this.matricula,}) : anoCompra = 2000, marca = 'Opel';
 +
</syntaxhighlight>
 +
 
 +
: Las expresiones de la lista de inicialización se ejecutan '''antes del cuerpo del constructor'''.
 +
 
 +
: Más información: [https://dart.dev/language/constructors Constructores en Dart].
 +
 
 +
<br />
 +
 
 +
===Constructores Factory===
 +
 
 +
* Más información: [https://dart.dev/language/constructors#factory-constructors Factory constructors].
 +
 
 +
* Los constructores `factory` se utilizan cuando la creación de un objeto necesita un comportamiento diferente al de un constructor generativo convencional.
 +
 
 +
 
 +
* Por ejemplo, pueden utilizarse cuando:
 +
:* queremos devolver una instancia creada anteriormente, como ocurre al utilizar una caché;
 +
:* queremos devolver una instancia de una subclase compatible;
 +
:* queremos ejecutar lógica previa antes de decidir qué objeto devolver;
 +
:* queremos transformar o interpretar información antes de delegar la construcción a otro constructor.
 +
 
 +
: Se definen anteponiendo la palabra reservada '''factory''' al nombre del constructor.
  
 +
: Un constructor `factory` debe devolver un objeto compatible con el tipo de la clase. Puede hacerlo mediante `return` o utilizando la sintaxis de función flecha (`=>`).
  
 +
: Un uso habitual consiste en delegar la creación del objeto al constructor principal.
  
−
* 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''' [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_Funciones#Par.C3.A1metros_obligatorios.2Fopcionales como ya vimos anteriormente].
+
: Veamos un ejemplo:
−
:<syntaxhighlight lang="java" enclose="div" highlight="7-11,19,22" >
+
 
−
class Vehiculo{
+
:<syntaxhighlight lang="dart" enclose="div" highlight="12-20">
−
 
+
class Vehiculo {
−
   int anoCompra;  
+
   int anoCompra;
 
   String matricula;
 
   String matricula;
 
   String marca;
 
   String marca;
−
 
+
 
 
   Vehiculo({
 
   Vehiculo({
−
      required this.anoCompra,
+
    required this.anoCompra,
−
      required this.marca,  
+
    required this.marca,
−
      required this.matricula
+
    required this.matricula,
−
     });  
+
  });
−
 
+
 
 +
  factory Vehiculo.sinAnho({
 +
    required String marca,
 +
     required String matricula,
 +
  }) => Vehiculo(anoCompra: 2020, marca: marca, matricula: matricula);
 +
 
 
   @override
 
   @override
−
   String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
   String toString() =>
−
 
+
      'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
}
 
}
 +
 
void main() {
 
void main() {
 +
  final veh2 = Vehiculo.sinAnho(marca: 'Opel', matricula: '2234GHZ');
  
−
  final veh1 = new Vehiculo(anoCompra: 2018 ,marca: 'Opel',matricula: '1234GHZ'); 
 
−
  print(veh1);
 
−
 
 
−
  final veh2 = Vehiculo(anoCompra: 2020,marca: 'Peugot',matricula: '6543HZZ');
 
 
   print(veh2);
 
   print(veh2);
−
 
 
−
 
 
 
}
 
}
−
</syntaxhighlight>  
+
</syntaxhighlight>
  
 +
<br />
  
 +
==Empleando Mapas==
  
−
'''<u>NOTAS IMPORTANTES:</u>'''
+
* En esta sección vamos a ver cómo crear un objeto utilizando como fuente de datos un mapa.
−
:* En DART los constructores no se heredan.
+
  :* Debéis recordar lo explicado acerca de los [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_variables_-_tipos_de_datos#Valores_nulos_al_utilizar_mapas valores nulos al emplear mapas].
−
:* Para llamar a un constructor que esté definido en una clase superior, debemos emplear la palabra clave '''super'''. Ejemplos de uso en [https://medium.com/@flutterman/dart-2-implementaci%C3%B3n-de-clases-a53360efa007 este enlace].
+
  :* También utilizaremos [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_Clases#M.C3.BAtliples_constructores._Constructores_con_nombre las listas de inicialización] vistas anteriormente.
  
 +
: Partimos de un mapa que almacena la información de un vehículo.
 +
: Es habitual recibir datos procedentes de servicios web en formato JSON y, una vez decodificados, trabajar con estructuras del tipo `Map<String, dynamic>`.
 +
 +
<center>
 +
{{#ev:youtube|kKczrKiV6Uc}}
 +
</center>
  
 
<br />
 
<br />
  
−
==Mútliples constructores. Constructores con nombre==
+
:<syntaxhighlight lang="dart" enclose="div">
−
* 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.
+
class Vehiculo {
−
: Para solucionarlo, DART permite crear múltiples constructores con diferentes nombres de la forma: '''nombre_clase.nombre_constructor(parámetros)'''.
+
   int anoCompra;
−
 
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="9,11,22,25" >
 
−
class Vehiculo{
 
−
 
 
−
   int anoCompra;  
 
 
   String matricula;
 
   String matricula;
 
   String marca;
 
   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';
+
   Vehiculo({
−
 
+
    required this.anoCompra,
 +
    required this.marca,
 +
    required this.matricula,
 +
  });
 +
 
 +
  Vehiculo.fromMap(Map<String, dynamic> dato)
 +
    : anoCompra = int.parse(dato['anoCompra'].toString()),
 +
      matricula = dato['matricula'] as String,
 +
      marca = dato['marca'] as String;
 +
 
 
   @override
 
   @override
−
   String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
   String toString() =>
−
 
+
      'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
}
 
}
 +
 
void main() {
 
void main() {
 +
  final vehiculoJson = <String, dynamic>{
 +
    'anoCompra': '2020',
 +
    'matricula': '1234GHZ',
 +
    'marca': 'Opel',
 +
  };
 +
 +
  final veh1 = Vehiculo.fromMap(vehiculoJson);
  
−
  final veh1 = new Vehiculo(anoCompra: 2018,marca: 'Opel',matricula: '1234GHZ'); 
 
 
   print(veh1);
 
   print(veh1);
−
 
 
−
  final veh2 = new Vehiculo.sinAnho(marca: 'Opel',matricula: '2234GHZ'); 
 
−
  print(veh2);
 
−
 
 
−
  final veh3 = new Vehiculo.sinAnhoMarca(matricula: '3234GHZ'); 
 
−
  print(veh3);
 
−
 
 
 
}
 
}
−
</syntaxhighlight>
 
  
−
* '''<u>Nota importante:</u>''' 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.
+
} </syntaxhighlight>
−
: 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'''. <u>Este código se ejecuta '''antes''' del constructor</u>. Es lo que se conoce como '''inicializadores'''.
 
−
: Más información: https://dart.dev/guides/language/language-tour#constructors
 
  
 +
: El tipo `Map<String, dynamic>` indica que las claves del mapa son cadenas de caracteres, mientras que sus valores pueden ser de diferentes tipos.
  
  
 
<br />
 
<br />
−
==Empleando Mapas==
+
* '''<u>Importante:</u>''' utilizar `dynamic` no garantiza que los datos sean correctos ni evita errores en tiempo de ejecución.
−
 
+
: Por ejemplo:
−
* 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 [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_variables_-_tipos_de_datos#Valores_nulos_al_utilizar_mapas valores nulos al emplear mapas].
 
−
:* Debéis recordar [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_Clases#M.C3.BAtliples_constructores._Constructores_con_nombre 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:
 
  
−
<center>
+
:* `dato['matricula'] as String` producirá un error si el valor existente no es un `String`.
−
{{#ev:youtube|kKczrKiV6Uc}}
+
:* `int.parse(...)` producirá un `FormatException` si el contenido no puede convertirse a un número entero.
−
</center>
+
:* Que la clave no exista en los datos recibidos de internet.
  
 +
: Por tanto, al trabajar con información externa conviene comprobar y validar los valores recibidos antes de utilizarlos.
 +
: '''USAREMOS ESTA FORMA PARA PROCESAR LOS DATOS QUE PROVENGAN DE UNA FUENTE DE DATOS EXTERNA'''
  
−
<br />
+
:<syntaxhighlight lang="dart" enclose="div">
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
class Vehiculo {
−
class Vehiculo{
+
   int anoCompra;
−
 
 
−
   int anoCompra;  
 
 
   String matricula;
 
   String matricula;
 
   String marca;
 
   String marca;
−
 
+
 
 
   Vehiculo({
 
   Vehiculo({
−
      required this.anoCompra,
+
    required this.anoCompra,
−
      required this.marca,  
+
    required this.marca,
−
      required this.matricula
+
    required this.matricula,
−
    });  
+
  });
−
+
 
−
   Vehiculo.fromJson(Map<String,String>dato) :
+
   Vehiculo.fromMap(Map<String, dynamic> dato)
−
     this.anoCompra = int.parse(dato['anoCompra']!),
+
     : anoCompra = int.tryParse(dato['anoCompra']?.toString() ?? '') ?? 1990,
−
    this.matricula = dato['matricula']!,
+
      matricula = dato['matricula']?.toString() ?? '',
−
    this.marca = dato['marca']!;
+
      marca = dato['marca']?.toString() ?? '';
−
 
+
 
 
   @override
 
   @override
−
   String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
   String toString() =>
−
 
+
      'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
−
 
 
}
 
}
 +
 
void main() {
 
void main() {
 +
  final vehiculoJson = <String, dynamic>{
 +
    'anoCompra': '2020',
 +
    'matricula': '1234GHZ',
 +
    'marca': 'Opel',
 +
  };
 +
 +
  final veh1 = Vehiculo.fromMap(vehiculoJson);
  
−
  final vehiculoJson = {
 
−
    'anoCompra' : '2020',
 
−
    'matricula' : '1234GHZ',
 
−
    'marca' : 'Opel'
 
−
  };
 
−
 
 
−
  Vehiculo veh1 = Vehiculo.fromJson(vehiculoJson);
 
 
   print(veh1);
 
   print(veh1);
−
 
 
 
}
 
}
  
 +
} </syntaxhighlight>
 +
 +
 +
 +
 +
 +
 +
* Podéis emplear [https://quicktype.io/ Quicktype] para generar automáticamente modelos a partir de una estructura JSON.
 +
* También podríamos utilizar un constructor `factory`, como vimos en el apartado anterior:
 +
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="12-16">
 +
class Vehiculo {
 +
int anoCompra;
 +
String matricula;
 +
String marca;
 +
 +
Vehiculo({
 +
required this.anoCompra,
 +
required this.marca,
 +
required this.matricula,
 +
});
 +
 +
factory Vehiculo.fromJson(Map<String, dynamic> dato) => Vehiculo(
 +
      anoCompra : int.tryParse(dato['anoCompra']?.toString() ?? '') ?? 1990,
 +
      matricula : dato['matricula']?.toString() ?? '',
 +
      marca : dato['marca']?.toString() ?? '');
 +
 +
@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 = <String, dynamic>{
 +
'anoCompra': '2020',
 +
'matricula': '1234GHZ',
 +
'marca': 'Opel',
 +
};
  
−
</syntaxhighlight>
+
final veh1 = Vehiculo.fromJson(vehiculoJson);
  
−
: 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.
+
print(veh1);
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
  
 
==Herencia==
 
==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 referenciar a un método o propiedad de la clase padre expleamos la palabra clave '''super'''.
+
* Más información: [https://dart.dev/language/extend Extending a class].
−
: Veamos un ejemplo de uso:
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="7,16" >
 
−
class Personal {
 
−
  String nombreCompleto;
 
  
−
   Personal(this.nombreCompleto);
+
* Para indicar que una clase hereda de otra empleamos la palabra reservada '''extends'''.
 +
   : La palabra reservada `super` permite acceder a miembros de la superclase y llamar a sus constructores.
  
−
}
+
: Veamos un ejemplo utilizando '''súper parámetros''', disponibles desde Dart 2.17.
−
class Alumno extends Personal{
+
: Los súper parámetros permiten enviar directamente un parámetro al constructor de la superclase sin tener que declararlo de nuevo y llamar explícitamente a `super(...)`.
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
:<syntaxhighlight lang="dart" enclose="div" highlight="7,16">
 +
class Personal {
 +
String nombreCompleto;
  
−
  @override
+
Personal({required this.nombreCompleto});
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
 
 
}
 
}
  
−
class Profesor extends Personal{
+
class Alumno extends Personal {
−
  String nrp;
+
String numCarnet;
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
Alumno({
 +
required this.numCarnet,
 +
required super.nombreCompleto,
 +
});
  
−
  @override
+
@override
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 
}
 
}
−
void main(){
 
  
−
  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
class Profesor extends Personal {
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
String nrp;
  
−
  print(alum1);
+
Profesor({
−
  print(prof1);
+
required this.nrp,
 +
required super.nombreCompleto,
 +
});
  
 +
@override
 +
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 
}
 
}
−
</syntaxhighlight>
 
  
 +
void main() {
 +
final alum1 = Alumno(
 +
numCarnet: 'C1A',
 +
nombreCompleto: 'Juan Luis Rodriguez',
 +
);
 +
 +
final prof1 = Profesor(
 +
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
 +
 +
print(alum1);
 +
print(prof1);
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
−
===Overriding===
 
−
* Tiene el mismo concepto que en otros lenguajes de programación orientado a objetos.
 
−
: De hecho ya lo estamos haciendo de cierta manera con el método toString.
 
  
−
: Veamos un ejemplo en el que defino el método imprimirDatos en la superclase y es heradado por la subclases.
+
===Overriding - Sobrescritura===
−
: Una de ellas (Alumno) lo sobreescribe y llama al método que está definido en la clase padre, Personal.
 
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="6-8,16-20,42,43" >
+
* La '''sobrescritura''' (''overriding'') permite que una subclase proporcione su propia implementación de un miembro que ya existe en la superclase.
 +
  : Es uno de los mecanismos que permiten aplicar polimorfismo en una jerarquía de herencia.
 +
 
 +
: Ya hemos utilizado este concepto al sobrescribir el método `toString`, heredado de la clase `Object`.
 +
 
 +
* Es recomendable emplear la anotación `@override` cuando sobrescribimos un miembro.
 +
  : No es obligatoria para que se produzca la sobrescritura, pero permite que el analizador de Dart compruebe nuestra intención y detecte posibles errores.
 +
 
 +
* <u>Nota:</u> No debemos confundir '''sobrescritura''' (''overriding'') con '''sobrecarga''' (''overloading'').
 +
  : La sobrecarga consiste en definir varios métodos con el mismo nombre pero diferentes listas de parámetros.
 +
  : Dart no soporta este tipo de sobrecarga tradicional.
 +
 
 +
: Veamos un ejemplo en el que definimos el método `imprimirDatos` en la superclase.
 +
: Este método es heredado por las subclases.
 +
: La clase `Alumno` lo sobrescribe y, además, llama desde su nueva implementación al método existente en la clase padre mediante `super.imprimirDatos()`.
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="6-8,16-20,42,43">
 
class Personal {
 
class Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
  
−
  Personal(this.nombreCompleto);
+
Personal(this.nombreCompleto);
−
 
 
−
  void imprimirDatos(){
 
−
    print('Nombre completo:$nombreCompleto');
 
−
  }
 
  
 +
void imprimirDatos() {
 +
print('Nombre completo: $nombreCompleto');
 +
}
 
}
 
}
−
class Alumno extends Personal{
 
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
class Alumno extends Personal {
 +
String numCarnet;
  
−
  @override
+
Alumno({
−
  void imprimirDatos(){
+
required this.numCarnet,
−
    super.imprimirDatos();
+
required String nombreCompleto,
−
    print('Carnet:$numCarnet');
+
}) : super(nombreCompleto);
−
  }
+
 
−
 
+
@override
−
  @override
+
void imprimirDatos() {
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
+
super.imprimirDatos();
 +
print('Carnet: $numCarnet');
 +
}
 +
 
 +
@override
 +
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 
}
 
}
  
−
class Profesor extends Personal{
+
class Profesor extends Personal {
−
  String nrp;
+
String nrp;
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
Profesor({
 +
required this.nrp,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  @override
+
@override
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 
}
 
}
−
void main(){
 
  
−
  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
void main() {
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
final alum1 = Alumno(
 +
numCarnet: 'C1A',
 +
nombreCompleto: 'Juan Luis Rodriguez',
 +
);
 +
 
 +
final prof1 = Profesor(
 +
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
−
  print(alum1);
+
print(alum1);
−
  print(prof1);
+
print(prof1);
−
 
 
−
  alum1.imprimirDatos();
 
−
  prof1.imprimirDatos();
 
  
−
}
+
alum1.imprimirDatos();
−
</syntaxhighlight>  
+
prof1.imprimirDatos();
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
Línea 355: Línea 564:
 
==Librerías. Privado/Público==
 
==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 [https://dart.dev/guides/packages paquetes].
+
* Un fichero `.dart` puede contener múltiples declaraciones: clases, funciones, variables, etc.
−
: Más información sobre librerías y paquetes: https://dart.dev/guides/libraries/create-library-packages
+
  : Normalmente, un fichero Dart constituye una '''biblioteca''' (''library'').
 +
  : También es posible que una biblioteca esté formada por varios ficheros mediante el mecanismo `part`.
  
−
* 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.
+
: Podemos agrupar bibliotecas y otros recursos para formar [https://dart.dev/tools/pub/packages paquetes].
  
 +
: Más información: [https://dart.dev/language/libraries Libraries & imports].
  
−
* Este ejemplo será necesario realizar en el Android Studio:
+
* Para utilizar declaraciones definidas en otra biblioteca debemos importar dicha biblioteca utilizando la palabra reservada '''import'''.
−
: Supongamos que tenemos un archivo de nombre '''Centro.dart''' con un conjunto de clases definidas:
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
 
−
class Personal {
 
−
  String nombreCompleto;
 
  
−
  Personal(this.nombreCompleto);
+
* Este ejemplo será necesario realizarlo en un IDE como Android Studio o VS Code.
  
−
}
+
: Supongamos que tenemos un archivo denominado '''centro.dart''' con las siguientes clases:
−
class Alumno extends Personal{
 
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
:<syntaxhighlight lang="dart" enclose="div">
 +
class Personal {
 +
String nombreCompleto;
  
−
  @override
+
Personal(this.nombreCompleto);
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
 
 
}
 
}
  
−
class Profesor extends Personal{
+
class Alumno extends Personal {
−
  String nrp;
+
String numCarnet;
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
Alumno({
 +
required this.numCarnet,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  @override
+
@override
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 
}
 
}
−
</syntaxhighlight>
 
  
−
: Y ahora queremos hacer uso de estas clases desde otro archivo DART.
+
class Profesor extends Personal {
−
: Para ello empleamos la orden import:
+
String nrp;
−
:<syntaxhighlight lang="java" enclose="div" highlight="1" >
 
−
import 'Centro.dart';
 
  
−
void main(){
+
Profesor({
 +
required this.nrp,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
@override
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 +
} </syntaxhighlight>
  
−
  print(alum1);
+
: Ahora queremos utilizar estas clases desde otro archivo Dart.
−
  print(prof1);
+
: Para ello empleamos `import`:
  
−
}
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1">
 +
import 'centro.dart';
 +
 
 +
void main() {
 +
final alum1 = Alumno(
 +
numCarnet: 'C1A',
 +
nombreCompleto: 'Juan Luis Rodriguez',
 +
);
  
−
</syntaxhighlight>
+
final prof1 = Profesor(
 +
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
 +
print(alum1);
 +
print(prof1);
 +
} </syntaxhighlight>
  
−
<br/>
+
<br />
−
* En Dart existen una serie de librerías ya creadas y que podemos usar en cualquier momento, haciendo el import correspondiente.
 
−
: Por ejemplo, la [https://api.dart.dev/stable/2.13.4/dart-math/dart-math-library.html librería math] tiene funciones y clases que nos pueden ser útiles a nivel matemático.
 
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="1,5" >
+
* Dart incluye numerosas bibliotecas que podemos utilizar mediante el `import` correspondiente.
 +
  : Por ejemplo, la biblioteca [https://api.dart.dev/dart-math/ dart:math] proporciona funciones y clases relacionadas con operaciones matemáticas y generación de números pseudoaleatorios.
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="1,4">
 
import 'dart:math';
 
import 'dart:math';
  
 
void main() {
 
void main() {
−
 
+
final semilla = Random();
−
  final semilla = Random();
 
−
  for(int cont=0; cont<10;cont++){
 
−
    print('Aleatorio generado:${semilla.nextInt(50)}');
 
−
  }
 
  
 +
for (var cont = 0; cont < 10; cont++) {
 +
print('Aleatorio generado: ${semilla.nextInt(50)}');
 
}
 
}
−
</syntaxhighlight>  
+
} </syntaxhighlight>
−
+
 
−
* Podemos establecer un 'alias' a la librería con la palabra clave '''as''' al hacer el import de la forma:
+
* Podemos asignar un alias o prefijo a una biblioteca mediante la palabra reservada '''as''':
−
:<syntaxhighlight lang="java" enclose="div" highlight="1,5" >
+
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="1,4">
 
import 'dart:math' as math;
 
import 'dart:math' as math;
  
 
void main() {
 
void main() {
−
 
+
final semilla = math.Random();
−
  final semilla = math.Random();
 
−
  for(int cont=0; cont<10;cont++){
 
−
    print('Aleatorio generado:${semilla.nextInt(50)}');
 
−
  }
 
  
 +
for (var cont = 0; cont < 10; cont++) {
 +
print('Aleatorio generado: ${semilla.nextInt(50)}');
 
}
 
}
−
</syntaxhighlight>  
+
} </syntaxhighlight>
 +
 
 +
 
 +
 
 +
 
 +
 
 +
* Si una biblioteca contiene muchas declaraciones podemos importar únicamente aquellas que queremos utilizar mediante la palabra reservada `show`:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
import 'package02/centro/centro.dart' show Alumno, Profesor; </syntaxhighlight>
 +
 
 +
: `show` no se limita a clases. Puede utilizarse con cualquier nombre exportado por la biblioteca, como funciones, variables, enumeraciones, etc.
 +
 
 +
* También podemos hacer lo contrario mediante `hide`, importando todas las declaraciones públicas excepto las indicadas:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
import 'package02/centro/centro.dart' hide Personal; </syntaxhighlight>
 +
 
 +
 
  
  
 
<br />
 
<br />
  
−
* Relacionado con lo anterior, en DART no existen los modificadores de acceso public, private, protected como en [https://wiki.cifprodolfoucha.es/index.php?title=Prog._Aprendiendo_POO#Encapsulaci.C3.B3n otros lenguajes de programación orientada a objetos].
+
* En Dart no existen modificadores de acceso `public`, `private` y `protected` equivalentes a los de lenguajes como Java.
 +
 
 +
: Los identificadores son públicos de forma predeterminada.
 +
 
 +
: Para declarar un miembro o una clase como privado debemos anteponer un guion bajo (`_`) a su nombre.
 +
 
 +
:'''<u>Importante:</u>''' La privacidad en Dart se aplica a nivel de '''biblioteca''', no estrictamente a nivel de clase ni de fichero.
 +
: Por tanto, un identificador que comienza por `_` puede ser utilizado desde cualquier código perteneciente a la misma biblioteca, pero no desde una biblioteca externa.
  
−
: Solamente tenemos la posibilidad de hacer que una propiedad, método o clase sea privada <u>anteponiendo un guión bajo </u> a su nombre.
+
* Siguiendo el ejemplo anterior, si no queremos exponer directamente la clase `Personal` fuera de su biblioteca podemos declararla como `_Personal`:
−
: 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.
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1,4,7,16">
−
: Para ello puedo definir la clase Personal como privada de la forma (fijarse en los guiones bajos):
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="1,4,7,16" >
 
 
class _Personal {
 
class _Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
  
−
  _Personal(this.nombreCompleto);
+
_Personal(this.nombreCompleto);
 +
}
  
−
}
+
class Alumno extends _Personal {
−
class Alumno extends _Personal{
+
String numCarnet;
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
Alumno({
 +
required this.numCarnet,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  @override
+
@override
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
+
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 
}
 
}
  
−
class Profesor extends _Personal{
+
class Profesor extends _Personal {
−
  String nrp;
+
String nrp;
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
Profesor({
 +
required this.nrp,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  @override
+
@override
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
String toString() =>
−
}
+
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 +
} </syntaxhighlight>
  
−
</syntaxhighlight>
+
: Desde otra biblioteca que importe este fichero no podremos referenciar directamente la clase `_Personal`:
  
−
: Ahora, desde el archivo DART donde hacemos uso de estas clases, ya no me estará permitido crear una instancia de la clase Personal:
 
 
[[Imagen:Flutter_dart_private_1.JPG|500px|center]]
 
[[Imagen:Flutter_dart_private_1.JPG|500px|center]]
  
 +
* El mismo mecanismo puede aplicarse a métodos, propiedades, funciones, variables y otras declaraciones.
  
−
* Y el mismo concepto podría ser aplicado tanto a métodos como a propiedades.
+
: '''<u>Nota:</u>''' El carácter `_` controla la visibilidad de un identificador, pero no impide que la propia biblioteca cree objetos de esa clase.
−
: Indicar que en el ejemplo anterior ya no me deja instancar la clase _Personal desde otros archivos DART que hagan uso de la importación de estas clases, pero sí me dejaría hacerlo dentro del mismo archivo.  
+
: Si queremos indicar que una clase no debe poder instanciarse directamente, podemos declararla como `abstract`, como veremos a continuación.
−
: Recordar que si no queremos permitir que se instancie una clase debemos de definirla como abstracta (lo veremos a continuación).
 
−
 
 
  
 
<br />
 
<br />
Línea 488: Línea 739:
 
==Getters y Setters==
 
==Getters y Setters==
  
−
* Más información: https://dart.dev/guides/language/language-tour#getters-and-setters
+
* Más información: [https://dart.dev/language/methods#getters-and-setters 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 (<u>pero sin paréntesis</u>).
+
* Los '''getters''' y '''setters''' son miembros especiales que permiten controlar el acceso y la modificación de determinadas propiedades manteniendo una sintaxis similar a la lectura o asignación directa de una variable.
−
: 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.
 
  
 +
:* Un '''getter''' (`get`) permite obtener un valor.
 +
:* Un '''setter''' (`set`) permite asignar o modificar un valor.
  
−
: Este punto está relacionado con lo visto anteriormente sobre public/private.
+
: La llamada a un getter o setter se realiza sin utilizar paréntesis.
−
: 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.
+
:<syntaxhighlight lang="dart" enclose="div">
−
: Modificamos el ejemplo anterior y vamos a crear una nueva propiedad en la clase Profesor del ejemplo anterior.
+
void main() {
−
: 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.
+
final prof1 = Profesor(
−
 
+
nrp: '1234534',
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
 +
print(prof1);
  
−
void main() {
+
print('Sueldo: ${prof1.sueldo}');
  
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
prof1.sueldo = 100.34; // Llama al setter.
 +
print('Nuevo sueldo: ${prof1.sueldo}');
  
−
  print(prof1);
+
prof1.sueldo = -50; // El setter ajustará el valor.
−
  print('Sueldo:${prof1.sueldo}'); // Podemos acceder a _sueldo porque estamos en el mismo fichero
+
print('Nuevo sueldo tras intento negativo: ${prof1.sueldo}');
−
  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 {
 
class _Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
  
−
  _Personal(this.nombreCompleto);
+
_Personal(this.nombreCompleto);
 +
}
  
−
}
+
class Profesor extends _Personal {
 +
String nrp;
 +
double _sueldo = 0;
  
−
class Profesor extends _Personal{
+
double get sueldo => _sueldo;
−
  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
+
set sueldo(double nuevoSueldo) {
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
_sueldo = nuevoSueldo <= 0 ? 0 : nuevoSueldo;
 
}
 
}
  
−
</syntaxhighlight>
+
Profesor({
−
 
+
required this.nrp,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
 +
@override
 +
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
−
* 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.
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="16,18,19,21-24" >
 
−
void main() {
 
  
−
  final alum1 = new Alumno(num: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
* También podemos utilizar getters y setters para validar los valores almacenados en las propiedades de una clase:
  
−
  print(alum1);
+
:<syntaxhighlight lang="dart" enclose="div" highlight="16,18,19,21-24">
 +
void main() {
 +
final alum1 = Alumno(
 +
num: 'C1A',
 +
nombreCompleto: 'Juan Luis Rodriguez',
 +
);
  
 +
print(alum1);
 
}
 
}
  
 
class _Personal {
 
class _Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
  
−
  _Personal(this.nombreCompleto);
+
_Personal(this.nombreCompleto);
 +
}
  
−
}
+
class Alumno extends _Personal {
−
class Alumno extends _Personal{
+
String _numCarnet;
−
  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}) :
+
String get numCarnet => _numCarnet;
−
      _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
+
set numCarnet(String numero) {
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
+
_numCarnet = numero.isNotEmpty ? numero : '1';
 
}
 
}
  
−
</syntaxhighlight>
+
Alumno({
 +
required String num,
 +
required String nombreCompleto,
 +
})  : _numCarnet = num.isNotEmpty ? num : '1',
 +
super(nombreCompleto);
  
 +
@override
 +
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
 
<br />
 
<br />
 +
 
==Clases abstractas / Interfaces==
 
==Clases abstractas / Interfaces==
  
−
* Más información: https://dart.dev/guides/language/language-tour#abstract-classes y en https://dart.dev/guides/language/language-tour#implicit-interfaces
+
* Más información:
 +
  :* [https://dart.dev/language/class-modifiers Class modifiers]
 +
  :* [https://dart.dev/language/class-modifiers#abstract Abstract]
 +
  :* [https://dart.dev/language/class-modifiers#interface Interface]
  
 +
* Una '''clase abstracta''' se declara utilizando el modificador `abstract`.
 +
  : Una clase abstracta no puede instanciarse directamente.
 +
  : Puede contener miembros con implementación y miembros abstractos, es decir, miembros cuya implementación tendrán que proporcionar las subclases.
  
−
* Es el mismo concepto que se aplica en [https://wiki.cifprodolfoucha.es/index.php?title=Prog._Ampliaci%C3%B3n_POO#Clases_abstractas otros lenguajes de programación].
+
* Además, en Dart '''toda clase define implícitamente una interfaz''' formada por los miembros de instancia que otras clases pueden implementar mediante `implements`.
−
: Una clase abstracta es una clase que no se puede instanciar y que se define con la palabra clave '''abstract'''.
 
−
: Podemos definir métodos con código o sin código (sólo la definición), cuya implentación tiene que ser realizada en la clase que vaya a heredar de la clase abstracta.
 
  
−
* Una diferencia con respecto a Java y otros lenguajes es que en DART todas las clases, implícitamente, definen una interface y por tanto, una clase abstracta (o clase normal) también puede ser empleada como interface, teniendo en cuenta que una interface está pensada para sólo tener definidas constantes o métodos sin código (sólo la definición).
+
* Desde Dart 3 existen también modificadores de clase específicos, entre ellos `interface`.
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="7,9,11,15,20,28-30,41-43" >
+
: Por ejemplo:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
interface class Ejemplo {
 +
} </syntaxhighlight>
 +
 
 +
: El modificador `interface` expresa que la clase puede utilizarse como interfaz desde otras bibliotecas.
 +
 
 +
: Cuando queremos definir una clase destinada únicamente a expresar un contrato y que, además, no pueda ser instanciada directamente, podemos combinar ambos modificadores:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
abstract interface class Ejemplo {
 +
} </syntaxhighlight>
 +
 
 +
===Clases abstractas===
 +
 
 +
: Veamos primero un ejemplo utilizando una clase abstracta como superclase:
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="">
 
void main() {
 
void main() {
 +
final alum1 = Alumno(
 +
numCarnet: 'C1A',
 +
nombreCompleto: 'Juan Luis Rodriguez',
 +
);
  
−
  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
final prof1 = Profesor(
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
−
  print(alum1);
+
print(alum1);
−
  alum1.pagarCafeteria();
+
alum1.pagarCafeteria();
−
  print(prof1);
+
 
−
  prof1.pagarCafeteria();
+
print(prof1);
−
 
+
prof1.pagarCafeteria();
−
  // Personal per1 = new Personal('Nombre Completo a enviar'); // ERROR. No se puede instanciar una clase abstracta
+
 
−
 
+
// final per1 = Personal('Nombre');
 +
// ERROR: no se pueden instanciar directamente las clases abstractas.
 
}
 
}
  
 
abstract class Personal {
 
abstract class Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
  
−
  Personal(this.nombreCompleto);
+
Personal(this.nombreCompleto);
−
 
 
−
  void pagarCafeteria();
 
  
 +
void pagarCafeteria();
 
}
 
}
−
class Alumno extends Personal{
 
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
class Alumno extends Personal {
 +
String numCarnet;
  
−
  void pagarCafeteria(){
+
Alumno({
−
    print('El alumno $numCarnet paga en efectivo');
+
required this.numCarnet,
−
  }
+
required String nombreCompleto,
−
 
+
}) : super(nombreCompleto);
−
  @override
+
 
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
+
@override
 +
void pagarCafeteria() {
 +
print('El alumno $numCarnet paga en efectivo');
 
}
 
}
  
−
class Profesor extends Personal{
+
@override
−
  String nrp;
+
String toString() =>
 +
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
 +
}
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
class Profesor extends Personal {
 +
String nrp;
  
−
  void pagarCafeteria(){
+
Profesor({
−
    print('El profesor $nrp paga con vale descuento');
+
required this.nrp,
−
  }
+
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
  @override
+
@override
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
void pagarCafeteria() {
 +
print('El profesor $nrp paga con vale descuento');
 
}
 
}
−
</syntaxhighlight>
+
 
 +
@override
 +
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 +
} </syntaxhighlight>
  
 
[[Imagen:Flutter_dart_abstract_1.JPG|500px|center]]
 
[[Imagen:Flutter_dart_abstract_1.JPG|500px|center]]
  
 +
<br />
 +
 +
===Interfaces===
 +
 +
* Cuando queremos definir principalmente un '''contrato''' que otras clases tengan que implementar podemos utilizar una `abstract interface class`.
 +
 +
: En este caso utilizamos `implements`.
  
−
<br />
+
: Cuando una clase implementa una interfaz debe proporcionar una implementación para todos sus miembros de instancia requeridos.
−
* Veamos ahora un caso de uso de interface.
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="11-17,19,28" >
 
  
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="">
 
void main() {
 
void main() {
−
    
+
   final cuadrado1 = Cuadrado(lado: 10);
−
  Cuadrado cuadrado1 = new Cuadrado(lado: 10);
+
   final circulo1 = Circulo(5);
−
   Circulo circulo1 = new Circulo(5);
 
  
 
   print('Área del cuadrado: ${cuadrado1.getArea()}');
 
   print('Área del cuadrado: ${cuadrado1.getArea()}');
 
   print('Área del círculo: ${circulo1.getArea()}');
 
   print('Área del círculo: ${circulo1.getArea()}');
−
 
 
 
}
 
}
  
−
abstract class Figura{
+
abstract interface class Figura {
−
 
+
   static const pi = 3.1416;
−
   static const PI = 3.1416;
+
 
−
 
 
 
   double getArea();
 
   double getArea();
−
 
 
 
}
 
}
  
−
class Cuadrado implements Figura{
+
class Cuadrado implements Figura {
−
    
+
   final double _lado;
−
  double _lado;
+
 
−
 
+
   Cuadrado({
−
   Cuadrado({required double lado}) : this._lado = lado;
+
    required double lado,
 +
  }) : _lado = lado;
  
−
   double getArea() => _lado*_lado;
+
  @override
−
 
+
   double getArea() => _lado * _lado;
 
}
 
}
−
class Circulo implements Figura{
+
 
−
    
+
class Circulo implements Figura {
−
  double _radio;
+
   final double _radio;
−
 
+
 
 
   Circulo(this._radio);
 
   Circulo(this._radio);
  
−
   double getArea() => Figura.PI*_radio*_radio;
+
  @override
−
 
+
   double getArea() => Figura.pi * _radio * _radio;
 
}
 
}
 +
</syntaxhighlight>
  
−
</syntaxhighlight>
+
: Fijarse en la diferencia entre los ejemplos anteriores:
  
 +
:* `Alumno extends Personal` indica una relación de '''herencia'''.
 +
:* `Cuadrado implements Figura` indica que `Cuadrado` se compromete a cumplir el '''contrato''' definido por `Figura`.
  
 +
===Polimorfismo===
  
 +
* Podemos utilizar una referencia cuyo tipo sea una clase abstracta o una interfaz para trabajar con objetos de diferentes clases que cumplan ese contrato.
  
−
* Recordar que el [https://wiki.cifprodolfoucha.es/index.php?title=Prog._Ampliaci%C3%B3n_POO#Interfaces uso de las interfaces] me va a permitir: Utilizar un mecanismo (el de la interface) que nos va a permitir relacionar a clases diferentes. Es decir, todas las clases que implementen una interface van a tener que implementar los mismos métodos (los definidos en la interface) y por lo tanto, podríamos hacer uso del polimorfismo y llamar a los métodos utilizando como referencia a objetos que implementen la interface aunque pertenezcan a clases diferentes.
+
: Por ejemplo:
  
−
: Veamos una modificación al ejercicio anterior:
+
:<syntaxhighlight lang="dart" enclose="div" highlight="">
−
:<syntaxhighlight lang="java" enclose="div" highlight="6,7,11-13" >
 
 
void main() {
 
void main() {
−
    
+
   final cuadrado1 = Cuadrado(lado: 10);
−
  Cuadrado cuadrado1 = new Cuadrado(lado: 10);
+
   final circulo1 = Circulo(5);
−
   Circulo circulo1 = new Circulo(5);
 
  
 
   imprimirArea(cuadrado1);
 
   imprimirArea(cuadrado1);
 
   imprimirArea(circulo1);
 
   imprimirArea(circulo1);
−
 
 
 
}
 
}
  
−
void imprimirArea(Figura figura){
+
void imprimirArea(Figura figura) {
−
   print('El área del ${figura.runtimeType} es ${figura.getArea()}');
+
   print(
 +
    'El área del ${figura.runtimeType} es ${figura.getArea()}',
 +
  );
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
: Tanto `Cuadrado` como `Circulo` pueden enviarse como argumento porque ambos implementan `Figura`.
 +
: En tiempo de ejecución se ejecutará el método `getArea()` correspondiente al tipo real del objeto.
  
 +
<br />
  
−
<br />
 
 
==Mixins==
 
==Mixins==
  
−
* Más información:  
+
* Más información:
−
:* https://dart.dev/guides/language/language-tour#adding-features-to-a-class-mixins
+
  :* [https://dart.dev/language/mixins Mixins]
−
:* https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3
+
  :* [https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3 Dart: What are mixins?]
−
 
 
−
 
 
−
* Los mixins es la forma que tiene DART de reutilizar código entre diferentes clases.
 
−
: Viene a ser algo parecido a extends - implements pero con la siguiente diferencia:
 
−
:* '''extends''': Sólo se puede heredar de una clase (no se permite la herencia múltiple). Automáticamente disponemos de todos los métodos/atributos definidos en la clase padre y podremos sobreescribir los métodos definidos previamente.
 
−
:* '''implements''': Una clase puede 'heredar' de múltiples clases (una forma de implementar la herencia múltiple). Obliga a implementar (o sobreescribir) todos los métodos y atributos definidos en la clase que se implementa.
 
−
:* '''mixins''': Una clase puede 'heredar' de múltiples clases, pero no tiene que sobreescribir los métodos o atributos definidos y puede hacer uso del código ya implementado en la clase que se implementa.
 
  
 +
* Los '''mixins''' permiten reutilizar implementaciones entre diferentes clases sin establecer necesariamente una relación de herencia entre ellas.
 +
  : Son especialmente útiles cuando queremos compartir comportamiento entre clases pertenecientes a diferentes jerarquías.
  
−
* Cualquier clase (real o abstracta) puede ser 'usada' como mixin, pero si modificamos su definición con la palabra clave '''mixin''' <u>no podrá ser heredada</u>.
+
: Los mixins se incorporan a una clase mediante la palabra reservada '''with'''.
  
−
: Veamos un ejemplo:
+
:<syntaxhighlight lang="dart" enclose="div" highlight="">
−
:<syntaxhighlight lang="java" enclose="div" highlight="1-6,8-12,15,22" >
+
mixin OperacionesListas {
−
mixin OperacionesListas{
+
   void imprimirLista(List<int> lista) {
−
 
+
     for (final elem in lista) {
−
   void imprimirLista(List<int> lista){
+
      print('Elemento de la lista: $elem');
−
     lista.forEach((elem) => print('Elmento de la lista:$elem'));
+
    }
 
   }
 
   }
 
}
 
}
  
−
mixin OperacionesMapas{
+
mixin OperacionesMapas {
−
   void imprimirMapa(Map<String,String> mapa){
+
   void imprimirMapa(Map<String, String> mapa) {
−
     mapa.forEach((clave,valor) => print('La $clave tiene el valor:$valor'));
+
     for (final entry in mapa.entries) {
 +
      print(
 +
        'La clave ${entry.key} tiene el valor: ${entry.value}',
 +
      );
 +
    }
 
   }
 
   }
 
}
 
}
  
 +
class ClaseListas with OperacionesListas {
 +
  List<int> lista;
  
−
class ClaseListas with OperacionesListas{
 
−
 
 
−
  List<int> lista;
 
 
   ClaseListas(this.lista);
 
   ClaseListas(this.lista);
−
 
 
 
}
 
}
  
−
class ClaseListasYMapas with OperacionesListas, OperacionesMapas{
+
class ClaseListasYMapas with OperacionesListas, OperacionesMapas {
 
   List<int> lista;
 
   List<int> lista;
−
   Map<String,String> mapa;
+
   Map<String, String> mapa;
−
 
+
 
 
   ClaseListasYMapas(this.lista, this.mapa);
 
   ClaseListasYMapas(this.lista, this.mapa);
 +
}
 +
 +
void main() {
 +
  final claseListas = ClaseListas(
 +
    [1, 2, 3, 4],
 +
  );
 +
 +
  final claseListasMapas = ClaseListasYMapas(
 +
    [10, 11, 12, 13],
 +
    {
 +
      'nombre': 'Jose',
 +
      'edad': '45',
 +
    },
 +
  );
 +
 +
  claseListas.imprimirLista(
 +
    claseListas.lista,
 +
  );
 +
 +
  claseListasMapas.imprimirLista(
 +
    claseListasMapas.lista,
 +
  );
 +
 +
  claseListasMapas.imprimirMapa(
 +
    claseListasMapas.mapa,
 +
  );
 +
}
 +
</syntaxhighlight>
 +
 +
<br />
 +
 +
==Ejercicios==
 +
<br />
 +
===Ejercicio 1===
 +
Crea un conjunto de clases para representar diferentes miembros de una escuela, tales como profesores y estudiantes.
 +
: Utilizarás herencia para compartir propiedades y comportamientos comunes, mientras que una interfaz permitirá definir el contrato que deberán cumplir los miembros del sistema.
 +
Requisitos del sistema:
 +
:* '''Interfaz Persona:'''
 +
:: Define Persona mediante una abstract interface class.
 +
:: Debe disponer de propiedades para el nombre y la edad, incluyendo los getters y setters correspondientes.
 +
:* '''Clase abstracta MiembroEscolar:'''
 +
:: Crea una clase abstracta MiembroEscolar que implemente Persona.
 +
:: Añade una propiedad id para identificar unívocamente al miembro.
 +
:: Implementa en esta clase los getters y setters definidos en Persona.
 +
:: El setter de la edad no debe permitir valores negativos.
 +
:: Define un método abstracto mostrarDatos().
 +
:* '''Clase Alumno:'''
 +
:: Debe heredar de MiembroEscolar.
 +
:: Añade una propiedad numExpediente.
 +
:: Sobrescribe el método mostrarDatos() para mostrar toda la información del alumno.
 +
:* '''Clase Profesor:'''
 +
:: Debe heredar de MiembroEscolar.
 +
:: Añade una propiedad nrp.
 +
:: Sobrescribe el método mostrarDatos() para mostrar toda la información del profesor.
 +
:* '''Programa principal:'''
 +
:: Crea un objeto de tipo Alumno y otro de tipo Profesor.
 +
:: Muestra sus datos utilizando el método mostrarDatos().
 +
:: Modifica la edad de alguno de ellos utilizando el setter y vuelve a mostrar sus datos.
 +
<br />
 +
===Ejercicio 2===
 +
Crea un programa que utilice '''mixins''' para añadir funcionalidades comunes a distintas clases sin utilizar herencia múltiple.
 +
Requisitos del sistema:
 +
:* '''Mixin Registrable:'''
 +
:: Define un mixin denominado Registrable.
 +
:: Debe contener un método registrarEntrada() que muestre por pantalla un mensaje indicando que se ha registrado una entrada en el sistema.
 +
:* '''Mixin Notificable:'''
 +
:: Define un mixin denominado Notificable.
 +
:: Debe contener un método enviarNotificacion() que muestre un mensaje indicando que se ha enviado una notificación.
 +
:* '''Clase Empleado:'''
 +
:: Debe disponer de las propiedades nombre y departamento.
 +
:: Debe utilizar los mixins Registrable y Notificable.
 +
:* '''Clase Visitante:'''
 +
:: Debe disponer de las propiedades nombre y empresa.
 +
:: Debe utilizar únicamente el mixin Registrable.
 +
:* '''Programa principal:'''
 +
:: Crea un objeto de tipo Empleado y otro de tipo Visitante.
 +
:: Llama a los métodos proporcionados por sus respectivos mixins.
 +
:: Comprueba que Empleado dispone de las dos funcionalidades mientras que Visitante únicamente puede registrar su entrada.
 +
<br />
 +
===Ejercicio 3===
 +
Crea una clase que permita transformar los datos recibidos en un Map<String, dynamic> en un objeto Dart.
 +
Requisitos del sistema:
 +
:* '''Clase Producto:'''
 +
:: Debe disponer de las propiedades id, nombre y precio.
 +
:: id será de tipo int.
 +
:: nombre será de tipo String.
 +
:: precio será de tipo double.
 +
:* '''Constructor principal:'''
 +
:: Define un constructor utilizando parámetros con nombre y obligatorios mediante required.
 +
:* '''Constructor factory:'''
 +
:: Define un constructor factory denominado Producto.fromJson.
 +
:: Recibirá un Map<String, dynamic>.
 +
:: Utiliza int.tryParse() para convertir el identificador.
 +
:: Utiliza double.tryParse() para convertir el precio.
 +
:: Si alguno de los dos valores no puede convertirse correctamente, lanza una FormatException.
 +
:* '''Método toString:'''
 +
:: Sobrescribe el método toString() para devolver la información del producto.
 +
:* '''Programa principal:'''
 +
:: Crea un mapa que represente un producto.
 +
:: Crea un objeto Producto utilizando Producto.fromJson().
 +
:: Muestra el objeto por pantalla.
 +
<br /> <br />
 +
 +
 +
==Soluciones==
 +
 +
<br />
 +
 +
===Solución Ejercicio 1===
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
abstract interface class Persona {
 +
  String get nombre;
 +
  set nombre(String valor);
 +
 +
  int get edad;
 +
  set edad(int valor);
 +
}
 +
 +
abstract class MiembroEscolar implements Persona {
 +
  final int id;
 +
  String _nombre;
 +
  int _edad;
 +
 +
  MiembroEscolar({required this.id, required String nombre, required int edad})
 +
    : _nombre = nombre,
 +
      _edad = edad >= 0 ? edad : 0;
 +
 +
  @override
 +
  String get nombre => _nombre;
 +
 +
  @override
 +
  set nombre(String valor) {
 +
    _nombre = valor;
 +
  }
 +
 +
  @override
 +
  int get edad => _edad;
 +
 +
  @override
 +
  set edad(int valor) {
 +
    _edad = valor >= 0 ? valor : 0;
 +
  }
 +
 +
  void mostrarDatos();
 +
}
 +
 +
class Alumno extends MiembroEscolar {
 +
  String numExpediente;
 +
 +
  Alumno({
 +
    required super.id,
 +
    required super.nombre,
 +
    required super.edad,
 +
    required this.numExpediente,
 +
  });
 +
 +
  @override
 +
  void mostrarDatos() {
 +
    print('Alumno: $nombre');
 +
    print('Edad: $edad');
 +
    print('Id: $id');
 +
    print('Número de expediente: $numExpediente');
 +
  }
 +
}
 +
 +
class Profesor extends MiembroEscolar {
 +
  String nrp;
 +
 +
  Profesor({
 +
    required super.id,
 +
    required super.nombre,
 +
    required super.edad,
 +
    required this.nrp,
 +
  });
 +
 +
  @override
 +
  void mostrarDatos() {
 +
    print('Profesor: $nombre');
 +
    print('Edad: $edad');
 +
    print('Id: $id');
 +
    print('NRP: $nrp');
 +
  }
 +
}
 +
 +
void main() {
 +
  final alumno = Alumno(
 +
    id: 1,
 +
    nombre: 'Ana López',
 +
    edad: 20,
 +
    numExpediente: 'EXP001',
 +
  );
 +
 +
  final profesor = Profesor(
 +
    id: 2,
 +
    nombre: 'Luis García',
 +
    edad: 45,
 +
    nrp: 'NRP12345',
 +
  );
 +
 +
  alumno.mostrarDatos();
 +
 +
  print('');
 +
 +
  profesor.mostrarDatos();
 +
 +
  print('');
 +
 +
  alumno.edad = 21;
 +
  alumno.mostrarDatos();
 +
}
 +
 +
</syntaxhighlight>
 +
 +
<br />
 +
 +
===Solución Ejercicio 2===
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
mixin Registrable {
 +
  void registrarEntrada() {
 +
    print('Entrada registrada en el sistema');
 +
  }
 +
}
 +
 +
mixin Notificable {
 +
  void enviarNotificacion() {
 +
    print('Notificación enviada');
 +
  }
 +
}
 +
 +
class Empleado with Registrable, Notificable {
 +
  String nombre;
 +
  String departamento;
 +
 +
  Empleado({required this.nombre, required this.departamento});
 +
}
 +
 +
class Visitante with Registrable {
 +
  String nombre;
 +
  String empresa;
 +
 +
  Visitante({required this.nombre, required this.empresa});
 +
}
 +
 +
void main() {
 +
  final empleado = Empleado(nombre: 'Laura', departamento: 'Informática');
 +
 +
  final visitante = Visitante(nombre: 'Carlos', empresa: 'Empresa S.A.');
 +
 +
  print('Empleado: ${empleado.nombre}');
 +
  empleado.registrarEntrada();
 +
  empleado.enviarNotificacion();
 +
 +
  print('');
 +
 +
  print('Visitante: ${visitante.nombre}');
 +
  visitante.registrarEntrada();
 +
}
 +
 +
</syntaxhighlight>
 +
 +
: En este ejemplo la clase `Empleado` incorpora las funcionalidades de los dos mixins:
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
class Empleado with Registrable, Notificable
 +
</syntaxhighlight>
 +
 +
: Por el contrario, `Visitante` solamente incorpora `Registrable`:
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
class Visitante with Registrable
 +
</syntaxhighlight>
 +
 +
: Por tanto, el siguiente código produciría un error:
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
visitante.enviarNotificacion();
 +
</syntaxhighlight>
 +
 +
<br />
 +
 +
===Solución Ejercicio 3===
 +
 +
:<syntaxhighlight lang="dart" enclose="div">
 +
class Producto {
 +
  int id;
 +
  String nombre;
 +
  double precio;
 +
 +
  Producto({required this.id, required this.nombre, required this.precio});
 +
 +
  factory Producto.fromJson(Map<String, dynamic> dato) {
 +
   
 +
    final id = int.tryParse(dato['id'].toString());
 +
    final precio = double.tryParse(dato['precio'].toString());
 +
 +
    if (id == null) {
 +
      throw FormatException('El identificador no es válido');
 +
    }
 +
 +
    if (precio == null) {
 +
      throw FormatException('El precio no es válido');
 +
    }
 +
 +
    return Producto(id: id, nombre: dato['nombre'] as String, precio: precio);
 +
  }
 +
 +
  @override
 +
  String toString() {
 +
    return 'Producto: $nombre - Id: $id - Precio: $precio €';
 +
  }
 
}
 
}
  
 
void main() {
 
void main() {
 
    
 
    
−
   final claseListas = new ClaseListas([1,2,3,4]);
+
   final productoJson = <String, dynamic>{
−
  final claseListasMapas = new ClaseListasYMapas([10,11,12,13],{'nombre' : 'Jose', 'edad' : '45'});
+
    'id': '25',
−
    
+
    'nombre': 'Teclado',
−
  claseListas.imprimirLista(claseListas.lista);
+
    'precio': '29.95',
−
  claseListasMapas.imprimirLista(claseListasMapas.lista);
+
   };
−
   claseListasMapas.imprimirMapa(claseListasMapas.mapa);
+
 
 +
   final producto = Producto.fromJson(productoJson);
  
 +
  print(producto);
 
}
 
}
  
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
: En este ejemplo utilizamos `tryParse()` en lugar de `parse()`:
  
−
* Como ejercicio podéis implementar el ejemplo que aparece en el segundo enlace de esta sección.
+
:<syntaxhighlight lang="dart" enclose="div">
 +
    final id = int.tryParse(dato['id'].toString());
 +
    final precio = double.tryParse(dato['precio'].toString());
 +
</syntaxhighlight>
  
 +
: La principal diferencia es que `tryParse()` devuelve `null` cuando el valor no puede convertirse.
  
 +
: Esto nos permite comprobar explícitamente si la conversión ha sido correcta:
  
−
<br />
+
:<syntaxhighlight lang="dart" enclose="div">
 +
if (id == null) {
 +
  throw FormatException('El identificador no es válido');
 +
}
 +
</syntaxhighlight>
  
−
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER#Unidad_2:_Lenguaje_DART Enlace a la página principal de la UD2]
+
: De esta forma podemos controlar el error antes de crear el objeto, en lugar de depender directamente de la excepción producida por `parse()`.
  
−
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER Enlace a la página principal del curso]
 
  
  
  
 +
<br />
 +
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER#Unidad_2:_Lenguaje_DART Enlace a la página principal de la UD2]
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2021).
+
[https://wiki.cifprodolfoucha.es/index.php?title=Programaci%C3%B3n_de_dispositivos_m%C3%B3viles_con_FLUTTER Enlace a la página principal del curso]

Revisión actual del 10:53 4 oct 2026

Introducción

  • Información: En VS Code, para que aparezcan las sugerencias de métodos, propiedades y otros miembros disponibles podemos emplear la combinación de teclas: Control + Barra Espaciadora.


  • 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 van a explicar las diferencias y características de Dart con respecto a otros lenguajes orientados a objetos en lo que se refiere al uso de clases y objetos.
  • En Dart no es necesario utilizar `new` cuando instanciamos una clase.
 : Aunque la palabra clave `new` sigue siendo válida al invocar un constructor se aconseja omitirla.
 : Esto resulta especialmente útil en Flutter, donde se crean objetos y widgets de forma continua y el uso repetido de `new` haría el código más redundante.
Veamos un ejemplo, empleando conceptos habituales de otros lenguajes de programación orientados a objetos, como la redefinición del método `toString`:
class Vehiculo {
int? anoCompra; // Admite null porque inicialmente no le asignamos ningún valor.
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 = Vehiculo();
veh1.marca = 'Opel';
veh1.matricula = '1234GHZ';
veh1.anoCompra = 2018;

print(veh1);

final veh2 = Vehiculo();
veh2.marca = 'Peugeot';
veh2.matricula = '6543HZZ';
veh2.anoCompra = 2020;

print(veh2);
}


Constructores

  • Los constructores tienen una finalidad similar a los de Java: permiten inicializar un objeto en el momento de crearlo.
 Sin embargo, Dart incorpora algunas características propias, como los constructores con nombre, las listas de inicialización, los constructores `factory` y los súper parámetros.
 Veamos un ejemplo de uso:
class Vehiculo {
int? anoCompra;
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 = Vehiculo(2018, '1234GHZ', 'Opel');
print(veh1);

final veh2 = Vehiculo(2020, '6543HZZ', 'Peugeot');
print(veh2);
}
Un constructor es una función especial asociada a la creación e inicialización de objetos. No debe confundirse con un método ordinario.
Los constructores pueden recibir parámetros posicionales, opcionales o con nombre, al igual que otras funciones de Dart.
Ejercicio propuesto: Modifica el ejercicio anterior y haz que 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 las propiedades admiten valores nulos porque no se inicializan en el momento de declararlas.
 : Sin embargo, si siempre vamos a proporcionar sus valores al crear el objeto, podemos hacer que sean no anulables e inicializarlas directamente desde el constructor.
 : Dart proporciona una sintaxis abreviada para realizar esta asignación:
class Vehiculo {
int anoCompra;
String matricula;
String marca;

Vehiculo(this.anoCompra, this.marca, 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 = Vehiculo(2018, 'Opel', '1234GHZ');
print(veh1);

final veh2 = Vehiculo(2020, 'Peugeot', '6543HZZ');
print(veh2);
}
En `Vehiculo(this.anoCompra, this.marca, this.matricula)`, Dart asigna automáticamente cada parámetro recibido a la propiedad correspondiente del objeto.



  • Si queremos emplear parámetros con nombre debemos utilizar las llaves { }.
Si además queremos que sea obligatorio indicar esos parámetros al llamar al constructor, emplearemos 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 = Vehiculo(
anoCompra: 2018,
marca: 'Opel',
matricula: '1234GHZ',
);
print(veh1);

final veh2 = Vehiculo(
anoCompra: 2020,
marca: 'Peugeot',
matricula: '6543HZZ',
);
print(veh2);
}



NOTAS IMPORTANTES:

  • En Dart los constructores no se heredan.
  • Para inicializar la parte correspondiente a una clase superior podemos llamar a uno de sus constructores mediante `super`.




Múltiples constructores. Constructores con nombre

  • Dart no permite la sobrecarga tradicional de métodos o constructores, es decir, no podemos declarar varias veces un método o constructor con el mismo nombre y diferenciarlos únicamente por el número o tipo de sus parámetros.
 : Para proporcionar diferentes formas de crear un objeto, Dart permite utilizar constructores con nombre.
 : Su forma general es:

NombreClase.nombreConstructor(parámetros)

Veamos un ejemplo:
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 = Vehiculo(anoCompra: 2018, marca: 'Opel', matricula: '1234GHZ');
  print(veh1);

  final veh2 = Vehiculo.sinAnho(marca: 'Opel', matricula: '2234GHZ');
  print(veh2);

  final veh3 = Vehiculo.sinAnhoMarca(matricula: '3234GHZ');
  print(veh3);
}
  • Nota importante: En los constructores anteriores no recibimos todos los valores necesarios para inicializar las propiedades del objeto.
 : Como las propiedades `anoCompra` y `marca` no admiten valores nulos, debemos inicializarlas antes de que finalice la construcción del objeto.
 : Para hacerlo hemos empleado una característica de Dart denominada lista de inicialización.
La lista de inicialización aparece después de los parámetros del constructor, precedida por dos puntos (`:`):
Vehiculo.sinAnhoMarca({required this.matricula,}) : anoCompra = 2000, marca = 'Opel';
Las expresiones de la lista de inicialización se ejecutan antes del cuerpo del constructor.
Más información: Constructores en Dart.


Constructores Factory

  • Los constructores `factory` se utilizan cuando la creación de un objeto necesita un comportamiento diferente al de un constructor generativo convencional.


  • Por ejemplo, pueden utilizarse cuando:
  • queremos devolver una instancia creada anteriormente, como ocurre al utilizar una caché;
  • queremos devolver una instancia de una subclase compatible;
  • queremos ejecutar lógica previa antes de decidir qué objeto devolver;
  • queremos transformar o interpretar información antes de delegar la construcción a otro constructor.
Se definen anteponiendo la palabra reservada factory al nombre del constructor.
Un constructor `factory` debe devolver un objeto compatible con el tipo de la clase. Puede hacerlo mediante `return` o utilizando la sintaxis de función flecha (`=>`).
Un uso habitual consiste en delegar la creación del objeto al constructor principal.
Veamos un ejemplo:
class Vehiculo {
  int anoCompra;
  String matricula;
  String marca;

  Vehiculo({
    required this.anoCompra,
    required this.marca,
    required this.matricula,
  });

  factory Vehiculo.sinAnho({
    required String marca,
    required String matricula,
  }) => Vehiculo(anoCompra: 2020, marca: marca, 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 veh2 = Vehiculo.sinAnho(marca: 'Opel', matricula: '2234GHZ');

  print(veh2);
}


Empleando Mapas

  • En esta sección vamos a ver cómo crear un objeto utilizando como fuente de datos un mapa.
 :* Debéis recordar lo explicado acerca de los valores nulos al emplear mapas.
 :* También utilizaremos las listas de inicialización vistas anteriormente.
Partimos de un mapa que almacena la información de un vehículo.
Es habitual recibir datos procedentes de servicios web en formato JSON y, una vez decodificados, trabajar con estructuras del tipo `Map<String, dynamic>`.


class Vehiculo {
  int anoCompra;
  String matricula;
  String marca;

  Vehiculo({
    required this.anoCompra,
    required this.marca,
    required this.matricula,
  });

  Vehiculo.fromMap(Map<String, dynamic> dato)
    : anoCompra = int.parse(dato['anoCompra'].toString()),
      matricula = dato['matricula'] as String,
      marca = dato['marca'] as String;

  @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 = <String, dynamic>{
    'anoCompra': '2020',
    'matricula': '1234GHZ',
    'marca': 'Opel',
  };

  final veh1 = Vehiculo.fromMap(vehiculoJson);

  print(veh1);
}

}
El tipo `Map<String, dynamic>` indica que las claves del mapa son cadenas de caracteres, mientras que sus valores pueden ser de diferentes tipos.



  • Importante: utilizar `dynamic` no garantiza que los datos sean correctos ni evita errores en tiempo de ejecución.
Por ejemplo:
  • `dato['matricula'] as String` producirá un error si el valor existente no es un `String`.
  • `int.parse(...)` producirá un `FormatException` si el contenido no puede convertirse a un número entero.
  • Que la clave no exista en los datos recibidos de internet.
Por tanto, al trabajar con información externa conviene comprobar y validar los valores recibidos antes de utilizarlos.
USAREMOS ESTA FORMA PARA PROCESAR LOS DATOS QUE PROVENGAN DE UNA FUENTE DE DATOS EXTERNA
class Vehiculo {
  int anoCompra;
  String matricula;
  String marca;

  Vehiculo({
    required this.anoCompra,
    required this.marca,
    required this.matricula,
  });

  Vehiculo.fromMap(Map<String, dynamic> dato)
    : anoCompra = int.tryParse(dato['anoCompra']?.toString() ?? '') ?? 1990,
      matricula = dato['matricula']?.toString() ?? '',
      marca = dato['marca']?.toString() ?? '';

  @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 = <String, dynamic>{
    'anoCompra': '2020',
    'matricula': '1234GHZ',
    'marca': 'Opel',
  };

  final veh1 = Vehiculo.fromMap(vehiculoJson);

  print(veh1);
}

}




  • Podéis emplear Quicktype para generar automáticamente modelos a partir de una estructura JSON.
  • También podríamos utilizar un constructor `factory`, como vimos en el apartado anterior:
class Vehiculo {
int anoCompra;
String matricula;
String marca;

Vehiculo({
required this.anoCompra,
required this.marca,
required this.matricula,
});

factory Vehiculo.fromJson(Map<String, dynamic> dato) => Vehiculo( 
      anoCompra : int.tryParse(dato['anoCompra']?.toString() ?? '') ?? 1990,
      matricula : dato['matricula']?.toString() ?? '',
      marca : dato['marca']?.toString() ?? '');

@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 = <String, dynamic>{
'anoCompra': '2020',
'matricula': '1234GHZ',
'marca': 'Opel',
};

final veh1 = Vehiculo.fromJson(vehiculoJson);

print(veh1);
}


Herencia

  • Para indicar que una clase hereda de otra empleamos la palabra reservada extends.
 : La palabra reservada `super` permite acceder a miembros de la superclase y llamar a sus constructores.
Veamos un ejemplo utilizando súper parámetros, disponibles desde Dart 2.17.
Los súper parámetros permiten enviar directamente un parámetro al constructor de la superclase sin tener que declararlo de nuevo y llamar explícitamente a `super(...)`.
class Personal {
String nombreCompleto;

Personal({required this.nombreCompleto});
}

class Alumno extends Personal {
String numCarnet;

Alumno({
required this.numCarnet,
required 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 super.nombreCompleto,
});

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

void main() {
final alum1 = Alumno(
numCarnet: 'C1A',
nombreCompleto: 'Juan Luis Rodriguez',
);

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

print(alum1);
print(prof1);
}


Overriding - Sobrescritura

  • La sobrescritura (overriding) permite que una subclase proporcione su propia implementación de un miembro que ya existe en la superclase.
 : Es uno de los mecanismos que permiten aplicar polimorfismo en una jerarquía de herencia.
Ya hemos utilizado este concepto al sobrescribir el método `toString`, heredado de la clase `Object`.
  • Es recomendable emplear la anotación `@override` cuando sobrescribimos un miembro.
 : No es obligatoria para que se produzca la sobrescritura, pero permite que el analizador de Dart compruebe nuestra intención y detecte posibles errores.
  • Nota: No debemos confundir sobrescritura (overriding) con sobrecarga (overloading).
 : La sobrecarga consiste en definir varios métodos con el mismo nombre pero diferentes listas de parámetros.
 : Dart no soporta este tipo de sobrecarga tradicional.
Veamos un ejemplo en el que definimos el método `imprimirDatos` en la superclase.
Este método es heredado por las subclases.
La clase `Alumno` lo sobrescribe y, además, llama desde su nueva implementación al método existente en la clase padre mediante `super.imprimirDatos()`.
class Personal {
String nombreCompleto;

Personal(this.nombreCompleto);

void imprimirDatos() {
print('Nombre completo: $nombreCompleto');
}
}

class Alumno extends Personal {
String numCarnet;

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

@override
void imprimirDatos() {
super.imprimirDatos();
print('Carnet: $numCarnet');
}

@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 = Alumno(
numCarnet: 'C1A',
nombreCompleto: 'Juan Luis Rodriguez',
);

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

print(alum1);
print(prof1);

alum1.imprimirDatos();
prof1.imprimirDatos();
}


Librerías. Privado/Público

  • Un fichero `.dart` puede contener múltiples declaraciones: clases, funciones, variables, etc.
 : Normalmente, un fichero Dart constituye una biblioteca (library).
 : También es posible que una biblioteca esté formada por varios ficheros mediante el mecanismo `part`.
Podemos agrupar bibliotecas y otros recursos para formar paquetes.
Más información: Libraries & imports.
  • Para utilizar declaraciones definidas en otra biblioteca debemos importar dicha biblioteca utilizando la palabra reservada import.
  • Este ejemplo será necesario realizarlo en un IDE como Android Studio o VS Code.
Supongamos que tenemos un archivo denominado centro.dart con las siguientes clases:
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 queremos utilizar estas clases desde otro archivo Dart.
Para ello empleamos `import`:
import 'centro.dart';

void main() {
final alum1 = Alumno(
numCarnet: 'C1A',
nombreCompleto: 'Juan Luis Rodriguez',
);

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

print(alum1);
print(prof1);
}


  • Dart incluye numerosas bibliotecas que podemos utilizar mediante el `import` correspondiente.
 : Por ejemplo, la biblioteca dart:math proporciona funciones y clases relacionadas con operaciones matemáticas y generación de números pseudoaleatorios.
import 'dart:math';

void main() {
final semilla = Random();

for (var cont = 0; cont < 10; cont++) {
print('Aleatorio generado: ${semilla.nextInt(50)}');
}
}
  • Podemos asignar un alias o prefijo a una biblioteca mediante la palabra reservada as:
import 'dart:math' as math;

void main() {
final semilla = math.Random();

for (var cont = 0; cont < 10; cont++) {
print('Aleatorio generado: ${semilla.nextInt(50)}');
}
}



  • Si una biblioteca contiene muchas declaraciones podemos importar únicamente aquellas que queremos utilizar mediante la palabra reservada `show`:
import 'package02/centro/centro.dart' show Alumno, Profesor;
`show` no se limita a clases. Puede utilizarse con cualquier nombre exportado por la biblioteca, como funciones, variables, enumeraciones, etc.
  • También podemos hacer lo contrario mediante `hide`, importando todas las declaraciones públicas excepto las indicadas:
import 'package02/centro/centro.dart' hide Personal;




  • En Dart no existen modificadores de acceso `public`, `private` y `protected` equivalentes a los de lenguajes como Java.
Los identificadores son públicos de forma predeterminada.
Para declarar un miembro o una clase como privado debemos anteponer un guion bajo (`_`) a su nombre.
Importante: La privacidad en Dart se aplica a nivel de biblioteca, no estrictamente a nivel de clase ni de fichero.
Por tanto, un identificador que comienza por `_` puede ser utilizado desde cualquier código perteneciente a la misma biblioteca, pero no desde una biblioteca externa.
  • Siguiendo el ejemplo anterior, si no queremos exponer directamente la clase `Personal` fuera de su biblioteca podemos declararla como `_Personal`:
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';
}
Desde otra biblioteca que importe este fichero no podremos referenciar directamente la clase `_Personal`:
Flutter dart private 1.JPG
  • El mismo mecanismo puede aplicarse a métodos, propiedades, funciones, variables y otras declaraciones.
Nota: El carácter `_` controla la visibilidad de un identificador, pero no impide que la propia biblioteca cree objetos de esa clase.
Si queremos indicar que una clase no debe poder instanciarse directamente, podemos declararla como `abstract`, como veremos a continuación.


Getters y Setters

  • Los getters y setters son miembros especiales que permiten controlar el acceso y la modificación de determinadas propiedades manteniendo una sintaxis similar a la lectura o asignación directa de una variable.
  • Un getter (`get`) permite obtener un valor.
  • Un setter (`set`) permite asignar o modificar un valor.
La llamada a un getter o setter se realiza sin utilizar paréntesis.
void main() {
final prof1 = Profesor(
nrp: '1234534',
nombreCompleto: 'Luisa Sanchez Dragó',
);

print(prof1);

print('Sueldo: ${prof1.sueldo}');

prof1.sueldo = 100.34; // Llama al setter.
print('Nuevo sueldo: ${prof1.sueldo}');

prof1.sueldo = -50; // El setter ajustará el valor.
print('Nuevo sueldo tras intento negativo: ${prof1.sueldo}');
}

class _Personal {
String nombreCompleto;

_Personal(this.nombreCompleto);
}

class Profesor extends _Personal {
String nrp;
double _sueldo = 0;

double get sueldo => _sueldo;

set sueldo(double nuevoSueldo) {
_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';
}


  • También podemos utilizar getters y setters para validar los valores almacenados en las propiedades de una clase:
void main() {
final alum1 = 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) {
_numCarnet = numero.isNotEmpty ? numero : '1';
}

Alumno({
required String num,
required String nombreCompleto,
})  : _numCarnet = num.isNotEmpty ? num : '1',
super(nombreCompleto);

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



Clases abstractas / Interfaces

  • Más información:
 :* Class modifiers
 :* Abstract
 :* Interface
  • Una clase abstracta se declara utilizando el modificador `abstract`.
 : Una clase abstracta no puede instanciarse directamente.
 : Puede contener miembros con implementación y miembros abstractos, es decir, miembros cuya implementación tendrán que proporcionar las subclases.
  • Además, en Dart toda clase define implícitamente una interfaz formada por los miembros de instancia que otras clases pueden implementar mediante `implements`.
  • Desde Dart 3 existen también modificadores de clase específicos, entre ellos `interface`.
Por ejemplo:
interface class Ejemplo {
}
El modificador `interface` expresa que la clase puede utilizarse como interfaz desde otras bibliotecas.
Cuando queremos definir una clase destinada únicamente a expresar un contrato y que, además, no pueda ser instanciada directamente, podemos combinar ambos modificadores:
abstract interface class Ejemplo {
}

Clases abstractas

Veamos primero un ejemplo utilizando una clase abstracta como superclase:
void main() {
final alum1 = Alumno(
numCarnet: 'C1A',
nombreCompleto: 'Juan Luis Rodriguez',
);

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

print(alum1);
alum1.pagarCafeteria();

print(prof1);
prof1.pagarCafeteria();

// final per1 = Personal('Nombre');
// ERROR: no se pueden instanciar directamente las clases abstractas.
}

abstract class Personal {
String nombreCompleto;

Personal(this.nombreCompleto);

void pagarCafeteria();
}

class Alumno extends Personal {
String numCarnet;

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

@override
void pagarCafeteria() {
print('El alumno $numCarnet paga en efectivo');
}

@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
void pagarCafeteria() {
print('El profesor $nrp paga con vale descuento');
}

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


Interfaces

  • Cuando queremos definir principalmente un contrato que otras clases tengan que implementar podemos utilizar una `abstract interface class`.
En este caso utilizamos `implements`.
Cuando una clase implementa una interfaz debe proporcionar una implementación para todos sus miembros de instancia requeridos.
void main() {
  final cuadrado1 = Cuadrado(lado: 10);
  final circulo1 = Circulo(5);

  print('Área del cuadrado: ${cuadrado1.getArea()}');
  print('Área del círculo: ${circulo1.getArea()}');
}

abstract interface class Figura {
  static const pi = 3.1416;

  double getArea();
}

class Cuadrado implements Figura {
  final double _lado;

  Cuadrado({
    required double lado,
  }) : _lado = lado;

  @override
  double getArea() => _lado * _lado;
}

class Circulo implements Figura {
  final double _radio;

  Circulo(this._radio);

  @override
  double getArea() => Figura.pi * _radio * _radio;
}
Fijarse en la diferencia entre los ejemplos anteriores:
  • `Alumno extends Personal` indica una relación de herencia.
  • `Cuadrado implements Figura` indica que `Cuadrado` se compromete a cumplir el contrato definido por `Figura`.

Polimorfismo

  • Podemos utilizar una referencia cuyo tipo sea una clase abstracta o una interfaz para trabajar con objetos de diferentes clases que cumplan ese contrato.
Por ejemplo:
void main() {
  final cuadrado1 = Cuadrado(lado: 10);
  final circulo1 = Circulo(5);

  imprimirArea(cuadrado1);
  imprimirArea(circulo1);
}

void imprimirArea(Figura figura) {
  print(
    'El área del ${figura.runtimeType} es ${figura.getArea()}',
  );
}
Tanto `Cuadrado` como `Circulo` pueden enviarse como argumento porque ambos implementan `Figura`.
En tiempo de ejecución se ejecutará el método `getArea()` correspondiente al tipo real del objeto.


Mixins

  • Más información:
 :* Mixins
 :* Dart: What are mixins?
  • Los mixins permiten reutilizar implementaciones entre diferentes clases sin establecer necesariamente una relación de herencia entre ellas.
 : Son especialmente útiles cuando queremos compartir comportamiento entre clases pertenecientes a diferentes jerarquías.
Los mixins se incorporan a una clase mediante la palabra reservada with.
mixin OperacionesListas {
  void imprimirLista(List<int> lista) {
    for (final elem in lista) {
      print('Elemento de la lista: $elem');
    }
  }
}

mixin OperacionesMapas {
  void imprimirMapa(Map<String, String> mapa) {
    for (final entry in mapa.entries) {
      print(
        'La clave ${entry.key} tiene el valor: ${entry.value}',
      );
    }
  }
}

class ClaseListas with OperacionesListas {
  List<int> lista;

  ClaseListas(this.lista);
}

class ClaseListasYMapas with OperacionesListas, OperacionesMapas {
  List<int> lista;
  Map<String, String> mapa;

  ClaseListasYMapas(this.lista, this.mapa);
}

void main() {
  final claseListas = ClaseListas(
    [1, 2, 3, 4],
  );

  final claseListasMapas = ClaseListasYMapas(
    [10, 11, 12, 13],
    {
      'nombre': 'Jose',
      'edad': '45',
    },
  );

  claseListas.imprimirLista(
    claseListas.lista,
  );

  claseListasMapas.imprimirLista(
    claseListasMapas.lista,
  );

  claseListasMapas.imprimirMapa(
    claseListasMapas.mapa,
  );
}


Ejercicios


Ejercicio 1

Crea un conjunto de clases para representar diferentes miembros de una escuela, tales como profesores y estudiantes.

Utilizarás herencia para compartir propiedades y comportamientos comunes, mientras que una interfaz permitirá definir el contrato que deberán cumplir los miembros del sistema.

Requisitos del sistema:

  • Interfaz Persona:
Define Persona mediante una abstract interface class.
Debe disponer de propiedades para el nombre y la edad, incluyendo los getters y setters correspondientes.
  • Clase abstracta MiembroEscolar:
Crea una clase abstracta MiembroEscolar que implemente Persona.
Añade una propiedad id para identificar unívocamente al miembro.
Implementa en esta clase los getters y setters definidos en Persona.
El setter de la edad no debe permitir valores negativos.
Define un método abstracto mostrarDatos().
  • Clase Alumno:
Debe heredar de MiembroEscolar.
Añade una propiedad numExpediente.
Sobrescribe el método mostrarDatos() para mostrar toda la información del alumno.
  • Clase Profesor:
Debe heredar de MiembroEscolar.
Añade una propiedad nrp.
Sobrescribe el método mostrarDatos() para mostrar toda la información del profesor.
  • Programa principal:
Crea un objeto de tipo Alumno y otro de tipo Profesor.
Muestra sus datos utilizando el método mostrarDatos().
Modifica la edad de alguno de ellos utilizando el setter y vuelve a mostrar sus datos.


Ejercicio 2

Crea un programa que utilice mixins para añadir funcionalidades comunes a distintas clases sin utilizar herencia múltiple. Requisitos del sistema:

  • Mixin Registrable:
Define un mixin denominado Registrable.
Debe contener un método registrarEntrada() que muestre por pantalla un mensaje indicando que se ha registrado una entrada en el sistema.
  • Mixin Notificable:
Define un mixin denominado Notificable.
Debe contener un método enviarNotificacion() que muestre un mensaje indicando que se ha enviado una notificación.
  • Clase Empleado:
Debe disponer de las propiedades nombre y departamento.
Debe utilizar los mixins Registrable y Notificable.
  • Clase Visitante:
Debe disponer de las propiedades nombre y empresa.
Debe utilizar únicamente el mixin Registrable.
  • Programa principal:
Crea un objeto de tipo Empleado y otro de tipo Visitante.
Llama a los métodos proporcionados por sus respectivos mixins.
Comprueba que Empleado dispone de las dos funcionalidades mientras que Visitante únicamente puede registrar su entrada.


Ejercicio 3

Crea una clase que permita transformar los datos recibidos en un Map<String, dynamic> en un objeto Dart. Requisitos del sistema:

  • Clase Producto:
Debe disponer de las propiedades id, nombre y precio.
id será de tipo int.
nombre será de tipo String.
precio será de tipo double.
  • Constructor principal:
Define un constructor utilizando parámetros con nombre y obligatorios mediante required.
  • Constructor factory:
Define un constructor factory denominado Producto.fromJson.
Recibirá un Map<String, dynamic>.
Utiliza int.tryParse() para convertir el identificador.
Utiliza double.tryParse() para convertir el precio.
Si alguno de los dos valores no puede convertirse correctamente, lanza una FormatException.
  • Método toString:
Sobrescribe el método toString() para devolver la información del producto.
  • Programa principal:
Crea un mapa que represente un producto.
Crea un objeto Producto utilizando Producto.fromJson().
Muestra el objeto por pantalla.




Soluciones


Solución Ejercicio 1

abstract interface class Persona {
  String get nombre;
  set nombre(String valor);

  int get edad;
  set edad(int valor);
}

abstract class MiembroEscolar implements Persona {
  final int id;
  String _nombre;
  int _edad;

  MiembroEscolar({required this.id, required String nombre, required int edad})
    : _nombre = nombre,
      _edad = edad >= 0 ? edad : 0;

  @override
  String get nombre => _nombre;

  @override
  set nombre(String valor) {
    _nombre = valor;
  }

  @override
  int get edad => _edad;

  @override
  set edad(int valor) {
    _edad = valor >= 0 ? valor : 0;
  }

  void mostrarDatos();
}

class Alumno extends MiembroEscolar {
  String numExpediente;

  Alumno({
    required super.id,
    required super.nombre,
    required super.edad,
    required this.numExpediente,
  });

  @override
  void mostrarDatos() {
    print('Alumno: $nombre');
    print('Edad: $edad');
    print('Id: $id');
    print('Número de expediente: $numExpediente');
  }
}

class Profesor extends MiembroEscolar {
  String nrp;

  Profesor({
    required super.id,
    required super.nombre,
    required super.edad,
    required this.nrp,
  });

  @override
  void mostrarDatos() {
    print('Profesor: $nombre');
    print('Edad: $edad');
    print('Id: $id');
    print('NRP: $nrp');
  }
}

void main() {
  final alumno = Alumno(
    id: 1,
    nombre: 'Ana López',
    edad: 20,
    numExpediente: 'EXP001',
  );

  final profesor = Profesor(
    id: 2,
    nombre: 'Luis García',
    edad: 45,
    nrp: 'NRP12345',
  );

  alumno.mostrarDatos();

  print('');

  profesor.mostrarDatos();

  print('');

  alumno.edad = 21;
  alumno.mostrarDatos();
}


Solución Ejercicio 2

mixin Registrable {
  void registrarEntrada() {
    print('Entrada registrada en el sistema');
  }
}

mixin Notificable {
  void enviarNotificacion() {
    print('Notificación enviada');
  }
}

class Empleado with Registrable, Notificable {
  String nombre;
  String departamento;

  Empleado({required this.nombre, required this.departamento});
}

class Visitante with Registrable {
  String nombre;
  String empresa;

  Visitante({required this.nombre, required this.empresa});
}

void main() {
  final empleado = Empleado(nombre: 'Laura', departamento: 'Informática');

  final visitante = Visitante(nombre: 'Carlos', empresa: 'Empresa S.A.');

  print('Empleado: ${empleado.nombre}');
  empleado.registrarEntrada();
  empleado.enviarNotificacion();

  print('');

  print('Visitante: ${visitante.nombre}');
  visitante.registrarEntrada();
}
En este ejemplo la clase `Empleado` incorpora las funcionalidades de los dos mixins:
class Empleado with Registrable, Notificable
Por el contrario, `Visitante` solamente incorpora `Registrable`:
class Visitante with Registrable
Por tanto, el siguiente código produciría un error:
visitante.enviarNotificacion();


Solución Ejercicio 3

class Producto {
  int id;
  String nombre;
  double precio;

  Producto({required this.id, required this.nombre, required this.precio});

  factory Producto.fromJson(Map<String, dynamic> dato) {
    
    final id = int.tryParse(dato['id'].toString());
    final precio = double.tryParse(dato['precio'].toString());

    if (id == null) {
      throw FormatException('El identificador no es válido');
    }

    if (precio == null) {
      throw FormatException('El precio no es válido');
    }

    return Producto(id: id, nombre: dato['nombre'] as String, precio: precio);
  }

  @override
  String toString() {
    return 'Producto: $nombre - Id: $id - Precio: $precio €';
  }
}

void main() {
  
  final productoJson = <String, dynamic>{
    'id': '25',
    'nombre': 'Teclado',
    'precio': '29.95',
  };

  final producto = Producto.fromJson(productoJson);

  print(producto);
}
En este ejemplo utilizamos `tryParse()` en lugar de `parse()`:
    final id = int.tryParse(dato['id'].toString());
    final precio = double.tryParse(dato['precio'].toString());
La principal diferencia es que `tryParse()` devuelve `null` cuando el valor no puede convertirse.
Esto nos permite comprobar explícitamente si la conversión ha sido correcta:
if (id == null) {
  throw FormatException('El identificador no es válido');
}
De esta forma podemos controlar el error antes de crear el objeto, en lugar de depender directamente de la excepción producida por `parse()`.




Enlace a la página principal de la UD2

Enlace a la página principal del curso