← Назад к вопросам

Что такое репозиторий?

2.0 Middle🔥 182 комментариев
#Архитектура Flutter#ООП и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Репозиторий (Repository)

Репозиторий — это слой абстракции, который инкапсулирует логику доступа к данным. Репозиторий скрывает детали того, откуда берутся данные (база данных, API, локальное хранилище), и предоставляет простой интерфейс для работы с ними.

Архитектура с Repository

┌─────────────────────┐
│   Presentation      │ UI, BLoC, Screens
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│  Interactor/UseCase │ Бизнес-логика
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│   Repository        │ <- Абстракция
└──────────┬──────────┘
           ↓
┌─────────────────────┐
│  Data Sources       │ API, Database, Cache
└─────────────────────┘

Repository получает данные из одного или нескольких источников и предоставляет их в единообразном формате.

Простой пример: Repository для пользователей

// Модель
class User {
  final String id;
  final String name;
  final String email;
  
  User({
    required this.id,
    required this.name,
    required this.email,
  });
  
  // Преобразование в JSON
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
  };
  
  // Создание из JSON
  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'],
    name: json['name'],
    email: json['email'],
  );
}

// Абстрактный интерфейс Repository
abstract class UserRepository {
  Future<User> getUserById(String id);
  Future<List<User>> getAllUsers();
  Future<void> createUser(User user);
  Future<void> updateUser(User user);
  Future<void> deleteUser(String id);
}

// Конкретная реализация: получение данных с API
class UserApiRepository implements UserRepository {
  final String baseUrl = 'https://api.example.com';
  
  @override
  Future<User> getUserById(String id) async {
    try {
      // Имитация HTTP запроса
      print('Fetching user from API: $id');
      // final response = await http.get(Uri.parse('$baseUrl/users/$id'));
      // return User.fromJson(jsonDecode(response.body));
      return User(id: id, name: 'John', email: 'john@example.com');
    } catch (e) {
      throw Exception('Failed to fetch user: $e');
    }
  }
  
  @override
  Future<List<User>> getAllUsers() async {
    try {
      print('Fetching all users from API');
      return [
        User(id: '1', name: 'John', email: 'john@example.com'),
        User(id: '2', name: 'Jane', email: 'jane@example.com'),
      ];
    } catch (e) {
      throw Exception('Failed to fetch users: $e');
    }
  }
  
  @override
  Future<void> createUser(User user) async {
    try {
      print('Creating user on API');
      // await http.post(...)
    } catch (e) {
      throw Exception('Failed to create user: $e');
    }
  }
  
  @override
  Future<void> updateUser(User user) async {
    try {
      print('Updating user on API');
    } catch (e) {
      throw Exception('Failed to update user: $e');
    }
  }
  
  @override
  Future<void> deleteUser(String id) async {
    try {
      print('Deleting user from API');
    } catch (e) {
      throw Exception('Failed to delete user: $e');
    }
  }
}

// Другая реализация: локальная база данных
class UserLocalRepository implements UserRepository {
  // Имитация локальной БД
  final List<User> _localDb = [];
  
  @override
  Future<User> getUserById(String id) async {
    try {
      print('Fetching user from local database: $id');
      return _localDb.firstWhere((user) => user.id == id);
    } catch (e) {
      throw Exception('User not found: $e');
    }
  }
  
  @override
  Future<List<User>> getAllUsers() async {
    print('Fetching all users from local database');
    return _localDb;
  }
  
  @override
  Future<void> createUser(User user) async {
    print('Creating user in local database');
    _localDb.add(user);
  }
  
  @override
  Future<void> updateUser(User user) async {
    print('Updating user in local database');
    final index = _localDb.indexWhere((u) => u.id == user.id);
    if (index >= 0) {
      _localDb[index] = user;
    }
  }
  
  @override
  Future<void> deleteUser(String id) async {
    print('Deleting user from local database');
    _localDb.removeWhere((user) => user.id == id);
  }
}

Repository с несколькими источниками данных

// Комбинированный Repository: сначала локальное хранилище, потом API
class UserCachedRepository implements UserRepository {
  final UserApiRepository apiRepository;
  final UserLocalRepository localRepository;
  
  UserCachedRepository({
    required this.apiRepository,
    required this.localRepository,
  });
  
  @override
  Future<User> getUserById(String id) async {
    try {
      // Сначала пытаемся получить из локального хранилища
      print('Trying local database...');
      return await localRepository.getUserById(id);
    } catch (e) {
      // Если не нашли, получаем с API
      print('Not found locally, fetching from API...');
      final user = await apiRepository.getUserById(id);
      // Сохраняем в локальное хранилище
      await localRepository.createUser(user);
      return user;
    }
  }
  
  @override
  Future<List<User>> getAllUsers() async {
    try {
      return await localRepository.getAllUsers();
    } catch (e) {
      final users = await apiRepository.getAllUsers();
      for (var user in users) {
        await localRepository.createUser(user);
      }
      return users;
    }
  }
  
  @override
  Future<void> createUser(User user) async {
    await apiRepository.createUser(user);
    await localRepository.createUser(user);
  }
  
  @override
  Future<void> updateUser(User user) async {
    await apiRepository.updateUser(user);
    await localRepository.updateUser(user);
  }
  
  @override
  Future<void> deleteUser(String id) async {
    await apiRepository.deleteUser(id);
    await localRepository.deleteUser(id);
  }
}

Использование Repository в Interactor

// Use Case (Interactor)
class GetUserUseCase {
  final UserRepository repository;
  
  GetUserUseCase({required this.repository});
  
  Future<User> call(String userId) async {
    // Бизнес-логика просто использует Repository
    final user = await repository.getUserById(userId);
    
    // Можно добавить обогащение данных
    // Можно добавить валидацию
    // Но детали доступа к данным скрыты
    
    return user;
  }
}

// BLoC использует Use Case
class UserBloc extends Bloc<UserEvent, UserState> {
  final GetUserUseCase getUserUseCase;
  
  UserBloc({required this.getUserUseCase})
      : super(const UserState.initial()) {
    on<GetUserEvent>(_onGetUser);
  }
  
  Future<void> _onGetUser(
    GetUserEvent event,
    Emitter<UserState> emit,
  ) async {
    emit(const UserState.loading());
    try {
      final user = await getUserUseCase(event.userId);
      emit(UserState.success(user));
    } catch (e) {
      emit(UserState.error(e.toString()));
    }
  }
}

Преимущества Repository паттерна

  1. Абстракция источника данных — код не зависит от того, откуда берутся данные
  2. Тестируемость — легко создать mock Repository для тестов
  3. Переиспользование — один Repository могут использовать разные Use Cases
  4. Кэширование — можно добавить слой кэша между API и БД
  5. Слабая связанность — изменения в API не влияют на бизнес-логику
  6. Централизованная логика — вся логика работы с данными в одном месте

Зависимость Injection

// При создании объектов передаём нужный Repository
final apiRepository = UserApiRepository();
final getUserUseCase = GetUserUseCase(repository: apiRepository);
final userBloc = UserBloc(getUserUseCase: getUserUseCase);

// Для тестов передаём mock
final mockRepository = MockUserRepository();
final testUseCase = GetUserUseCase(repository: mockRepository);

Repository — это фундаментальный паттерн в Clean Architecture, обеспечивающий разделение ответственности и гибкость архитектуры.