DMarlon Studio

Lección 9 · 9 de 20

Genéricos · Dart

Escriba clases y funciones genéricas en Dart, con tipos acotados (extends) e inferencia, con un repositorio en memoria y una función groupBy.

Dart · Genéricos en DMarlon Studio: el código del ejemplo y su salida en la terminal

Los genéricos permiten escribir una clase o una función una sola vez y usarla con cualquier tipo, sin perder la comprobación de tipos. Seguramente ya los usa a diario: List<String> o Map<int, T> son clases genéricas.

Repository<T extends Entity> es un repositorio en memoria para cualquier entidad. T es un parámetro de tipo y extends Entity lo acota: solo valen tipos que tengan id, y gracias a eso save puede usar item.id como clave. Un Repository<Customer> guarda clientes y nada más; customers.save('Elena') no compilaría. findById devuelve T? porque el cliente puede no existir, y con el 9 da null.

groupBy<K, V> es una función genérica: recibe elementos de tipo V y una función que obtiene de cada uno una clave de tipo K. No hace falta escribir los tipos al llamarla, porque Dart los infiere de los argumentos: con clientes y c.city, K es String; con palabras y s.length, K es int. El operador ??= crea la lista de cada grupo la primera vez que aparece su clave.

A diferencia de Java, los genéricos de Dart se conservan al ejecutar el programa (son reificados): runtimeType escribe Repository<Customer>, y una comprobación como is List<int> funciona de verdad.

Código · main.dart

abstract class Entity { int get id; }class Customer implements Entity {  @override final int id;  final String name, city;  Customer(this.id, this.name, this.city);  @override String toString() => name;}// One generic class, valid for any type that is an Entity.class Repository<T extends Entity> {  final Map<int, T> _items = {};  void save(T item) => _items[item.id] = item;  T? findById(int id) => _items[id];  List<T> get all => _items.values.toList();}// A generic function: K and V are inferred from the arguments.Map<K, List<V>> groupBy<K, V>(Iterable<V> items, K Function(V) keyOf) {  final groups = <K, List<V>>{};  for (final item in items) (groups[keyOf(item)] ??= []).add(item);  return groups;}void main() {  final customers = Repository<Customer>()    ..save(Customer(1, 'Elena', 'Madrid'))    ..save(Customer(2, 'Jorge', 'Malaga'))    ..save(Customer(3, 'Nuria', 'Madrid'));  print('Found: ${customers.findById(2)}, missing: ${customers.findById(9)}');  print(groupBy(customers.all, (c) => c.city));  print(groupBy(['kiwi', 'fig', 'pear', 'plum'], (s) => s.length));  print('Type kept at runtime: ${customers.runtimeType}');}

Salida · dart run main.dart

Found: Jorge, missing: null
{Madrid: [Elena, Nuria], Malaga: [Jorge]}
{4: [kiwi, pear, plum], 3: [fig]}
Type kept at runtime: Repository<Customer>

Para recordar

  • <T> es un parámetro de tipo que se fija al usar la clase.
  • T extends Base limita los tipos y permite usar los miembros de Base.
  • Las funciones también pueden ser genéricas, y Dart infiere sus tipos.
  • Los genéricos de Dart se conservan en tiempo de ejecución.

Ejercicio

Cree una clase Product implements Entity con id, name y price, guarde tres en un Repository<Product> y agrúpelos con groupBy según cuesten más o menos de 20 euros.