MVP 패턴 소개 (안드로이드)
신림프로그래머, 최범균, 2016-06-18
madvirus@madvirus.net
다룰 내용
• 막 만들면
• MVP 패턴
– 적용 결과
• MVP 패턴 적용 예
– 안드로이드
– 자바스크립트
• 정리
2
간단한 앱, 막 만들면…
3
드래그 탭
미림분식
별: 5
4
public class StoreActivity extends Activity …. {
private Handler handler = new Handler();
protected void onStart() {
loadingProgress.show();
LoadListThread lThread = new LoadListThread();
lThread.start();
}
private class LoadListThread extends Thread {
public void run() {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
...
handler.post(new Runnable() {
public void run() {
loadingProgress.hide();
if (loadingResult) {
localDB.save(loadedData);
drawMarkerForStores(loadedData);
} else {
toasts..short("에러!");
}
}
});
}
}
기능 흐름 제어
데이터 보관 처리
UI 화면 제어
네트
워크
UI 처리 위한 쓰레드
화면 제어
쓰레드 제어
5
public class StoreActivity extends Activity …. {
private MapPoint center;
public void onMapViewDragEnded(
MapView mapView, MapPoint mapPoint) {
center = mapPoint;
loadingProgress.show();
LoadListThread lThread = new LoadListThread();
lThread.start();
}
private class LoadListThread extends Thread {
public void run() {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
...
if (center != null) { params.put("center", toCoord(center)); }
...
handler.post(new Runnable() {
public void run() {
loadingProgress.hide();
…
}
});
}
}
사용자 이벤트 처리
UI 의존
데이터
화면 제어 + 쓰레드 제어
6
public class StoreActivity extends Activity …. {
private LocalDB localDB;
public void onMarkerTab(MapView mapView, Marker marker) {
StoreData storeData = localDB.select(marker.getMeta().getId());
if (storeData == null) {
alerts.showAlert("데이터가 없습니다.");
} else {
showBalloon(mapView, storeData);
}
}
private void showBallon(MapView mapView, StoreData storeData) {
BalloonInfoWindow infoWin = new BallonInfoWindow(this,storeData);
mapView.addFigure(infoWin);
…
}
데이터
조회
화면 흐름 제어
위젯
제어
막 만들다 보면
7
UI 조작
흐름 제어
로직
쓰레드
네트워크
DB
도메인
로직
단점
• 실행 흐름 파악이 어렵다.
– 흐름 제어 로직이 산재
• 부분적인 테스트가 어렵다.
– 위젯 조작 + UI 흐름 제어 + 데이터 관리 짬뽕
• 변경이 점점 어려워진다.
– 산재한 로직, 뒤섞인 코드  코드 변경 어려움
8
해결책
9
역할에 따라 알맞게 나누기
10
MVP
Model-View-Presenter
11
모델프리젠터뷰
• 비즈니스 로직과
어플리케이션
데이터
• 어플리케이션의
인터페이스
• 사용자에게는 뷰가 곧
어플리케이션
• 빈번하게 바뀌는 영역
• 모델과 뷰를 연결
• MVP에서 프리젠터는
기능/흐름을 제공
• 요구사항이 프리젠터의
기능에 대응
Model-View-Presenter
12
모델프리젠터뷰
• 비즈니스 로직과
어플리케이션
데이터
• 예, 인증
• 예, 특정 위치 기준
가계 정보
• 이벤트를 프리젠터에
전달
• 예, 로그인 버튼 클릭을
클릭하면 프리젠터에
인증 실행 요청
• 사용자에게 알맞은 화면
제공
• 예, 프리젠터를 통해
전달받은 데이터를
리스트와 같은 위젯을
이용해서 표현
• 로딩 중, 경고 대화창 등
안내
• 사용자 요청에 반응
• 예, 뷰를 통해 인증 요청을
받으면 모델을 사용해서
인증을 수행하고, 결과를
뷰에 전달
• 뷰의 흐름 제어
• 예, 인증 시작 전에 뷰를
통해 진행 중 대화창을
보여주고, 인증에 성공하면
뷰를 통해 메인 화면으로
이동
뷰
• 인터페이스
– 사용자에게 보여질
기능을 정의
– 프리젠터가 호출할 기능
• 예
– 데이터 출력 기능
– 진행 중 알림 기능
• 뷰 구현체
– 인터페이스에 정의한
기능의 구현 제공
– 사용자 이벤트를
프리젠터에 전달
• 예
– 데이터를 리스트로 표시
– 사용자가 항목을
클릭하면 프리젠터에
선택 요청 전달
13
뷰 구현
14
public interface StoreListView {
void showLoadingMessage();
void hideLoadingMessage();
void displayStores(stores);
void showFailMessage();
}
public class MapStoreListView implements StoreListView, … {
private StoreListPresenter presenter;
public void MapStoreListView(StoreListPresenter presenter) {
presenter.setView(this);
}
public void onMapViewDragEnded(
MapView mapView, MapPoint mapPoint) {
Coord center = toCoord(mapView.getCenter()))
presenter.onLocationChange(center);
}
public void showLoadingMessage() {
progress = ProgressDialog.show(this, "", "로딩중");
}
public void hideLoadingMessage() { progress.hide(); }
public void displayStores(List<Store> stores) {
…지도위에 마커 표시
}
프리젠터 정의
• 사용자의 UI 행위를 추상화한 메서드로
기능을 정의
– 뷰 구현에 의존하지 않음
15
onLocationChange: 상점을 조회할 위치 기준을 변경한다는 의미, O
뷰 구현 기술에 종속되지 않음
onMapDragged: 뷰의 지도를 드래그했다는 것을 의미, X
뷰 구현 기술에 종속됨
프리젠터 구현
• 모델과 뷰를 사용해서 사용자의 요청 처리
16
public class StoreListPresenter {
private StoreListView view;
private StoreListModel model;
public void onLocationChange(Coord location) {
view.showLoadingMessage();
try {
List<Store> stores = model.getStoresIn(location, 100);
view.hideLoadingMessage();
view.displayStores(stores);
} catch(Exception e) {
view.hideLoading();
view.showFailMessage();
}
}
UI 로직을
제어
모델로
비즈니스
로직 실행
전형적인 실행 흐름
17
MVP 적용 결과
• 뷰 구현 기술과 UI 로직(프리젠터)의 분리
– UI 로직 응집도 향상
18
MVP 적용 결과
• UI 구현 기술(뷰)과 UI 로직(프리젠터)의 분리
– UI 로직에 영향을 주지 않고 뷰 구현 변경 가능
19
public class MapStoreListView
implements StoreListView, … {
private StoreListPresenter presenter;
…
public void displayStores(List<Store> stores) {
listAdapter.clear();
listAdapter.addAll(stores);
}
public class MapStoreListView
implements StoreListView, … {
private StoreListPresenter presenter;
…
public void displayStores(List<Store> stores) {
map.clearAllMarks();
for(Store store : stores) {
map.addMarker(createMarker(store));
}
}
MVP 적용 결과
• UI 구현 기술(뷰), UI 로직(프리젠터), 도메인
로직(모델) 구현의 분리
– 뷰/모델 구현없이 UI 로직 테스트 가능
• 뷰를 구현하지 않아도 모의(mock) 객체로 UI 로직에
대한 테스트 가능
20
21
안드로이드와 MVP
기본 구조
22
Activity나 Fragment 역할
23
public class MapStoreListActivity implements StoreListView, … {
private StoreListPresenter presenter;
public void onCreate(Bundle savedInstanceState) {
StoreListModel model = new RestStoreListModel();
this.presenter = new StoreListPresenter(this, model);
this.presenter.init();
}
public void onMapViewDragEnded(MapView mapView, MapPoint mapPoint) {
Coord center = toCoord(mapView.getCenter()))
presenter.onLocationChange(center);
}
public void showLoadingMessage() {
progress = ProgressDialog.show(this, "", "로딩중");
}
public void hideLoadingMessage() {
progress.hide();
}
public void displayStores(List<Store> stores) {
…지도위에 마커 표시
}
뷰 구현
프리젠터/모델 객체를
생성하고 연결
안드로이드 앱에서 모델의 특징
• 외부 서버와 통신을 비동기로 처리
– 허니콤(3.0): Main(UI) 쓰레드에서 네트워크 금지
– AsyncTask나 Thread 사용
• 프리젠터에 비동기 결과 전달 방법 필요
– 주요 방법: 콜백(Callback), 옵저버(Observer) 등
24
모델과 콜백
25
public class RestStoreListModel
implements StoreListModel {
public void getStoresIn(Coord center,
int distance,
StoreListCallback callback) {
new Thread(new Runnable() {
public void run() {
try {
… network 처리, 데이터 변환
callback.onSuccess(results);
} catch(Exception ex) {
callback.onFail(ex);
}
}
}).start();
}
public interface StoreListCallback {
void onSuccess(List<Store> stores);
void onFail(Exception ex);
}
프리젠터는 콜백을 통해 결과 수신
26
public class StoreListPresenter {
public void onLocationChange(Coord coord) {
view.showProgressMessage();
model.getStoresIn(coord, 100,
new StoreListCallback() {
public void onSuccess(List<Store> stores) {
view.hideProgressMessage();
view.displayStores(stores);
}
public void onFail(Exception ex) {
view.hideProgressMessage();
view.showFailMessage(ex);
}
});
}
프리젠터와 라이프사이클
• Activity/Fragment의 라이프사이클에 맞춰
기능을 구현해야 할 경우, 프리젠터에
라이프사이클 관련 메서드 정의
27
public class StoreListPresenter {
public void onResume() { … }
public void onStop() { … }
…
}
public class StoreListActivity … {
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onStop() {
super.onStop();
presenter.onStop();
}
유용한 도구
• Dagger
– 의존 주입(DI)
– Activity에서 직접 프리젠터나 뷰를 생성하지
않도록 해 줌
• RxJava
– 옵저버
– 콜백 인터페이스 대체
28
29
자바스크립트
기본 구현
• 구성 요소는 동일
– 언어 특성상 인터페이스없이 구현
• 초기화
– HTML 로딩 시점에 뷰/모델/프리젠터 초기화
• 예, $.ready()에서 초기화
30
뷰 예시
31
functionMeasureListView(){
varpresenter;
functiononSelect(idx){ presenter.selectMeasure(idx); }
functioninit(){
resizeContent();
...
};
...
init();
return{
setPresenter:function(_presenter){ presenter=_presenter; },
showMeasures:function(measures){
if(measures.length==0){
$("#noData").show();
}else{
$("#hasData").show();
measureBox.setState({measures:measures,loading:false});
}
},
showLoadingError:function(){console.log("error"); }
};
}
프리젠터 예시
32
function MeasureListPresenter(view, model) {
this.reloadList = function() {
model.getMeasureList({
success: function(measures) {
view.showMeasures(measures);
},
error: function() {
view.showLoadingError();
}
});
};
this.selectMeasure = function(idx) {
var measure = model.getMeasure(idx);
view.showMeasure(measure);
};
}
모델 예시
33
functionMeasureListModel(){
varmeasures=[];
functiontoCoord(coordStr){...}
functionconvertToMeasure(feeds){...};
return{
getMeasureList:function(callback){
$.ajax({url:"https://api.mycompany.com/....", type:'GET',
success:function(data){
varmeasures=convertToMeasure(data.feeds);
callback.success(measures);
},
error:function(xhr,status,error){callback.error();}
});
},
getMeasure:function(idx){returnmeasures[idx];}
};
}
초기화 예
34
<script>
$(document).ready(function() {
var model = new MeasureListModel();
var view = new MeasureListView();
var presenter = new MeasureListPresenter(view, model);
view.setPresenter(presenter);
presenter.reloadList();
});
</script>
35
생각할 거리
뷰와 외부 입력
• 뷰의 역할: 외부 데이터 입력
– 기능 예
• 블루투스로 데이터를 받는 기능
• GPS로 현재 좌표 받는 기능
– 이 두 기능은 뷰를 통해 전달받는 사용자 이벤트
36
모델 영역의 구조
• 모델에 얼마나 많은 도메인 로직이 있나?
– 앱/웹에서 모델은 주로 다음의 두 가지 수행
• 서버와 통신
– 중요 로직은 서버에 존재
• 임시로 데이터 보관
– 로컬DB, 메모리 등에 보관
• 모델에 다양한 로직이 있다면 테스트 가능한
구조로 만드는 것이 중요
37
프리젠터
• 인터페이스와 구현체로 구분 필요 여부
– 코드 수준 테스트 대상은 주로 프리젠터와 모델
– 프리젠터가 유연해야 하는 경우 드뭄
– 모델 테스트시 프리젠터에 대한 모의 객체가
필요한 경우 없음
• 프리젠터 메서드 이름, 파라미터, 리턴 타입
– 뷰에 의존하면 안 됨
• 프리젠터 먼저 구현하기
38
39
정리
MVP
• 뷰 구현, UI 로직, 모델 구현을 분리
• 복잡도 감소
– 분리에 따른 복잡도 <<< 뒤섞일 때의 복잡도
• 생산성 향상시키기
– 코드 변경이 상대적으로 쉬워짐
– UI 구현없이 사용자와의 상호작용에 대한 테스트
가능
40
41
끝

MVP 패턴 소개

  • 1.
    MVP 패턴 소개(안드로이드) 신림프로그래머, 최범균, 2016-06-18 madvirus@madvirus.net
  • 2.
    다룰 내용 • 막만들면 • MVP 패턴 – 적용 결과 • MVP 패턴 적용 예 – 안드로이드 – 자바스크립트 • 정리 2
  • 3.
    간단한 앱, 막만들면… 3 드래그 탭 미림분식 별: 5
  • 4.
    4 public class StoreActivityextends Activity …. { private Handler handler = new Handler(); protected void onStart() { loadingProgress.show(); LoadListThread lThread = new LoadListThread(); lThread.start(); } private class LoadListThread extends Thread { public void run() { URL url = new URL(urlstr); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); ... handler.post(new Runnable() { public void run() { loadingProgress.hide(); if (loadingResult) { localDB.save(loadedData); drawMarkerForStores(loadedData); } else { toasts..short("에러!"); } } }); } } 기능 흐름 제어 데이터 보관 처리 UI 화면 제어 네트 워크 UI 처리 위한 쓰레드 화면 제어 쓰레드 제어
  • 5.
    5 public class StoreActivityextends Activity …. { private MapPoint center; public void onMapViewDragEnded( MapView mapView, MapPoint mapPoint) { center = mapPoint; loadingProgress.show(); LoadListThread lThread = new LoadListThread(); lThread.start(); } private class LoadListThread extends Thread { public void run() { URL url = new URL(urlstr); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); ... if (center != null) { params.put("center", toCoord(center)); } ... handler.post(new Runnable() { public void run() { loadingProgress.hide(); … } }); } } 사용자 이벤트 처리 UI 의존 데이터 화면 제어 + 쓰레드 제어
  • 6.
    6 public class StoreActivityextends Activity …. { private LocalDB localDB; public void onMarkerTab(MapView mapView, Marker marker) { StoreData storeData = localDB.select(marker.getMeta().getId()); if (storeData == null) { alerts.showAlert("데이터가 없습니다."); } else { showBalloon(mapView, storeData); } } private void showBallon(MapView mapView, StoreData storeData) { BalloonInfoWindow infoWin = new BallonInfoWindow(this,storeData); mapView.addFigure(infoWin); … } 데이터 조회 화면 흐름 제어 위젯 제어
  • 7.
    막 만들다 보면 7 UI조작 흐름 제어 로직 쓰레드 네트워크 DB 도메인 로직
  • 8.
    단점 • 실행 흐름파악이 어렵다. – 흐름 제어 로직이 산재 • 부분적인 테스트가 어렵다. – 위젯 조작 + UI 흐름 제어 + 데이터 관리 짬뽕 • 변경이 점점 어려워진다. – 산재한 로직, 뒤섞인 코드  코드 변경 어려움 8
  • 9.
  • 10.
  • 11.
    Model-View-Presenter 11 모델프리젠터뷰 • 비즈니스 로직과 어플리케이션 데이터 •어플리케이션의 인터페이스 • 사용자에게는 뷰가 곧 어플리케이션 • 빈번하게 바뀌는 영역 • 모델과 뷰를 연결 • MVP에서 프리젠터는 기능/흐름을 제공 • 요구사항이 프리젠터의 기능에 대응
  • 12.
    Model-View-Presenter 12 모델프리젠터뷰 • 비즈니스 로직과 어플리케이션 데이터 •예, 인증 • 예, 특정 위치 기준 가계 정보 • 이벤트를 프리젠터에 전달 • 예, 로그인 버튼 클릭을 클릭하면 프리젠터에 인증 실행 요청 • 사용자에게 알맞은 화면 제공 • 예, 프리젠터를 통해 전달받은 데이터를 리스트와 같은 위젯을 이용해서 표현 • 로딩 중, 경고 대화창 등 안내 • 사용자 요청에 반응 • 예, 뷰를 통해 인증 요청을 받으면 모델을 사용해서 인증을 수행하고, 결과를 뷰에 전달 • 뷰의 흐름 제어 • 예, 인증 시작 전에 뷰를 통해 진행 중 대화창을 보여주고, 인증에 성공하면 뷰를 통해 메인 화면으로 이동
  • 13.
    뷰 • 인터페이스 – 사용자에게보여질 기능을 정의 – 프리젠터가 호출할 기능 • 예 – 데이터 출력 기능 – 진행 중 알림 기능 • 뷰 구현체 – 인터페이스에 정의한 기능의 구현 제공 – 사용자 이벤트를 프리젠터에 전달 • 예 – 데이터를 리스트로 표시 – 사용자가 항목을 클릭하면 프리젠터에 선택 요청 전달 13
  • 14.
    뷰 구현 14 public interfaceStoreListView { void showLoadingMessage(); void hideLoadingMessage(); void displayStores(stores); void showFailMessage(); } public class MapStoreListView implements StoreListView, … { private StoreListPresenter presenter; public void MapStoreListView(StoreListPresenter presenter) { presenter.setView(this); } public void onMapViewDragEnded( MapView mapView, MapPoint mapPoint) { Coord center = toCoord(mapView.getCenter())) presenter.onLocationChange(center); } public void showLoadingMessage() { progress = ProgressDialog.show(this, "", "로딩중"); } public void hideLoadingMessage() { progress.hide(); } public void displayStores(List<Store> stores) { …지도위에 마커 표시 }
  • 15.
    프리젠터 정의 • 사용자의UI 행위를 추상화한 메서드로 기능을 정의 – 뷰 구현에 의존하지 않음 15 onLocationChange: 상점을 조회할 위치 기준을 변경한다는 의미, O 뷰 구현 기술에 종속되지 않음 onMapDragged: 뷰의 지도를 드래그했다는 것을 의미, X 뷰 구현 기술에 종속됨
  • 16.
    프리젠터 구현 • 모델과뷰를 사용해서 사용자의 요청 처리 16 public class StoreListPresenter { private StoreListView view; private StoreListModel model; public void onLocationChange(Coord location) { view.showLoadingMessage(); try { List<Store> stores = model.getStoresIn(location, 100); view.hideLoadingMessage(); view.displayStores(stores); } catch(Exception e) { view.hideLoading(); view.showFailMessage(); } } UI 로직을 제어 모델로 비즈니스 로직 실행
  • 17.
  • 18.
    MVP 적용 결과 •뷰 구현 기술과 UI 로직(프리젠터)의 분리 – UI 로직 응집도 향상 18
  • 19.
    MVP 적용 결과 •UI 구현 기술(뷰)과 UI 로직(프리젠터)의 분리 – UI 로직에 영향을 주지 않고 뷰 구현 변경 가능 19 public class MapStoreListView implements StoreListView, … { private StoreListPresenter presenter; … public void displayStores(List<Store> stores) { listAdapter.clear(); listAdapter.addAll(stores); } public class MapStoreListView implements StoreListView, … { private StoreListPresenter presenter; … public void displayStores(List<Store> stores) { map.clearAllMarks(); for(Store store : stores) { map.addMarker(createMarker(store)); } }
  • 20.
    MVP 적용 결과 •UI 구현 기술(뷰), UI 로직(프리젠터), 도메인 로직(모델) 구현의 분리 – 뷰/모델 구현없이 UI 로직 테스트 가능 • 뷰를 구현하지 않아도 모의(mock) 객체로 UI 로직에 대한 테스트 가능 20
  • 21.
  • 22.
  • 23.
    Activity나 Fragment 역할 23 publicclass MapStoreListActivity implements StoreListView, … { private StoreListPresenter presenter; public void onCreate(Bundle savedInstanceState) { StoreListModel model = new RestStoreListModel(); this.presenter = new StoreListPresenter(this, model); this.presenter.init(); } public void onMapViewDragEnded(MapView mapView, MapPoint mapPoint) { Coord center = toCoord(mapView.getCenter())) presenter.onLocationChange(center); } public void showLoadingMessage() { progress = ProgressDialog.show(this, "", "로딩중"); } public void hideLoadingMessage() { progress.hide(); } public void displayStores(List<Store> stores) { …지도위에 마커 표시 } 뷰 구현 프리젠터/모델 객체를 생성하고 연결
  • 24.
    안드로이드 앱에서 모델의특징 • 외부 서버와 통신을 비동기로 처리 – 허니콤(3.0): Main(UI) 쓰레드에서 네트워크 금지 – AsyncTask나 Thread 사용 • 프리젠터에 비동기 결과 전달 방법 필요 – 주요 방법: 콜백(Callback), 옵저버(Observer) 등 24
  • 25.
    모델과 콜백 25 public classRestStoreListModel implements StoreListModel { public void getStoresIn(Coord center, int distance, StoreListCallback callback) { new Thread(new Runnable() { public void run() { try { … network 처리, 데이터 변환 callback.onSuccess(results); } catch(Exception ex) { callback.onFail(ex); } } }).start(); } public interface StoreListCallback { void onSuccess(List<Store> stores); void onFail(Exception ex); }
  • 26.
    프리젠터는 콜백을 통해결과 수신 26 public class StoreListPresenter { public void onLocationChange(Coord coord) { view.showProgressMessage(); model.getStoresIn(coord, 100, new StoreListCallback() { public void onSuccess(List<Store> stores) { view.hideProgressMessage(); view.displayStores(stores); } public void onFail(Exception ex) { view.hideProgressMessage(); view.showFailMessage(ex); } }); }
  • 27.
    프리젠터와 라이프사이클 • Activity/Fragment의라이프사이클에 맞춰 기능을 구현해야 할 경우, 프리젠터에 라이프사이클 관련 메서드 정의 27 public class StoreListPresenter { public void onResume() { … } public void onStop() { … } … } public class StoreListActivity … { @Override protected void onResume() { super.onResume(); presenter.onResume(); } @Override protected void onStop() { super.onStop(); presenter.onStop(); }
  • 28.
    유용한 도구 • Dagger –의존 주입(DI) – Activity에서 직접 프리젠터나 뷰를 생성하지 않도록 해 줌 • RxJava – 옵저버 – 콜백 인터페이스 대체 28
  • 29.
  • 30.
    기본 구현 • 구성요소는 동일 – 언어 특성상 인터페이스없이 구현 • 초기화 – HTML 로딩 시점에 뷰/모델/프리젠터 초기화 • 예, $.ready()에서 초기화 30
  • 31.
    뷰 예시 31 functionMeasureListView(){ varpresenter; functiononSelect(idx){ presenter.selectMeasure(idx);} functioninit(){ resizeContent(); ... }; ... init(); return{ setPresenter:function(_presenter){ presenter=_presenter; }, showMeasures:function(measures){ if(measures.length==0){ $("#noData").show(); }else{ $("#hasData").show(); measureBox.setState({measures:measures,loading:false}); } }, showLoadingError:function(){console.log("error"); } }; }
  • 32.
    프리젠터 예시 32 function MeasureListPresenter(view,model) { this.reloadList = function() { model.getMeasureList({ success: function(measures) { view.showMeasures(measures); }, error: function() { view.showLoadingError(); } }); }; this.selectMeasure = function(idx) { var measure = model.getMeasure(idx); view.showMeasure(measure); }; }
  • 33.
  • 34.
    초기화 예 34 <script> $(document).ready(function() { varmodel = new MeasureListModel(); var view = new MeasureListView(); var presenter = new MeasureListPresenter(view, model); view.setPresenter(presenter); presenter.reloadList(); }); </script>
  • 35.
  • 36.
    뷰와 외부 입력 •뷰의 역할: 외부 데이터 입력 – 기능 예 • 블루투스로 데이터를 받는 기능 • GPS로 현재 좌표 받는 기능 – 이 두 기능은 뷰를 통해 전달받는 사용자 이벤트 36
  • 37.
    모델 영역의 구조 •모델에 얼마나 많은 도메인 로직이 있나? – 앱/웹에서 모델은 주로 다음의 두 가지 수행 • 서버와 통신 – 중요 로직은 서버에 존재 • 임시로 데이터 보관 – 로컬DB, 메모리 등에 보관 • 모델에 다양한 로직이 있다면 테스트 가능한 구조로 만드는 것이 중요 37
  • 38.
    프리젠터 • 인터페이스와 구현체로구분 필요 여부 – 코드 수준 테스트 대상은 주로 프리젠터와 모델 – 프리젠터가 유연해야 하는 경우 드뭄 – 모델 테스트시 프리젠터에 대한 모의 객체가 필요한 경우 없음 • 프리젠터 메서드 이름, 파라미터, 리턴 타입 – 뷰에 의존하면 안 됨 • 프리젠터 먼저 구현하기 38
  • 39.
  • 40.
    MVP • 뷰 구현,UI 로직, 모델 구현을 분리 • 복잡도 감소 – 분리에 따른 복잡도 <<< 뒤섞일 때의 복잡도 • 생산성 향상시키기 – 코드 변경이 상대적으로 쉬워짐 – UI 구현없이 사용자와의 상호작용에 대한 테스트 가능 40
  • 41.