Menu
CoddyTech

레포지토리 패턴

Coddy Dart 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 110개 중 100번째.

Repository 패턴은 애플리케이션과 데이터 소스 사이에 추상화 계층을 만들어 데이터 접근 로직과 비즈니스 로직을 분리합니다. 클래스가 데이터베이스, API 또는 파일과 직접 상호작용하는 대신, 모든 데이터 작업을 처리하는 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
}

비즈니스 로직은 UserRepository 인터페이스에만 의존합니다. 나중에 동일한 인터페이스를 구현하는 DatabaseUserRepository 또는 ApiUserRepository를 만들고, 저장소를 사용하는 코드를 수정하지 않고도 구현을 교체할 수 있습니다. 이 패턴은 데이터 소스가 시간이 지남에 따라 변경될 수 있는 테스트 가능하고 유지 관리하기 쉬운 애플리케이션에 필수적입니다.

challenge icon

챌린지

쉬움

Repository 패턴을 사용해 제품 재고 관리 시스템을 만들어 봅시다! 제품을 저장하는 방식과 제품을 사용하는 방식을 분리하는 추상화 계층을 만들어, 비즈니스 로직을 변경하지 않고도 저장소 구현을 쉽게 교체할 수 있습니다.

코드를 두 파일로 나누겠습니다.

  • product_repository.dart: 이 파일에는 엔터티 클래스와 저장소 클래스가 들어갑니다. 먼저 id(int), name(String), price(double)에 해당하는 final 필드가 있는 Product 클래스를 만듭니다. 그런 다음 데이터 작업의 계약을 선언하는 추상 클래스 ProductRepository를 정의합니다. 여기에는 null을 허용하는 Product를 반환하는 getById(int id), 제품 목록을 반환하는 getAll(), 제품을 저장하는 save(Product product), 제품을 삭제하는 delete(int id)가 있습니다. 마지막으로 저장소로 Map<int, Product>를 사용해 이 인터페이스를 구현하는 InMemoryProductRepository를 만듭니다.
  • 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

직접 해보기

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 컴파일러