레포지토리 패턴
Coddy Dart 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 110개 중 100번째.
레포지토리 패턴(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를 생성하고, 저장소를 사용하는 코드를 수정하지 않고도 구현을 교체할 수 있습니다. 이 패턴은 데이터 소스가 시간이 지남에 따라 변경될 수 있는 테스트 가능하고 유지 관리 가능한 애플리케이션에 필수적입니다.
챌린지
쉬움Repository 패턴을 사용하여 제품 인벤토리 시스템을 구축해 봅시다! 제품이 저장되는 방식과 사용되는 방식을 분리하는 추상화 계층을 만들어, 비즈니스 로직을 변경하지 않고도 저장소 구현을 쉽게 교체할 수 있도록 할 것입니다.
코드는 두 개의 파일로 구성됩니다:
product_repository.dart: 이 파일에는 엔티티 및 리포지토리 클래스가 포함됩니다. 먼저id(int),name(String),price(double)에 대한final필드를 가진Product클래스를 만듭니다. 그런 다음 데이터 작업을 위한 계약을 선언하는 추상 클래스ProductRepository를 정의합니다: nullableProduct를 반환하는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치트 시트
레포지토리 패턴(Repository pattern)은 애플리케이션과 데이터 소스 사이에 추상화 계층을 생성하여 데이터 액세스 로직을 비즈니스 로직에서 분리합니다.
데이터 작업을 위한 인터페이스를 정의합니다:
// 엔티티
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]"
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.