Flutter DART Clases

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

Introducción

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


  • Como comenté anteriormente, este curso está pensado para alumnos que ya tienen conocimientos previos en algún lenguaje de programación orientado a objetos.
 : En esta sección se van a explicar las diferencias y características de Dart con respecto a otros lenguajes orientados a objetos en lo que se refiere al uso de clases y objetos.
  • En Dart no es necesario utilizar `new` cuando instanciamos una clase.
 : Aunque la palabra clave `new` sigue siendo válida al invocar un constructor se aconseja omitirla.
 : Esto resulta especialmente útil en Flutter, donde se crean objetos y widgets de forma continua y el uso repetido de `new` haría el código más redundante.
Veamos un ejemplo, empleando conceptos habituales de otros lenguajes de programación orientados a objetos, como la redefinición del método `toString`:
class Vehiculo {
int? anoCompra; // Admite null porque inicialmente no le asignamos ningún valor.
String? matricula;
String? marca;

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final veh1 = Vehiculo();
veh1.marca = 'Opel';
veh1.matricula = '1234GHZ';
veh1.anoCompra = 2018;

print(veh1);

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

print(veh2);
}


Constructores

  • Los constructores tienen una finalidad similar a los de Java: permiten inicializar un objeto en el momento de crearlo.
 : Sin embargo, Dart incorpora algunas características propias, como los constructores con nombre, las listas de inicialización, los constructores `factory` y los súper parámetros.
 : Veamos un ejemplo de uso:
class Vehiculo {
int? anoCompra;
String? matricula;
String? marca;

Vehiculo(int anoCompra, String matricula, String marca) {
this.anoCompra = anoCompra;
this.marca = marca;
this.matricula = matricula;
}

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final veh1 = Vehiculo(2018, '1234GHZ', 'Opel');
print(veh1);

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

Vehiculo(this.anoCompra, this.marca, this.matricula);

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final veh1 = Vehiculo(2018, 'Opel', '1234GHZ');
print(veh1);

final veh2 = Vehiculo(2020, 'Peugeot', '6543HZZ');
print(veh2);
}
En `Vehiculo(this.anoCompra, this.marca, this.matricula)`, Dart asigna automáticamente cada parámetro recibido a la propiedad correspondiente del objeto.
  • Si queremos emplear parámetros con nombre debemos utilizar las llaves { }.
 : Si además queremos que sea obligatorio indicar esos parámetros al llamar al constructor, emplearemos la palabra reservada required, como ya vimos anteriormente.
class Vehiculo {
int anoCompra;
String matricula;
String marca;

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

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final veh1 = Vehiculo(
anoCompra: 2018,
marca: 'Opel',
matricula: '1234GHZ',
);
print(veh1);

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

NOTAS IMPORTANTES:

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


Múltiples constructores. Constructores con nombre

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

NombreClase.nombreConstructor(parámetros)

Veamos un ejemplo:
class Vehiculo {
int anoCompra;
String matricula;
String marca;

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

Vehiculo.sinAnho({
required this.marca,
required this.matricula,
}) : anoCompra = 2000;

Vehiculo.sinAnhoMarca({
required this.matricula,
})  : anoCompra = 2000,
marca = 'Opel';

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final veh1 = Vehiculo(
anoCompra: 2018,
marca: 'Opel',
matricula: '1234GHZ',
);
print(veh1);

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

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


Constructores Factory

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

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

factory Vehiculo.sinAnho({
required String marca,
required String matricula,
}) =>
Vehiculo(
anoCompra: 2020,
marca: marca,
matricula: matricula,
);

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

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

print(veh2);
}


Empleando Mapas

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


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

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

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

@override
String toString() =>
'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra';
}

void main() {
final vehiculoJson = <String, dynamic>{
'anoCompra': '2020',
'matricula': '1234GHZ',
'marca': 'Opel',
};

final veh1 = Vehiculo.fromMap(vehiculoJson);

print(veh1);
}
El tipo `Map<String, dynamic>` indica que las claves del mapa son cadenas de caracteres, mientras que sus valores pueden ser de diferentes tipos.
Importante: utilizar `dynamic` no garantiza que los datos sean correctos ni evita errores en tiempo de ejecución.
Por ejemplo:
  • `dato['matricula'] as String` producirá un error si el valor existente no es un `String`.
  • `int.parse(...)` producirá un `FormatException` si el contenido no puede convertirse a un número entero.
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.