Diferencia entre revisiones de «Flutter DART Clases»
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 | ||
| − | |||
| − | |||
<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 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 | + | * En Dart no es necesario utilizar `new` cuando instanciamos una clase. |
| − | : Esto | + | : 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`: | ||
| + | :<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'; | ||
| + | } | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
void main() { | 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); | ||
| + | } </syntaxhighlight> | ||
<br /> | <br /> | ||
| Línea 57: | Línea 47: | ||
==Constructores== | ==Constructores== | ||
| − | * | + | * Los constructores tienen una finalidad similar a los de Java: permiten inicializar un objeto en el momento de crearlo. |
| − | : Veamos un ejemplo de uso: | + | : Sin embargo, Dart incorpora algunas características propias, como los constructores con nombre, las listas de inicialización, los constructores `factory` y los súper parámetros. |
| + | : Veamos un ejemplo de uso: | ||
| + | |||
| + | :<syntaxhighlight lang="dart" enclose="div" highlight="7-11"> | ||
| + | class Vehiculo { | ||
| + | int? anoCompra; | ||
| + | String? matricula; | ||
| + | String? marca; | ||
| + | Vehiculo(int anoCompra, String matricula, String marca) { | ||
| + | this.anoCompra = anoCompra; | ||
| + | this.marca = marca; | ||
| + | this.matricula = matricula; | ||
| + | } | ||
| − | + | @override | |
| − | + | String toString() => | |
| − | + | 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| + | |||
void main() { | void main() { | ||
| + | final veh1 = Vehiculo(2018, '1234GHZ', 'Opel'); | ||
| + | print(veh1); | ||
| − | + | final veh2 = Vehiculo(2020, '6543HZZ', 'Peugeot'); | |
| − | + | print(veh2); | |
| − | + | } </syntaxhighlight> | |
| − | + | ||
| − | + | : Un constructor es una función especial asociada a la creación e inicialización de objetos. No debe confundirse con un método ordinario. | |
| − | + | : Los constructores pueden recibir parámetros posicionales, opcionales o con nombre, al igual que otras funciones de Dart. | |
| − | } | ||
| − | </syntaxhighlight> | ||
| − | + | :'''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 | ||
| + | * 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); | ||
| − | + | @override | |
| − | + | String toString() => | |
| − | + | 'El vehículo de la marca $marca con matrícula $matricula se compró en el año $anoCompra'; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| + | |||
void main() { | void main() { | ||
| + | final veh1 = Vehiculo(2018, 'Opel', '1234GHZ'); | ||
| + | print(veh1); | ||
| − | + | final veh2 = Vehiculo(2020, 'Peugeot', '6543HZZ'); | |
| − | + | print(veh2); | |
| − | + | } </syntaxhighlight> | |
| − | + | ||
| − | + | : En `Vehiculo(this.anoCompra, this.marca, this.matricula)`, Dart asigna automáticamente cada parámetro recibido a la propiedad correspondiente del objeto. | |
| − | |||
| − | |||
| − | } | ||
| − | </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; | ||
| − | + | 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); | ||
| + | } </syntaxhighlight> | ||
'''<u>NOTAS IMPORTANTES:</u>''' | '''<u>NOTAS IMPORTANTES:</u>''' | ||
| − | |||
| − | |||
| + | :* 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últiples constructores. Constructores con nombre=== |
| − | * | + | |
| − | : Para | + | * Dart no permite la sobrecarga tradicional de métodos o constructores, es decir, no podemos declarar varias veces un método o constructor con el mismo nombre y diferenciarlos únicamente por el número o tipo de sus parámetros. |
| + | : Para proporcionar diferentes formas de crear un objeto, Dart permite utilizar '''constructores con nombre'''. | ||
| + | : Su forma general es: | ||
| − | + | NombreClase.nombreConstructor(parámetros) | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | : Veamos un ejemplo: | |
| − | + | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="9,11,22,25"> | |
| − | + | 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> | ||
| − | + | * '''<u>Nota importante:</u>''' En los constructores anteriores no recibimos todos los valores necesarios para inicializar las propiedades del objeto. | |
| − | + | : Como las propiedades `anoCompra` y `marca` no admiten valores nulos, debemos inicializarlas antes de que finalice la construcción del objeto. | |
| − | + | : Para hacerlo hemos empleado una característica de Dart denominada '''lista de inicialización'''. | |
| − | + | ||
| − | + | : La lista de inicialización aparece después de los parámetros del constructor, precedida por dos puntos (`:`): | |
| − | + | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | + | Vehiculo.sinAnhoMarca({ | |
| − | + | required this.matricula, | |
| − | + | }) : anoCompra = 2000, | |
| − | </syntaxhighlight> | + | marca = 'Opel'; </syntaxhighlight> |
| − | + | : Las expresiones de la lista de inicialización se ejecutan '''antes del cuerpo del constructor'''. | |
| − | : | ||
| − | |||
| + | : Más información: [https://dart.dev/language/constructors Constructores en Dart]. | ||
<br /> | <br /> | ||
| Línea 206: | Línea 228: | ||
===Constructores Factory=== | ===Constructores Factory=== | ||
| − | * Más información | + | * Más información: [https://dart.dev/language/constructors#factory-constructors Factory constructors]. |
| − | * | + | * Los constructores `factory` se utilizan cuando la creación de un objeto necesita un comportamiento diferente al de un constructor generativo convencional. |
| − | : | + | : Por ejemplo, pueden utilizarse cuando: |
| − | : Se | + | |
| + | :* 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: | : Veamos un ejemplo: | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | :<syntaxhighlight lang="dart" 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 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 | + | * En esta sección vamos a ver cómo crear un objeto utilizando como fuente de datos un mapa. |
| − | :* Debéis recordar lo | + | :* 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]. |
| − | :* | + | :* 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 | + | : Partimos de un mapa que almacena la información de un vehículo. |
| − | : | + | : Es habitual recibir datos procedentes de servicios web en formato JSON y, una vez decodificados, trabajar con estructuras del tipo `Map<String, dynamic>`. |
<center> | <center> | ||
| Línea 260: | Línea 297: | ||
</center> | </center> | ||
| + | <br /> | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | :<syntaxhighlight lang=" | + | class Vehiculo { |
| − | 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); | |
| − | + | } </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. | ||
| − | + | : '''<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. | ||
| − | * | + | * Podéis emplear [https://quicktype.io/ Quicktype] para generar automáticamente modelos a partir de una estructura JSON. |
| − | |||
| − | |||
| − | + | * También podríamos utilizar un constructor `factory`, como vimos en el apartado anterior: | |
| − | |||
| − | |||
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="13-18"> | |
| − | + | 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() { | void main() { | ||
| − | + | final vehiculoJson = <String, dynamic>{ | |
| − | + | 'anoCompra': '2020', | |
| − | + | 'matricula': '1234GHZ', | |
| − | + | 'marca': 'Opel', | |
| − | + | }; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | final veh1 = Vehiculo.fromJson(vehiculoJson); | ||
| + | print(veh1); | ||
| + | } </syntaxhighlight> | ||
<br /> | <br /> | ||
==Herencia== | ==Herencia== | ||
| − | |||
| − | * | + | * Más información: [https://dart.dev/language/extend Extending a class]. |
| − | : | + | |
| − | :<syntaxhighlight lang=" | + | * 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; | |
| − | |||
| − | |||
| + | 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{ | + | 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); |
| − | </syntaxhighlight> | + | print(prof1); |
| + | } </syntaxhighlight> | ||
| + | <br /> | ||
| − | + | ===Overriding - Sobrescritura=== | |
| − | ===Overriding- | ||
| − | |||
| − | |||
| + | * La '''sobrescritura''' (''overriding'') permite que una subclase proporcione su propia implementación de un miembro que ya existe en la superclase. | ||
| + | : Es uno de los mecanismos que permiten aplicar polimorfismo en una jerarquía de herencia. | ||
| − | + | : Ya hemos utilizado este concepto al sobrescribir el método `toString`, heredado de la clase `Object`. | |
| + | * Es recomendable emplear la anotación `@override` cuando sobrescribimos un miembro. | ||
| + | : No es obligatoria para que se produzca la sobrescritura, pero permite que el analizador de Dart compruebe nuestra intención y detecte posibles errores. | ||
| + | * <u>Nota:</u> No debemos confundir '''sobrescritura''' (''overriding'') con '''sobrecarga''' (''overloading''). | ||
| + | : La sobrecarga consiste en definir varios métodos con el mismo nombre pero diferentes listas de parámetros. | ||
| + | : Dart no soporta este tipo de sobrecarga tradicional. | ||
| − | : Veamos un ejemplo en el que | + | : Veamos un ejemplo en el que definimos el método `imprimirDatos` en la superclase. |
| − | : | + | : Este método es heredado por las subclases. |
| + | : La clase `Alumno` lo sobrescribe y, además, llama desde su nueva implementación al método existente en la clase padre mediante `super.imprimirDatos()`. | ||
| − | :<syntaxhighlight lang=" | + | :<syntaxhighlight lang="dart" enclose="div" highlight="6-8,16-20,42,43"> |
class Personal { | 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{ | + | 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(); |
| − | </syntaxhighlight> | + | prof1.imprimirDatos(); |
| + | } </syntaxhighlight> | ||
<br /> | <br /> | ||
| Línea 462: | Línea 527: | ||
==Librerías. Privado/Público== | ==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 [https://dart.dev/tools/pub/packages paquetes]. | |
| + | : Más información: [https://dart.dev/language/libraries 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: | |
| − | |||
| − | |||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| + | class Personal { | ||
| + | String nombreCompleto; | ||
| − | + | Personal(this.nombreCompleto); | |
| − | |||
} | } | ||
| − | class | + | 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'; | ||
| + | } </syntaxhighlight> | ||
| − | + | : Ahora queremos utilizar estas clases desde otro archivo Dart. | |
| − | + | : Para ello empleamos `import`: | |
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="1"> | |
| − | + | import 'centro.dart'; | |
| − | + | void main() { | |
| + | final alum1 = Alumno( | ||
| + | numCarnet: 'C1A', | ||
| + | nombreCompleto: 'Juan Luis Rodriguez', | ||
| + | ); | ||
| − | + | final prof1 = Profesor( | |
| + | nrp: '1234534', | ||
| + | nombreCompleto: 'Luisa Sanchez Dragó', | ||
| + | ); | ||
| + | print(alum1); | ||
| + | print(prof1); | ||
| + | } </syntaxhighlight> | ||
| − | <br/> | + | <br /> |
| − | |||
| − | |||
| − | :<syntaxhighlight lang=" | + | * 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(); | |
| − | |||
| − | |||
| − | |||
| − | |||
| + | for (var cont = 0; cont < 10; cont++) { | ||
| + | print('Aleatorio generado: ${semilla.nextInt(50)}'); | ||
} | } | ||
| − | </syntaxhighlight> | + | } </syntaxhighlight> |
| − | + | ||
| − | * Podemos | + | * Podemos asignar un alias o prefijo a una biblioteca mediante la palabra reservada '''as''': |
| − | :<syntaxhighlight lang=" | + | |
| + | :<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(); | |
| − | |||
| − | |||
| − | |||
| − | |||
| + | for (var cont = 0; cont < 10; cont++) { | ||
| + | print('Aleatorio generado: ${semilla.nextInt(50)}'); | ||
} | } | ||
| − | </syntaxhighlight> | + | } </syntaxhighlight> |
| + | * Si una biblioteca contiene muchas declaraciones podemos importar únicamente aquellas que queremos utilizar mediante la palabra reservada `show`: | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | :<syntaxhighlight lang=" | + | import 'package02/centro/centro.dart' show Alumno, Profesor; </syntaxhighlight> |
| − | import ' | ||
| − | </syntaxhighlight> | ||
| − | + | : `show` no se limita a clases. Puede utilizarse con cualquier nombre exportado por la biblioteca, como funciones, variables, enumeraciones, etc. | |
| − | |||
| − | |||
| − | |||
| + | * También podemos hacer lo contrario mediante `hide`, importando todas las declaraciones públicas excepto las indicadas: | ||
| + | |||
| + | :<syntaxhighlight lang="dart" enclose="div"> | ||
| + | import 'package02/centro/centro.dart' hide Personal; </syntaxhighlight> | ||
<br /> | <br /> | ||
| − | * | + | * 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. | ||
| − | + | * Siguiendo el ejemplo anterior, si no queremos exponer directamente la clase `Personal` fuera de su biblioteca podemos declararla como `_Personal`: | |
| − | : | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="1,4,7,16"> | |
| − | |||
| − | :<syntaxhighlight lang=" | ||
class _Personal { | class _Personal { | ||
| − | + | String nombreCompleto; | |
| − | + | _Personal(this.nombreCompleto); | |
| + | } | ||
| − | + | class Alumno extends _Personal { | |
| − | 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{ | + | 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'; |
| + | } </syntaxhighlight> | ||
| − | + | : Desde otra biblioteca que importe este fichero no podremos referenciar directamente 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. | ||
| − | + | : '''<u>Nota:</u>''' 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. |
| − | : | ||
| − | |||
<br /> | <br /> | ||
| Línea 606: | Línea 695: | ||
==Getters y Setters== | ==Getters y Setters== | ||
| − | * Más información: https://dart.dev | + | * Más información: [https://dart.dev/language/methods#getters-and-setters Getters and setters]. |
| − | * Los getters y setters son | + | * 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. |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | : | + | 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 { | 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'; | ||
| + | } </syntaxhighlight> | ||
<br /> | <br /> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * También podemos utilizar getters y setters para validar los valores almacenados en las propiedades de una clase: | |
| − | + | :<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; | |
| − | + | _Personal(this.nombreCompleto); | |
| + | } | ||
| − | + | class Alumno extends _Personal { | |
| − | 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'; | ||
| + | } </syntaxhighlight> | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
| + | |||
==Clases abstractas / Interfaces== | ==Clases abstractas / Interfaces== | ||
| − | * Más información: https://dart.dev/ | + | * 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. | ||
| − | * | + | * 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: |
| − | |||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | + | interface class Ejemplo { | |
| + | } </syntaxhighlight> | ||
| − | + | : El modificador `interface` expresa que la clase puede utilizarse como interfaz desde otras bibliotecas. | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | : Cuando queremos definir una clase destinada únicamente a expresar un contrato y que, además, no pueda ser instanciada directamente, podemos combinar ambos modificadores: | |
| − | |||
| − | + | :<syntaxhighlight lang="dart" enclose="div"> | |
| − | + | abstract interface class Ejemplo { | |
| − | + | } </syntaxhighlight> | |
| − | + | ===Clases abstractas=== | |
| − | |||
| − | |||
| − | + | : Veamos primero un ejemplo utilizando una clase abstracta como superclase: | |
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="7,9,11,15,20,28-30,41-43"> | |
| − | + | 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 | + | 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'; | |
| − | + | } </syntaxhighlight> | |
| + | |||
| + | [[Imagen:Flutter_dart_abstract_1.JPG|500px|center]] | ||
| − | </ | + | <br /> |
| + | ===Interfaces=== | ||
| + | * Cuando queremos definir principalmente un '''contrato''' que otras clases tengan que implementar podemos utilizar una `abstract interface class`. | ||
| + | : En este caso utilizamos `implements`. | ||
| − | + | : Cuando una clase implementa una interfaz debe proporcionar una implementación para todos sus miembros de instancia requeridos. | |
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="11-17,19,25-26,35-36"> | |
| − | :<syntaxhighlight lang=" | ||
void main() { | 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; | |
| − | + | } </syntaxhighlight> | |
| − | |||
| − | |||
| + | : Fijarse en la diferencia entre los ejemplos anteriores: | ||
| − | * | + | :* `Alumno extends Personal` indica una relación de '''herencia'''. |
| + | :* `Cuadrado implements Figura` indica que `Cuadrado` se compromete a cumplir el '''contrato''' definido por `Figura`. | ||
| − | + | ===Polimorfismo=== | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * Podemos utilizar una referencia cuyo tipo sea una clase abstracta o una interfaz para trabajar con objetos de diferentes clases que cumplan ese contrato. | |
| − | |||
| − | |||
| − | |||
| − | |||
| + | : Por ejemplo: | ||
| − | + | :<syntaxhighlight lang="dart" enclose="div" highlight="6,7,11-13"> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
void main() { | void main() { | ||
| − | + | final cuadrado1 = Cuadrado(lado: 10); | |
| − | + | final circulo1 = Circulo(5); | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | 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. | ||
| − | + | <br /> | |
| + | ==Mixins== | ||
| − | + | * Más información: | |
| − | + | :* [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. | ||
| − | + | : Los mixins se incorporan a una clase mediante la palabra reservada '''with'''. | |
| − | |||
| − | + | :<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'); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| − | + | 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, | ||
| + | ); | ||
| + | } </syntaxhighlight> | ||
| + | <br /> | ||
| + | ==Ejercicios== | ||
<br /> | <br /> | ||
| − | + | ===Ejercicio 1=== | |
| − | |||
| − | |||
| + | * Crea un conjunto de clases para representar diferentes miembros de una escuela, tales como profesores y estudiantes. | ||
| + | : Utilizarás herencia para compartir propiedades y comportamientos comunes, mientras que una interfaz permitirá definir el contrato que deberán cumplir los 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. | ||
Revisión del 18:19 23 sep 2026
Sumario
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.
- Podéis consultar cómo se implementan estos conceptos en Java en Fundamentos de POO en Java y en la ampliación de la POO.
- 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
- Más información: Factory constructors.
- Los constructores `factory` se utilizan cuando la creación de un objeto necesita un comportamiento diferente al de un constructor generativo convencional.
: Por ejemplo, pueden utilizarse cuando:
- queremos devolver una instancia creada anteriormente, como ocurre al utilizar una caché;
- queremos devolver una instancia de una subclase compatible;
- queremos ejecutar lógica previa antes de decidir qué objeto devolver;
- queremos transformar o interpretar información antes de delegar la construcción a otro constructor.
- Se definen anteponiendo la palabra reservada factory al nombre del constructor.
- Un constructor `factory` debe devolver un objeto compatible con el tipo de la clase. Puede hacerlo mediante `return` o utilizando la sintaxis de función flecha (`=>`).
- Un uso habitual consiste en delegar la creación del objeto al constructor principal.
- 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
- Más información: Extending a class.
- 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`:
- 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
- Más información: Getters and 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'; }
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.