Menu
Coddy logo textTech

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

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

Паттерн Репозиторий (Repository pattern) отделяет логику доступа к данным от бизнес-логики, создавая слой абстракции между вашим приложением и источниками данных. Вместо того чтобы ваши классы напрямую взаимодействовали с базами данных, 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

Задание

Легко

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

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

  • product_repository.dart: Этот файл содержит ваш класс сущности и классы репозитория. Начните с класса Product, который имеет поля final для id (int), name (String) и price (double). Затем определите абстрактный класс ProductRepository, который объявляет контракт для операций с данными: getById(int id), возвращающий nullable Product, getAll(), возвращающий список товаров, save(Product product) для сохранения товара и delete(int id) для его удаления. Наконец, создайте InMemoryProductRepository, который реализует этот интерфейс, используя Map<int, Product> для хранения.
  • main.dart: Импортируйте файл репозитория и продемонстрируйте паттерн Repository в действии. Создайте 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

Шпаргалка

Паттерн Repository (Репозиторий) отделяет логику доступа к данным от бизнес-логики, создавая слой абстракции между вашим приложением и источниками данных.

Определите интерфейс для операций с данными:

// Сущность
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
}

Бизнес-логика зависит только от интерфейса, что позволяет вам менять реализации (например, DatabaseUserRepository, ApiUserRepository) без изменения кода, использующего репозиторий.

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

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Проверьте себя

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

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