Diferencia entre revisiones de «Flutter DART Clases»

De MediaWiki
Ir a la navegación Ir a la búsqueda
Línea 1: Línea 1:
 
==Introducción==
 
==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.
−
* '''Información:''' En VS Code, para que aparezcan los métodos de una clase pulsar las combinaciones de teclas: Control + Barra Espaciadora.
 
−
 
 
−
 
 
  
 
<br />
 
<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 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].
+
* 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, siguiendo las recomendaciones oficiales de ''Effective Dart'' 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`:
  
 +
:<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;
  
 +
@override
 +
String toString() =>
 +
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 +
}
  
−
: Veamos un ejemplo, con conceptos de otros lenguajes de programación orientado a objetos como el empleo del método toString:
 
−
 
−
:<syntaxhighlight lang="java" enclose="div" highlight="1-9" >
 
−
class Vehiculo{
 
−
 
 
−
  int? anoCompra; // Necesitamos indicar que admite valores nulos, ya que al instanciar no le damos un valor por defecto
 
−
  String? matricula;
 
−
  String? marca;
 
−
 
 
−
  @override
 
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
−
}
 
 
void main() {
 
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 57: 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;
 +
}
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="7-11" >
+
@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;
 
−
 
 
−
  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() {
 
void main() {
 +
final veh1 = Vehiculo(2018, '1234GHZ', 'Opel');
 +
print(veh1);
  
−
  final veh1 = new Vehiculo(2018,'Opel','1234GHZ'); 
+
final veh2 = Vehiculo(2020, '6543HZZ', 'Peugeot');
−
  print(veh1);
+
print(veh2);
−
 
+
} </syntaxhighlight>
−
  final veh2 = Vehiculo(2020,'Peugot','6543HZZ');  
+
 
−
  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.
−
}
 
−
</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,...)
+
:'''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.
−
:'''Ejercicio propuesto:''' Modifica el ejercicio anterior y haz con los parámetros puedan ser enviados en cualquier orden especificando el nombre del parámetro en la llamada. Haz que todos ellos sean obligatorios.
 
  
 +
* En el ejemplo anterior 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:
  
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="7">
 +
class Vehiculo {
 +
int anoCompra;
 +
String matricula;
 +
String marca;
  
 +
Vehiculo(this.anoCompra, this.marca, this.matricula);
  
−
* 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.
+
@override
−
: Para solucionarlo.
+
String toString() =>
−
:<syntaxhighlight lang="java" enclose="div" highlight="7" >
+
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
−
class Vehiculo{
 
−
 
 
−
  int anoCompra;
 
−
  String matricula;
 
−
  String marca;
 
−
 
 
−
  Vehiculo(this.anoCompra,this.marca, this.matricula); // Esto hace que automaticamente asigne los valores enviados a las propiedades y obliga a instanciar con los 3 parámetros.
 
−
 
 
−
  @override
 
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
 
−
 
 
 
}
 
}
 +
 
void main() {
 
void main() {
 +
final veh1 = Vehiculo(2018, 'Opel', '1234GHZ');
 +
print(veh1);
  
−
  final veh1 = new Vehiculo(2018,'Opel','1234GHZ'); 
+
final veh2 = Vehiculo(2020, 'Peugeot', '6543HZZ');
−
  print(veh1);
+
print(veh2);
−
 
+
} </syntaxhighlight>
−
  final veh2 = Vehiculo(2020,'Peugot','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.
−
 
 
−
 
 
−
}
 
−
</syntaxhighlight>  
 
  
 +
* 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''', [https://wiki.cifprodolfoucha.es/index.php?title=Flutter_DART_Funciones#Par.C3.A1metros_obligatorios.2Fopcionales como ya vimos anteriormente].
  
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="7-11,19,22">
 +
class Vehiculo {
 +
int anoCompra;
 +
String matricula;
 +
String marca;
  
−
* 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].
+
Vehiculo({
−
:<syntaxhighlight lang="java" enclose="div" highlight="7-11,19,22" >
+
required this.anoCompra,
−
class Vehiculo{
+
required this.marca,
−
 
+
required this.matricula,
−
  int anoCompra;
+
});
−
  String matricula;
 
−
  String marca;
 
−
 
 
−
  Vehiculo({
 
−
      required this.anoCompra,
 
−
      required this.marca,  
 
−
      required this.matricula
 
−
    });  
 
−
 
 
−
  @override
 
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
 
−
 
 
−
}
 
−
void main() {
 
  
−
  final veh1 = new Vehiculo(anoCompra: 2018 ,marca: 'Opel',matricula: '1234GHZ'); 
+
@override
−
  print(veh1);
+
String toString() =>
−
 
+
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
−
  final veh2 = Vehiculo(anoCompra: 2020,marca: 'Peugot',matricula: '6543HZZ');  
 
−
  print(veh2);
 
−
 
 
−
 
 
 
}
 
}
−
</syntaxhighlight>
 
  
 +
void main() {
 +
final veh1 = Vehiculo(
 +
anoCompra: 2018,
 +
marca: 'Opel',
 +
matricula: '1234GHZ',
 +
);
 +
print(veh1);
  
 +
final veh2 = Vehiculo(
 +
anoCompra: 2020,
 +
marca: 'Peugeot',
 +
matricula: '6543HZZ',
 +
);
 +
print(veh2);
 +
} </syntaxhighlight>
  
 
'''<u>NOTAS IMPORTANTES:</u>'''
 
'''<u>NOTAS IMPORTANTES:</u>'''
−
:* En DART los constructores no se heredan.
 
−
:* Para llamar a un constructor que esté definido en una clase superior, debemos emplear la palabra clave '''super'''. Ejemplos de uso en [https://medium.com/@flutterman/dart-2-implementaci%C3%B3n-de-clases-a53360efa007 este enlace].
 
  
 +
:* 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`.
  
 
<br />
 
<br />
  
−
===Mútliples constructores. Constructores con nombre===
+
===Múltiples constructores. Constructores con nombre===
−
* En DART no existe la sobrecarga de constructores y por tanto no podemos definir múltiples constructores en base a diferente tipo o número de parámetros.
+
 
−
: Para solucionarlo, DART permite crear múltiples constructores con diferentes nombres de la forma: '''nombre_clase.nombre_constructor(parámetros)'''.
+
* 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:
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="9,11,22,25" >
+
NombreClase.nombreConstructor(parámetros)
−
class Vehiculo{
 
−
 
 
−
  int anoCompra;
 
−
  String matricula;
 
−
  String marca;
 
−
 
 
−
  Vehiculo({required this.anoCompra,required this.marca, required this.matricula});
 
−
 
 
−
  Vehiculo.sinAnho({required this.marca, required this.matricula}) : anoCompra = 2000;
 
  
−
  Vehiculo.sinAnhoMarca({required this.matricula}) : anoCompra = 2000, marca='Opel';
+
: Veamos un ejemplo:
−
 
+
 
−
  @override
+
:<syntaxhighlight lang="dart" enclose="div" highlight="9,11,22,25">
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
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() {
 
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);
 +
} </syntaxhighlight>
  
−
  final veh1 = new Vehiculo(anoCompra: 2018,marca: 'Opel',matricula: '1234GHZ'); 
+
* '''<u>Nota importante:</u>''' En los constructores anteriores no recibimos todos los valores necesarios para inicializar las propiedades del objeto.
−
  print(veh1);
+
   : 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'''.
−
   final veh2 = new Vehiculo.sinAnho(marca: 'Opel',matricula: '2234GHZ'); 
+
 
−
  print(veh2);
+
: La lista de inicialización aparece después de los parámetros del constructor, precedida por dos puntos (`:`):
−
 
+
 
−
  final veh3 = new Vehiculo.sinAnhoMarca(matricula: '3234GHZ'); 
+
:<syntaxhighlight lang="dart" enclose="div">
−
  print(veh3);
+
Vehiculo.sinAnhoMarca({
−
 
+
required this.matricula,
−
}
+
})  : anoCompra = 2000,
−
</syntaxhighlight>  
+
marca = 'Opel'; </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.
+
: Las expresiones de la lista de inicialización se ejecutan '''antes del cuerpo del constructor'''.
−
: 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
 
  
 +
: Más información: [https://dart.dev/language/constructors Constructores en Dart].
  
 
<br />
 
<br />
Línea 206: Línea 228:
 
===Constructores Factory===
 
===Constructores Factory===
  
−
* Más información en https://dart.dev/guides/language/language-tour#factory-constructors
+
* Más información: [https://dart.dev/language/constructors#factory-constructors Factory constructors].
  
−
* Normalmente este tipo de constructores se suelen emplear cuando no queramos instanciar un objeto de la clase, sino que recuperamos una instancia previamente guardada en alguna lista (por ejemplo) en base al parámetro que recibe el constructor (podéis consultar el ejemplo del enlace anterior).
+
* Los constructores `factory` se utilizan cuando la creación de un objeto necesita un comportamiento diferente al de un constructor generativo convencional.
−
: Siempre deben devolver una instancia de la clase (debemos de hacer el return).
+
  : Por ejemplo, pueden utilizarse cuando:
−
: Se crean anteponiendo la palabra reservada '''factory''' antes del nombre del constructor.
+
 
 +
:* 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.
  
−
: Otro uso que se les suele dar es cuando queremos llamar a otro constructor dentro del código de un constructor.
 
−
: Suele usarse para leer archivos JSON, en los que creamos un constructor de nombre 'NombreClase.fromJson' al que se le pasa un Mapa con los datos de la clase en el archivo Json, y se devuelve un objeto de la clase con los datos de dicho archivo.
 
−
 
 
 
: Veamos un ejemplo:
 
: Veamos un ejemplo:
−
:<syntaxhighlight lang="java" enclose="div" highlight="9-10" >
 
−
class Vehiculo{
 
−
 
 
−
  int anoCompra;
 
−
  String matricula;
 
−
  String marca;
 
−
 
 
−
  Vehiculo({required this.anoCompra,required this.marca, required this.matricula});
 
−
 
 
−
  factory Vehiculo.sinAnho({required marca, required matricula}) =>
 
−
    Vehiculo(anoCompra: 2020,marca: marca, matricula: matricula );
 
  
−
  @override
+
:<syntaxhighlight lang="dart" enclose="div" highlight="9-10">
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
+
class Vehiculo {
−
 
+
int anoCompra;
−
}
+
String matricula;
−
void main() {
+
String marca;
  
−
  final veh2 = new Vehiculo.sinAnho(marca: 'Opel',matricula: '2234GHZ'); 
+
Vehiculo({
−
  print(veh2);
+
required this.anoCompra,
−
 
+
required this.marca,
−
 
+
required this.matricula,
−
}
+
});
  
−
</syntaxhighlight>  
+
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);
 +
} </syntaxhighlight>
  
 
<br />
 
<br />
Línea 249: Línea 286:
 
==Empleando Mapas==
 
==Empleando Mapas==
  
−
* En esta sección voy a explicar como, teniendo como fuente de datos un mapa, crear un objeto.
+
* En esta sección vamos a ver cómo crear un objeto utilizando como fuente de datos un mapa.
−
:* 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 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].
−
:* 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.
+
  :* 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 va a guardar la información de un Coche.  
+
: Partimos de un mapa que almacena la información de un vehículo.
−
: Normalmente los datos que tengan como fuente de datos Internet vendrán en formato JSON:
+
: Es habitual recibir datos procedentes de servicios web en formato JSON y, una vez decodificados, trabajar con estructuras del tipo `Map<String, dynamic>`.
  
 
<center>
 
<center>
Línea 260: Línea 297:
 
</center>
 
</center>
  
 +
<br />
  
−
<br />
+
:<syntaxhighlight lang="dart" enclose="div">
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
class Vehiculo {
−
class Vehiculo{
+
int anoCompra;
−
 
+
String matricula;
−
  int anoCompra;  
+
String marca;
−
  String matricula;
+
 
−
  String marca;
+
Vehiculo({
−
 
+
required this.anoCompra,
−
  Vehiculo({
+
required this.marca,
−
      required this.anoCompra,
+
required this.matricula,
−
      required this.marca,  
+
});
−
      required this.matricula
+
 
−
    });  
+
Vehiculo.fromMap(Map<String, dynamic> dato)
−
+
: anoCompra = int.parse(dato['anoCompra'].toString()),
−
  Vehiculo.fromMap(Map<String,String>dato) :
+
matricula = dato['matricula'] as String,
−
    this.anoCompra = int.parse(dato['anoCompra']!),
+
marca = dato['marca'] as String;
−
    this.matricula = dato['matricula']!,
 
−
    this.marca = dato['marca']!;
 
−
 
 
−
  @override
 
−
  String toString() => 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';  
 
−
 
 
−
 
−
}
 
−
void main() {
 
  
−
  final vehiculoJson = {
+
@override
−
    'anoCompra' : '2020',
+
String toString() =>
−
    'matricula' : '1234GHZ',
+
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
−
    'marca' : 'Opel'
 
−
  };
 
−
 
 
−
  Vehiculo veh1 = Vehiculo.fromMap(vehiculoJson);
 
−
  print(veh1);
 
−
 
 
 
}
 
}
  
 +
void main() {
 +
final vehiculoJson = <String, dynamic>{
 +
'anoCompra': '2020',
 +
'matricula': '1234GHZ',
 +
'marca': 'Opel',
 +
};
  
−
</syntaxhighlight>
+
final veh1 = Vehiculo.fromMap(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);
−
: <u>Otra forma de evitarlo es emplear un mapa de tipo Map<String,'''dynamic'''>dato</u>
+
} </syntaxhighlight>
  
 +
: El tipo `Map<String, dynamic>` indica que las claves del mapa son cadenas de caracteres, mientras que sus valores pueden ser de diferentes tipos.
  
−
* Podéis emplear [https://quicktype.io/ este sitio Web] para que os genere el código automáticamente a partir de un Json dado.
+
: '''<u>Importante:</u>''' 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.
  
 +
: Por tanto, al trabajar con información externa conviene comprobar y validar los valores recibidos antes de utilizarlos.
  
−
* Cabe indicar que podríamos emplear un constructor Factory como vimos en el punto anterior:
+
* Podéis emplear [https://quicktype.io/ Quicktype] para generar automáticamente modelos a partir de una estructura JSON.
−
:<syntaxhighlight lang="java" enclose="div" highlight="13-18" >
 
−
class Vehiculo{
 
  
−
  int anoCompra;
+
* También podríamos utilizar un constructor `factory`, como vimos en el apartado anterior:
−
  String matricula;
 
−
  String marca;
 
  
−
  Vehiculo({
+
:<syntaxhighlight lang="dart" enclose="div" highlight="13-18">
−
    required this.anoCompra,
+
class Vehiculo {
−
    required this.marca,
+
int anoCompra;
−
    required this.matricula
+
String matricula;
−
  });
+
String marca;
  
−
  factory Vehiculo.fromJson(Map<String,String>dato) => Vehiculo(anoCompra: int.parse(dato['anoCompra']!),
+
Vehiculo({
−
                    matricula:dato['matricula']!,
+
required this.anoCompra,
−
                    marca:dato['marca']!
+
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';
 
  
 +
factory Vehiculo.fromJson(Map<String, dynamic> dato) => Vehiculo(
 +
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() {
 
void main() {
−
  final vehiculoJson = {
+
final vehiculoJson = <String, dynamic>{
−
    'anoCompra' : '2020',
+
'anoCompra': '2020',
−
    'matricula' : '1234GHZ',
+
'matricula': '1234GHZ',
−
    'marca' : 'Opel'
+
'marca': 'Opel',
−
  };
+
};
−
 
 
−
  Vehiculo veh1 = Vehiculo.fromJson(vehiculoJson);
 
−
  print(veh1);
 
−
}
 
−
</syntaxhighlight>
 
  
 +
final veh1 = Vehiculo.fromJson(vehiculoJson);
  
 +
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" >
+
* 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(...)`.
 +
 
 +
:<syntaxhighlight lang="dart" enclose="div" highlight="7,16">
 
class Personal {
 
class Personal {
−
  String nombreCompleto;
+
String nombreCompleto;
−
 
 
−
  Personal(required this.nombreCompleto);
 
  
 +
Personal({required this.nombreCompleto});
 
}
 
}
−
class Alumno extends Personal{
 
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
class Alumno extends Personal {
−
  // Si estamos en Dart > 2.17 podemos emplear otra forma, siempre que el constructor de la clase padre tenga parámetros nombrados (los que van entre llaves)
+
String numCarnet;
−
  //  Alumno({required this.numCarnet, required super.nombreCompleto});
 
  
 +
Alumno({
 +
required this.numCarnet,
 +
required 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({
−
  // Si estamos en Dart > 2.17 podemos emplear otra forma, siempre que el constructor de la clase padre tenga parámetros nombrados (los que van entre llaves)
+
required this.nrp,
−
  //  Profesor({required this.nrp, required super.nombreCompleto});
+
required 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',
 +
);
  
−
  print(alum1);
+
final prof1 = Profesor(
−
  print(prof1);
+
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
−
}
+
print(alum1);
−
</syntaxhighlight>  
+
print(prof1);
 +
} </syntaxhighlight>
  
 +
<br />
  
−
<br />
+
===Overriding - Sobrescritura===
−
===Overriding-Sobreescritura===
 
−
* Tiene el mismo concepto que en otros lenguajes de programación orientado a objetos. Es un tipo de polimorfismo entre clases que tienen una relación de herancia.
 
−
: De hecho ya lo estamos haciendo de cierta manera con el método toString.
 
  
 +
* 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.
  
−
* <u>Nota:</u>No confundir con sobrecarga (overloading) en POO que se da cuando definimos el mismo nombre de método con diferente número y/o tipo de parámetros. Esto en Dart no se puede hacer.
+
: 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 defino el método imprimirDatos en la superclase y es heradado por la subclases.
+
: Veamos un ejemplo en el que definimos el método `imprimirDatos` en la superclase.
−
: Una de ellas (Alumno) lo sobreescribe y llama al método que está definido en la clase padre, Personal.  
+
: 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="java" enclose="div" highlight="6-8,16-20,42,43" >
+
:<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 = Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez'); // En DART no es necesario emplear la palabra new para crear una instancia. LLega con poner el nombre de la clase.
+
void main() {
−
  final prof1 = 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 462: Línea 527:
 
==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 realizarlo en el Android Studio (no vale vía Web):
+
* 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';
+
Profesor({
 +
required this.nrp,
 +
required String nombreCompleto,
 +
}) : super(nombreCompleto);
  
−
void main(){
+
@override
 +
String toString() =>
 +
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
 +
} </syntaxhighlight>
  
−
  final alum1 = new Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
: Ahora queremos utilizar estas clases desde otro archivo Dart.
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
: Para ello empleamos `import`:
  
−
  print(alum1);
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1">
−
  print(prof1);
+
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,5">
 
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,5">
 
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`:
  
−
* Cuando tenemos muchas clases en un paquete podemos indicar sólo las clases que vamos a utilizar con la palabra show de la forma:
+
:<syntaxhighlight lang="dart" enclose="div">
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
import 'package02/centro/centro.dart' show Alumno, Profesor; </syntaxhighlight>
−
import 'package:dart_application_2/centro/centro.dart' show Alumno,Profesor;
 
−
</syntaxhighlight>  
 
  
−
* También podemos hacer lo contrario, y exponer todas las clases del paquete menos alguna con la palabra hide de la forma:
+
: `show` no se limita a clases. Puede utilizarse con cualquier nombre exportado por la biblioteca, como funciones, variables, enumeraciones, etc.
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
 
−
import 'package:dart_application_2/centro/centro.dart' hide Personal;
 
−
</syntaxhighlight>
 
  
 +
* 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 606: Línea 695:
 
==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',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
−
:<syntaxhighlight lang="java" enclose="div" highlight="" >
+
print(prof1);
  
 +
print('Sueldo: ${prof1.sueldo}');
  
−
void main() {
+
prof1.sueldo = 100.34; // Llama al setter.
−
 
+
print('Nuevo sueldo: ${prof1.sueldo}');
−
  final prof1 = new Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
 
  
−
  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:
−
void main() {
 
  
−
  final alum1 = Alumno(numCarnet: 'C1A',nombreCompleto: 'Juan Luis Rodriguez');
+
:<syntaxhighlight lang="dart" enclose="div">
−
  final prof1 = Profesor(nrp: '1234534',nombreCompleto: 'Luisa Sanchez Dragó');
+
interface class Ejemplo {
 +
} </syntaxhighlight>
  
−
  print(alum1);
+
: El modificador `interface` expresa que la clase puede utilizarse como interfaz desde otras bibliotecas.
−
  alum1.pagarCafeteria();
 
−
  print(prof1);
 
−
  prof1.pagarCafeteria();
 
−
 
 
−
  // Personal per1 = Personal('Nombre Completo a enviar'); // ERROR. No se puede instanciar una clase abstracta
 
−
 
 
−
}
 
  
−
abstract class Personal {
+
: Cuando queremos definir una clase destinada únicamente a expresar un contrato y que, además, no pueda ser instanciada directamente, podemos combinar ambos modificadores:
−
  String nombreCompleto;
 
  
−
  Personal(this.nombreCompleto);
+
:<syntaxhighlight lang="dart" enclose="div">
−
 
+
abstract interface class Ejemplo {
−
  void pagarCafeteria();
+
} </syntaxhighlight>
  
−
}
+
===Clases abstractas===
−
class Alumno extends Personal{
 
−
  String numCarnet;
 
  
−
  Alumno({required this.numCarnet, required String nombreCompleto}) : super(nombreCompleto);
+
: Veamos primero un ejemplo utilizando una clase abstracta como superclase:
  
−
  void pagarCafeteria(){
+
:<syntaxhighlight lang="dart" enclose="div" highlight="7,9,11,15,20,28-30,41-43">
−
    print('El alumno $numCarnet paga en efectivo');
+
void main() {
−
  }
+
final alum1 = Alumno(
−
 
+
numCarnet: 'C1A',
−
  @override
+
nombreCompleto: 'Juan Luis Rodriguez',
−
  String toString() => 'Alumna/o:$nombreCompleto con número de carnet:$numCarnet';
+
);
−
}
 
  
−
class Profesor extends Personal{
+
final prof1 = Profesor(
−
  String nrp;
+
nrp: '1234534',
 +
nombreCompleto: 'Luisa Sanchez Dragó',
 +
);
  
−
  Profesor({required this.nrp, required String nombreCompleto}) : super(nombreCompleto);
+
print(alum1);
 +
alum1.pagarCafeteria();
  
−
  void pagarCafeteria(){
+
print(prof1);
−
    print('El profesor $nrp paga con vale descuento');
+
prof1.pagarCafeteria();
−
  }
 
  
−
  @override
+
// final per1 = Personal('Nombre');
−
  String toString() => 'Profesor/a:$nombreCompleto con número de registro personal:$nrp';
+
// ERROR: no se pueden instanciar directamente las clases abstractas.
 
}
 
}
−
</syntaxhighlight>
 
  
−
[[Imagen:Flutter_dart_abstract_1.JPG|500px|center]]
+
abstract class Personal {
 +
String nombreCompleto;
 +
 
 +
Personal(this.nombreCompleto);
  
 +
void pagarCafeteria();
 +
}
  
−
<br />
+
class Alumno extends Personal {
−
* Veamos ahora un caso de uso de interface.
+
String numCarnet;
−
:<syntaxhighlight lang="java" enclose="div" highlight="11-17,19,29,25-26,35-36" >
 
  
−
void main() {
+
Alumno({
−
 
+
required this.numCarnet,
−
  Cuadrado cuadrado1 = Cuadrado(lado: 10);
+
required String nombreCompleto,
−
  Circulo circulo1 = Circulo(5);
+
}) : super(nombreCompleto);
  
−
  print('Área del cuadrado: ${cuadrado1.getArea()}');
+
@override
−
  print('Área del círculo: ${circulo1.getArea()}');
+
void pagarCafeteria() {
−
 
+
print('El alumno $numCarnet paga en efectivo');
 
}
 
}
  
−
abstract class Figura{
+
@override
−
 
+
String toString() =>
−
  static const pi = 3.1416;
+
'Alumna/o: $nombreCompleto con número de carnet: $numCarnet';
−
 
 
−
  double getArea();
 
−
 
 
 
}
 
}
  
−
class Cuadrado implements Figura{
+
class Profesor extends Personal {
−
 
+
String nrp;
−
  final double _lado;
 
−
 
 
−
  Cuadrado({required double lado}) : _lado = lado;
 
  
−
  @override
+
Profesor({
−
  double getArea() => _lado*_lado;
+
required this.nrp,
−
 
+
required String nombreCompleto,
 +
}) : super(nombreCompleto);
 +
 
 +
@override
 +
void pagarCafeteria() {
 +
print('El profesor $nrp paga con vale descuento');
 
}
 
}
−
class Circulo implements Figura{
 
−
 
 
−
  final double _radio;
 
−
 
 
−
  Circulo(this._radio);
 
  
−
  @override
+
@override
−
  double getArea() => Figura.pi*_radio*_radio;
+
String toString() =>
−
 
+
'Profesor/a: $nombreCompleto con número de registro personal: $nrp';
−
}
+
} </syntaxhighlight>
 +
 
 +
[[Imagen:Flutter_dart_abstract_1.JPG|500px|center]]
  
−
</syntaxhighlight>
+
<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`.
  
−
* 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.
+
: Cuando una clase implementa una interfaz debe proporcionar una implementación para todos sus miembros de instancia requeridos.
  
−
: Veamos una modificación al ejercicio anterior:
+
:<syntaxhighlight lang="dart" enclose="div" highlight="11-17,19,25-26,35-36">
−
:<syntaxhighlight lang="java" enclose="div" highlight="6,7,11-13" >
 
 
void main() {
 
void main() {
−
 
+
final cuadrado1 = Cuadrado(lado: 10);
−
  Cuadrado cuadrado1 = Cuadrado(lado: 10);
+
final circulo1 = Circulo(5);
−
  Circulo circulo1 = Circulo(5);
 
  
−
  imprimirArea(cuadrado1);
+
print('Área del cuadrado: ${cuadrado1.getArea()}');
−
  imprimirArea(circulo1);
+
print('Área del círculo: ${circulo1.getArea()}');
−
 
 
 
}
 
}
  
−
void imprimirArea(Figura figura){
+
abstract interface class Figura {
−
  print('El área del ${figura.runtimeType} es ${figura.getArea()}');
+
static const pi = 3.1416;
 +
 
 +
double getArea();
 
}
 
}
−
</syntaxhighlight>
 
  
 +
class Cuadrado implements Figura {
 +
final double _lado;
  
 +
Cuadrado({
 +
required double lado,
 +
}) : _lado = lado;
  
−
<br />
+
@override
−
 
+
double getArea() => _lado * _lado;
−
==Mixins==
+
}
  
−
* Más información:
+
class Circulo implements Figura {
−
:* https://dart.dev/guides/language/language-tour#adding-features-to-a-class-mixins
+
final double _radio;
−
:* https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3
 
  
 +
Circulo(this._radio);
  
−
* Los mixins es la forma que tiene DART de reutilizar código entre diferentes clases.
+
@override
−
: Viene a ser algo parecido a extends - implements pero con la siguiente diferencia:
+
double getArea() => Figura.pi * _radio * _radio;
−
:* '''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.
+
} </syntaxhighlight>
−
:* '''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.
 
  
 +
: Fijarse en la diferencia entre los ejemplos anteriores:
  
−
* 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>.
+
:* `Alumno extends Personal` indica una relación de '''herencia'''.
 +
:* `Cuadrado implements Figura` indica que `Cuadrado` se compromete a cumplir el '''contrato''' definido por `Figura`.
  
−
: Veamos un ejemplo:
+
===Polimorfismo===
−
:<syntaxhighlight lang="java" enclose="div" highlight="1-6,8-12,15,22" >
 
−
mixin OperacionesListas{
 
−
 
 
−
  void imprimirLista(List<int> lista){
 
−
    lista.forEach((elem) => print('Elmento de la lista:$elem'));
 
−
  }
 
−
}
 
  
−
mixin OperacionesMapas{
+
* Podemos utilizar una referencia cuyo tipo sea una clase abstracta o una interfaz para trabajar con objetos de diferentes clases que cumplan ese contrato.
−
  void imprimirMapa(Map<String,String> mapa){
 
−
    mapa.forEach((clave,valor) => print('La $clave tiene el valor:$valor'));
 
−
  }
 
−
}
 
  
 +
: Por ejemplo:
  
−
class ClaseListas with OperacionesListas{
+
:<syntaxhighlight lang="dart" enclose="div" highlight="6,7,11-13">
−
 
 
−
  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() {
 
void main() {
−
 
+
final cuadrado1 = Cuadrado(lado: 10);
−
  final claseListas = new ClaseListas([1,2,3,4]);
+
final circulo1 = Circulo(5);
−
  final claseListasMapas = new ClaseListasYMapas([10,11,12,13],{'nombre' : 'Jose', 'edad' : '45'});
 
−
 
 
−
  claseListas.imprimirLista(claseListas.lista);
 
−
  claseListasMapas.imprimirLista(claseListasMapas.lista);
 
−
  claseListasMapas.imprimirMapa(claseListasMapas.mapa);
 
  
 +
imprimirArea(cuadrado1);
 +
imprimirArea(circulo1);
 
}
 
}
  
−
</syntaxhighlight>
+
void imprimirArea(Figura figura) {
 +
print(
 +
'El área del ${figura.runtimeType} es ${figura.getArea()}',
 +
);
 +
} </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.
  
−
* Como ejercicio podéis implementar [https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3 el ejemplo que aparece en el segundo enlace de esta sección].
+
<br />
  
 +
==Mixins==
  
−
<br />
+
* Más información:
−
==Ejercicios==
+
  :* [https://dart.dev/language/mixins Mixins]
 +
  :* [https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3 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.
  
−
<br >
+
: Los mixins se incorporan a una clase mediante la palabra reservada '''with'''.
−
===Ejercicio 1===
 
  
−
* Crea un conjunto de clases para representar a diferentes miembros de una escuela, tales como profesores y estudiantes. Utilizarás herencia para compartir comportamientos y propiedades comunes, mientras que las interfaces ayudarán a definir los contratos para las funcionalidades básicas de los personajes del sistema.
+
:<syntaxhighlight lang="dart" enclose="div" highlight="1-6,8-12,15,22">
 +
mixin OperacionesListas {
 +
void imprimirLista(List<int> lista) {
 +
for (final elem in lista) {
 +
print('Elemento de la lista: $elem');
 +
}
 +
}
 +
}
  
−
* Requisitos del Sistema:
+
mixin OperacionesMapas {
 +
void imprimirMapa(Map<String, String> mapa) {
 +
for (final entry in mapa.entries) {
 +
print(
 +
'La clave ${entry.key} tiene el valor: ${entry.value}',
 +
);
 +
}
 +
}
 +
}
  
−
:* Interfaz Persona:
+
class ClaseListas with OperacionesListas {
−
::        Define una interfaz Persona con propiedades para nombre y edad, incluyendo tanto getters como setters para cada una.
+
List<int> lista;
  
−
:*    Clase Abstracta MiembroEscolar:
+
ClaseListas(this.lista);
−
::        Crea una clase abstracta MiembroEscolar que implemente Persona y añade una propiedad id para identificar unívocamente al miembro.
+
}
  
−
:*    Clase Profesor:
+
class ClaseListasYMapas with OperacionesListas, OperacionesMapas {
−
::        Extiende MiembroEscolar para implementar la clase Profesor.
+
List<int> lista;
−
::        Añade una propiedad asignatura para representar la materia que enseña.
+
Map<String, String> mapa;
−
::        Incluye un constructor principal y uno nombrado que permita crear un profesor sin asignar asignatura.
 
  
−
:*    Clase Estudiante:
+
ClaseListasYMapas(this.lista, this.mapa);
−
::        Extiende MiembroEscolar para implementar la clase Estudiante.
+
}
−
::        Añade una propiedad año para indicar el año escolar del estudiante.
 
−
::        Implementa un constructor para inicializar todos los campos.
 
  
−
* Tareas:
+
void main() {
 +
final claseListas = ClaseListas(
 +
[1, 2, 3, 4],
 +
);
  
−
:*    Define las clases y las interfaces según los requisitos.
+
final claseListasMapas = ClaseListasYMapas(
−
:*    Implementa getters y setters apropiados para gestionar el estado de las instancias de manera controlada.
+
[10, 11, 12, 13],
−
:*    Crea diferentes tipos de constructores para proporcionar flexibilidad en la creación de instancias de tus clases.
+
{
−
:*    Asegúrate de probar las funcionalidades de tus clases creando instancias y manipulando sus propiedades.
+
'nombre': 'Jose',
 +
'edad': '45',
 +
},
 +
);
  
 +
claseListas.imprimirLista(
 +
claseListas.lista,
 +
);
  
 +
claseListasMapas.imprimirLista(
 +
claseListasMapas.lista,
 +
);
  
 +
claseListasMapas.imprimirMapa(
 +
claseListasMapas.mapa,
 +
);
 +
} </syntaxhighlight>
  
 +
<br />
  
 +
==Ejercicios==
  
 
<br />
 
<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]
+
===Ejercicio 1===
−
 
 
−
[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]
 
  
 +
* 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 personajes 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.
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2021).
+
:* '''Clase abstracta MiembroEscolar:'''
 +
:: Crea una clase abstracta `MiembroEscolar` que implemente `Persona` y añade una propiedad `id` para identificar unívocamente al miembro.

Revisión del 18:19 23 sep 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, siguiendo las recomendaciones oficiales de Effective Dart 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.
Por tanto, al trabajar con información externa conviene comprobar y validar los valores recibidos antes de utilizarlos.
  • 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.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.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 personajes 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` y añade una propiedad `id` para identificar unívocamente al miembro.