Паттерн Репозиторий
Часть раздела Объектно-ориентированное программирование путешествия по 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, реализующий тот же интерфейс, и заменить реализацию, не изменяя код, который использует репозиторий. Этот шаблон необходим для тестируемых и удобных в сопровождении приложений, в которых источники данных могут со временем меняться.
Задание
ЛегкоДавайте создадим систему учёта товаров с использованием шаблона «Репозиторий»! Вы создадите уровень абстракции, который отделяет способ хранения товаров от способа их использования, благодаря чему можно будет легко менять реализацию хранилища, не изменяя бизнес-логику.
Вы разместите код в двух файлах:
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]"
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн РепозиторийПотренируйтесь самостоятельно: Онлайн-компилятор Dart