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.

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 Baselimita los tipos y permite usar los miembros deBase.- 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.