Menu
Coddy logo textTech

Patrón Repository

Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy. Lección 100 de 110.

El patrón Repository separa la lógica de acceso a datos de la lógica de negocio mediante la creación de una capa de abstracción entre tu aplicación y las fuentes de datos. En lugar de hacer que tus clases interactúen directamente con bases de datos, API o archivos, estas trabajan con un repositorio que gestiona todas las operaciones de datos.

El patrón define una interfaz para operaciones de datos, y las implementaciones concretas manejan el mecanismo de almacenamiento real. Esto facilita el intercambio de fuentes de datos sin cambiar su lógica de negocio:

// Entidad
class User {
  final int id;
  final String name;
  User(this.id, this.name);
}

// Interfaz del repositorio
abstract class UserRepository {
  User? getById(int id);
  List<User> getAll();
  void save(User user);
  void delete(int id);
}

// Implementación en memoria
class InMemoryUserRepository implements UserRepository {
  final Map<int, User> _users = {};

  @override
  User? getById(int id) => _users[id];

  @override
  List<User> getAll() => _users.values.toList();

  @override
  void save(User user) => _users[user.id] = user;

  @override
  void delete(int id) => _users.remove(id);
}

void main() {
  UserRepository repo = InMemoryUserRepository();
  
  repo.save(User(1, 'Alice'));
  repo.save(User(2, 'Bob'));
  
  print(repo.getById(1)?.name);  // Alice
  print(repo.getAll().length);    // 2
}

Tu lógica de negocio depende únicamente de la interfaz UserRepository. Más adelante podrías crear un DatabaseUserRepository o un ApiUserRepository que implemente la misma interfaz, e intercambiar las implementaciones sin modificar el código que utiliza el repositorio. Este patrón es esencial para aplicaciones testeables y mantenibles donde las fuentes de datos pueden cambiar con el tiempo.

challenge icon

Desafío

Fácil

¡Construyamos un sistema de inventario de productos usando el patrón Repository! Crearás una capa de abstracción que separa cómo se almacenan los productos de cómo se utilizan, lo que facilita cambiar las implementaciones de almacenamiento sin modificar la lógica de negocio.

Organizarás tu código en dos archivos:

  • product_repository.dart: Este archivo contiene tus clases de entidad y repositorio. Comienza con una clase Product que tenga campos final para id (int), name (String) y price (double). Después, define una clase abstracta ProductRepository que declare el contrato para las operaciones de datos: getById(int id), que devuelve un Product anulable; getAll(), que devuelve una lista de productos; save(Product product), para almacenar un producto; y delete(int id), para eliminar uno. Finalmente, crea un InMemoryProductRepository que implemente esta interfaz usando un Map<int, Product> para el almacenamiento.
  • main.dart: Importa tu archivo de repositorio y demuestra el patrón Repository en acción. Crea un InMemoryProductRepository, pero guárdalo en una variable de tipo ProductRepository; esto muestra que tu código depende de la interfaz, no de la implementación. Guarda tres productos: Laptop (id: 1, price: 999.99), Mouse (id: 2, price: 29.99) y Keyboard (id: 3, price: 79.99). Después, recupera e imprime el producto con id 2 en el formato Found: [name] - $[price]. Elimina el producto con id 1. Finalmente, imprime la cantidad de productos restantes como Products in stock: [count].

La ventaja de este patrón es que tu código principal solo conoce la interfaz ProductRepository. Más adelante podrías crear un DatabaseProductRepository o un ApiProductRepository y sustituirlo sin cambiar nada de la lógica de negocio.

Salida esperada:

Found: Mouse - $29.99
Products in stock: 2

Pruébalo tú mismo

import 'product_repository.dart';

void main() {
  // Crear repositorio - nota que el tipo es la interfaz, no la implementación
  // TODO: Crear un InMemoryProductRepository y almacenarlo en una variable ProductRepository
  
  // TODO: Guardar tres productos:
  // - Laptop (id: 1, price: 999.99)
  // - Mouse (id: 2, price: 29.99)
  // - Keyboard (id: 3, price: 79.99)
  
  // TODO: Recuperar el producto con id 2 e imprimir: "Found: [name] - $[price]"
  
  // TODO: Eliminar el producto con id 1
  
  // TODO: Imprimir el recuento de los productos restantes: "Products in stock: [count]"
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de Dart online