안녕하세요. 점냥입니다:)
이번 포스팅에서는 MVP 아키텍처 패턴에서 Model
에 관하여 알아보려고 합니다.
구글의 Blueprints v2 다양한 아키텍처 샘플 코드와 안드로이드 개발자 사이트의 예제를 보면 데이터를 관리하는 부분의 클래스 이름이 Model이 아니란 것을 느낄 수 있다. data, source 등 새로운 개념이 등장하여 이것이 모델인가?라는 의문이 필자도 들었다.
해당 개념은 Repository Pattern
이라는 디자인 패턴 개념이다.
Repository
Repository pattern은 Model에 대해서 관심사 분리
를 수행한 개념이라고 할 수있습니다.
MVP에서 Model이랑 데이터를 요청하고 받는 Presenter는 최대 관심사가 뭘까요? 바로 View에게 넘겨줄 Data를 받고 전달해주는 것입니다. 기존 Model은 Prsenter가 Model이 어떻게 Data를 불러오는지 관심있게 지켜보고 있어야 합니다. 무슨 뜻인지 코드를 보며 추가적으로 설명드리겠습니다.
MainModel.class
class MainModel(FoodService foodService) {
public static MainModel INSTANCE = null;
private FoodService foodService = null;
private MainModel(FoodService foodService) {
this.foodService = foodService;
}
public MainModel getInstance(@NotNull FoodService foodService) {
if (INSTANCE == null) {
INSTANCE = new MainModel(foodService);
}
return INSTANCE;
}
public Food getFood() {
return foodService.getFood();
}
}
MainPresenter.class
class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private MainModel mainModel;
public MainPresenter(MainContract.View view, MainModel model) {
this.view = view;
this.model = model;
}
@Override
public void getFood() {
view.showFood(mainModel.getFood());
}
}
MainModel은 서버에서 음식 정보를 가져오는 FoodService를 가지고 있으며, MainPresenter는 MainModel의 함수를 직접 호출하여 음식 정보를 받고 View에게 넘겨줍니다.
이런 관계는 아래와 같은 상황이 온다면 수정할 곳이 여러군데 생길 수 있습니다.
- 음식 정보를 서버에서도, 로컬(sqlite) 등 한 곳뿐만 아니라 여러곳에서도 가져올 수 있게 변경해야하는 경우
private LocalFoodService remoteFoodService = null;
private RemoteFoodService localFoodService = null;
public Food getRemoteFood() { return remoteFoodService.getFood(); }
public Food getLocalFood() { return loaclFoodService.getFood(); }
- 함수 이름이 변경되어 호출 방법이 달라지는 경우
public Food getLikeFood() { return foodService.getFood(); }
Repository pattern 구현
Repositry pattern는 MVP에서 Model이 Data와 Source 부분으로 나누어진다. Source
는 필요한 정보를 local, remote에서 정보를 직접 가져오는 역할을 담당하고, Data
는 Presenter는 모르게 local에서 가져올 지, remote에서 가져올 지 선택하여 Data를 저장, 전달해주는 역할을 담당한다.
FoodRepository.class
interface FoodRepository {
@NotNull
Food getFood();
void refreshFood();
}
FoodRepositoryImpl.class
class FoodRepositoryImpl implements FoodRepository {
public static FoodRepositoryImpl INSTANCE = null;
private LocalFoodService remoteFoodService = null;
private RemoteFoodService localFoodService = null;
boolean cacheIsDirty = false;
Food cachedFood;
private FoodRepositoryImple(@NotNull LocalFoodService localFoodService,
@NotNull RemoteFoodService remoteFoodService) {
this.localFoodService = localFoodService;
this.RemoteFoodService = remoteFoodService;
}
public FoodRepositoryImpl getInstance(@NotNull LocalFoodService localFoodService,
@NotNull RemoteFoodService remoteFoodService) {
if (INSTANCE == null) {
INSTANCE = new FoodRepository(localFoodService, remoteFoodService);
}
return INSTANCE;
}
public Food getFood() {
if (cachedFood != null && !cacheIsDirty) {
return cachedFood;
}
if (cacheisDirty) {
cachedFood = remoteFoodService.getFood();
cacheisDirty = false;
return cachedFood;
} else {
cachedFood = localFoodService.getFood();
cacheisDirty = false;
return cachedFood;
}
}
private Food getRemoteFood() {
return remoteFoodService.getFood();
}
private Food getLocalFood() {
return loaclFoodService.getFood();
}
@Override
public void refreshFood() {
cacheIsDirty = true;
}
}
MainPresenter.class
class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private FoodRepository repository;
public MainPresenter(@NotNull MainContract.View view,
@NotNull FoodRepository repository) {
this.view = view;
this.repository = repository;
}
@Override
public void getFood() {
Food food = repository.getFood();
view.showFood(food)
}
}
MainPresenter는 interface인 Repository를 인자로 받는다. Repository class를 직접 받지 않고 interface로 추상화하기 때문에 의존성이 줄어들고, 이후에 Repository를 구현한 Mock 객체를 주입해서 테스트를 할 수도 있다.
interface를 구현한 클래스는 implements의 축약한 Impl
단어를 클래스 이름 끝에 설정한다. 위 코드에서 FoodRepositoryImpl
클래스가 해당하며 해당 클래스 내에서 local, remote datasource에서 적절히 Data를 불러오고 cached 작업까지 담당한다. 비로서 Presenter에서 Data를 불러오는 로직을 분리하게 된다.
Model 말고 Repository
일반적인 Model은 가게의 사장님 처럼 주방에 대해 빠삭해야 한다고 할 수 있다. 어떤 원산지를 사용하여 요리를 하는지 알고 있고 주방의 변화가 생기면 그대로 사장님 자신에게 영향이 간다. 반면 오른쪽 Repository Pattern은 손님 입장으로 볼 수있다. 가게에서 준 메뉴판(interface)을 보고서 믿고 시키면 된다. 요리를 어떤 원산지를 쓰는지, 주방의 변화가 크게 영향을 미치지 않는다.
'Android > Common' 카테고리의 다른 글
[Android] Testable App - Ui Test (0) | 2020.12.26 |
---|---|
[Android] Testable App - JUnit Unit Test (0) | 2020.12.15 |
[Android] 뷰의 성능 개선 - RecyclerView (3) | 2020.11.05 |
[Android] 뷰의 성능 개선 - 오버드로 줄이기 (3) | 2020.10.08 |
[Android] Drawable color 속성을 코드로 변경하기 (0) | 2020.08.24 |