Menu
CoddyTech

Паттерн Репозиторий

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy. Урок 100 из 110.

Паттерн Repository отделяет логику доступа к данным от бизнес-логики, создавая слой абстракции между приложением и источниками данных. Вместо того чтобы классы напрямую взаимодействовали с базами данных, API или файлами, они работают с репозиторием, который выполняет все операции с данными.

Этот шаблон определяет интерфейс для операций с данными, а конкретные реализации отвечают за фактический механизм хранения. Благодаря этому источники данных можно легко менять, не изменяя бизнес-логику:

// Сущность
class User {
  final int id;
  final String name;
  User(this.id, this.name);
}

// Интерфейс репозитория
abstract class UserRepository {
  User? getById(int id);
  List<User> getAll();
  void save(User user);
  void delete(int id);
}

// Реализация в оперативной памяти
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
}

Ваша бизнес-логика зависит только от интерфейса UserRepository. Позже вы можете создать DatabaseUserRepository или ApiUserRepository, реализующий тот же интерфейс, и заменить реализацию, не изменяя код, который использует репозиторий. Этот шаблон необходим для тестируемых и удобных в сопровождении приложений, в которых источники данных могут со временем меняться.

challenge icon

Задание

Легко

Давайте создадим систему учёта товаров с использованием шаблона «Репозиторий»! Вы создадите уровень абстракции, который отделяет способ хранения товаров от способа их использования, благодаря чему можно будет легко менять реализацию хранилища, не изменяя бизнес-логику.

Вы разместите код в двух файлах:

  • product_repository.dart: этот файл содержит классы сущности и репозитория. Начните с класса Product, в котором есть поля final для id (int), name (String) и price (double). Затем определите абстрактный класс ProductRepository, объявляющий контракт для операций с данными: getById(int id), возвращающий допускающий значение null объект Product; getAll(), возвращающий список товаров; save(Product product), сохраняющий товар; и delete(int id), удаляющий товар. Наконец, создайте класс InMemoryProductRepository, реализующий этот интерфейс с помощью Map<int, Product> для хранения данных.
  • main.dart: импортируйте файл репозитория и продемонстрируйте работу шаблона «Репозиторий». Создайте объект InMemoryProductRepository, но сохраните его в переменной типа ProductRepository — так вы покажете, что код зависит от интерфейса, а не от реализации. Сохраните три товара: Laptop (id: 1, price: 999.99), Mouse (id: 2, price: 29.99) и Keyboard (id: 3, price: 79.99). Затем получите товар с id 2 и выведите его в формате Found: [name] - $[price]. Удалите товар с id 1. Наконец, выведите количество оставшихся товаров в формате Products in stock: [count].

Преимущество этого шаблона в том, что основной код знает только об интерфейсе ProductRepository. Позже вы сможете создать DatabaseProductRepository или ApiProductRepository и заменить им текущую реализацию, не меняя бизнес-логику!

Ожидаемый вывод:

Found: Mouse - $29.99
Products in stock: 2

Попробуйте сами

import 'product_repository.dart';

void main() {
  // Создайте репозиторий — обратите внимание, что типом является интерфейс, а не реализация
  // TODO: Создайте InMemoryProductRepository и сохраните его в переменной типа ProductRepository
  
  // TODO: Сохраните три товара:
  // - Laptop (id: 1, price: 999.99)
  // - Mouse (id: 2, price: 29.99)
  // - Keyboard (id: 3, price: 79.99)
  
  // TODO: Получите товар с id 2 и выведите: "Found: [name] - $[price]"
  
  // TODO: Удалите товар с id 1
  
  // TODO: Выведите количество оставшихся товаров: "Products in stock: [count]"
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Dart