-
[Android] MVC vs MVP vs MVVM디자인패턴 2017. 8. 1. 14:21반응형
MVC, MVP, MVVM등 Android Application을 논리적인 component로 작성하는데 기여를 한 Design pattern들을 비교하도록 하겠다.
각각의 공통점은 아래와 같다.
1) Android Application을 크게 3가지 책임으로 분류한다.
2) Model과 View의 dependency를 제거함으로써 독립적인 Module로 사용할 수 있도록, testable하게 코드를 작성할 수 있도록 한다.
MVC
M : Model은 Data, Data의 State 변화를 담고있고, Business logic을 관리하는 역할이다. Application의 머리부분이며 View와 Controller에 종속적이지 않고 많은 context에서 재사용될 수 있다.
V : View는 Model을 사용자의 눈에 보여주는(Representation) 역할이다. 사용자에게 Model에 대한 정보를 보여주고, 사용자가 Application을 동작했을 때 Controller에 알려주는 역할을 한다. 중요한 것은 View는 아는 것이 없고 멍청(dumb)해야한다. 어떤 Model을 표시해야할 지, 사용자가 View를 Click했을 때 어떤 상태여야하는지 몰라야한다. 그래야 Model에 대한 coupling을 끊고 더 유연하게 동작할 수 있게 된다.
C : Controller는 접착제(Glue)같은 역할이다. View가 Controller에게 사용자가 버튼을 클릭했다고 말하면 Model에 어떻게 요청할지 결정한다. Model안에 Data 변화가 변하면 View의 상태를 바꿀지 결정한다. Android Application에서는 Activity나 Fragment가 이 역할을 한다.
public class TicTacToeActivity extends AppCompatActivity { private Board model; /* View Components referenced by the controller */ private ViewGroup buttonGrid; private View winnerPlayerViewGroup; private TextView winnerPlayerLabel; /** * In onCreate of the Activity we lookup & retain references to view components * and instantiate the model. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tictactoe); winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel); winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup); buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid); model = new Board(); } /** * Here we inflate and attach our reset button in the menu. */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_tictactoe, menu); return true; } /** * We tie the reset() action to the reset tap event. */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_reset: reset(); return true; default: return super.onOptionsItemSelected(item); } } /** * When the view tells us a cell is clicked in the tic tac toe board, * this method will fire. We update the model and then interrogate it's state * to decide how to proceed. If X or O won with this move, update the view * to display this and otherwise mark the cell that was clicked. */ public void onCellClicked(View v) { Button button = (Button) v; int row = Integer.valueOf(tag.substring(0,1)); int col = Integer.valueOf(tag.substring(1,2)); Player playerThatMoved = model.mark(row, col); if(playerThatMoved != null) { button.setText(playerThatMoved.toString()); if (model.getWinner() != null) { winnerPlayerLabel.setText(playerThatMoved.toString()); winnerPlayerViewGroup.setVisibility(View.VISIBLE); } } } /** * On reset, we clear the winner label and hide it, then clear out each button. * We also tell the model to reset (restart) it's state. */ private void reset() { winnerPlayerViewGroup.setVisibility(View.GONE); winnerPlayerLabel.setText(""); model.restart(); for( int i = 0; i < buttonGrid.getChildCount(); i++ ) { ((Button) buttonGrid.getChildAt(i)).setText(""); } } }
MVP
MVP는 View와 Controller역할을 했던 Activity와 View간의 coupling을 끊을 수 있다.
M : MVC의 M과 동일한 역할이다.
V : Activity/Fragment를 View의 역할을 하도록 생각할 수 있다. 가장 좋은 방법으로는 View가 interface를 구현하도록 함으로써 Presenter는 이 interface만 가지고 있도록 하는 것이다. Android Framework의 dependency를 끊고 간단한 unit test를 작성할 수 있다.
P : Interface를 가지고 있게 함으로써 어떤 View와도 dependency를 끊을 수 있다. 따라서 MVC에서 고려하지 못했던 모듈화, 간단한 테스트를 걱정하지 않을 수 있다!
위의 이론대로 작성을 해보면, 아래 코드처럼 Presenter에 Activity나 View를 전체를 넘기지 않아도 된다.
public class TicTacToePresenter implements Presenter { private TicTacToeView view; private Board model; public TicTacToePresenter(TicTacToeView view) { this.view = view; this.model = new Board(); } // Here we implement delegate methods for the standard Android Activity Lifecycle. // These methods are defined in the Presenter interface that we are implementing. public void onCreate() { model = new Board(); } public void onPause() { } public void onResume() { } public void onDestroy() { } /** * When the user selects a cell, our presenter only hears about * what was (row, col) pressed, it's up to the view now to determine that from * the Button that was pressed. */ public void onButtonSelected(int row, int col) { Player playerThatMoved = model.mark(row, col); if(playerThatMoved != null) { view.setButtonText(row, col, playerThatMoved.toString()); if (model.getWinner() != null) { view.showWinner(playerThatMoved.toString()); } } } /** * When we need to reset, we just dictate what to do. */ public void onResetSelected() { view.clearWinnerDisplay(); view.clearButtons(); model.restart(); } }
Test를 할 때에도 Interface의 mock 데이터만 작성하면 된다. 개인적인 궁금증이었던 Presenter나 View에 interface를 implement하는 이유도 해결되었다.
public interface TicTacToeView { void showWinner(String winningPlayerDisplayLabel); void clearWinnerDisplay(); void clearButtons(); void setButtonText(int row, int col, String text); }
MVVM
MVVM은 Data binding on Android 로 작성한다. MVP와 마찬가지로 모듈화, 간단한 테스트에 대한 걱정을 해결할 수 있다. 즉, View와 Model을 연결해야하는 많은 양의 코드를 제거할 수 있다.
M : 다른 패턴들의 Model과 동일하다.
V : View는 observable한 variables과 actions에 의존한다. 이는 유연하다!
VM : View에 대한 의존성이 없다. 다만, View가 알아야하는 Model을 observable variables과 actions으로 제공한다.
위의 이론대로 코드를 작성해보면, VM은 아래와 같이 작성할 수 있다.
public class TicTacToeViewModel implements ViewModel { private Board model; /* * These are observable variables that the viewModel will update as appropriate * The view components are bound directly to these objects and react to changes * immediately, without the ViewModel needing to tell it to do so. They don't * have to be public, they could be private with a public getter method too. */ public final ObservableArrayMap<String, String> cells = new ObservableArrayMap<>(); public final ObservableField<String> winner = new ObservableField<>(); public TicTacToeViewModel() { model = new Board(); } // As with presenter, we implement standard lifecycle methods from the view // in case we need to do anything with our model during those events. public void onCreate() { } public void onPause() { } public void onResume() { } public void onDestroy() { } /** * An Action, callable by the view. This action will pass a message to the model * for the cell clicked and then update the observable fields with the current * model state. */ public void onClickedCellAt(int row, int col) { Player playerThatMoved = model.mark(row, col); cells.put("" + row + col, playerThatMoved == null ? null : playerThatMoved.toString()); winner.set(model.getWinner() == null ? null : model.getWinner().toString()); } /** * An Action, callable by the view. This action will pass a message to the model * to restart and then clear the observable data in this ViewModel. */ public void onResetSelected() { model.restart(); winner.set(null); cells.clear(); } }
<!-- With Data Binding, the root element is <layout>. It contains 2 things. 1. <data> - We define variables to which we wish to use in our binding expressions and import any other classes we may need for reference, like android.view.View. 2. <root layout> - This is the visual root layout of our view. This is the root xml tag in the MVC and MVP view examples. --> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- We will reference the TicTacToeViewModel by the name viewModel as we have defined it here. --> <data> <import type="android.view.View" /> <variable name="viewModel" type="com.acme.tictactoe.viewmodel.TicTacToeViewModel" /> </data> <LinearLayout...> <GridLayout...> <!-- onClick of any cell in the board, the button clicked will invoke the onClickedCellAt method with its row,col --> <!-- The display value comes from the ObservableArrayMap defined in the ViewModel --> <Button style="@style/tictactoebutton" android:onClick="@{() -> viewModel.onClickedCellAt(0,0)}" android:text='@{viewModel.cells["00"]}' /> ... <Button style="@style/tictactoebutton" android:onClick="@{() -> viewModel.onClickedCellAt(2,2)}" android:text='@{viewModel.cells["22"]}' /> </GridLayout> <!-- The visibility of the winner view group is based on whether or not the winner value is null. Caution should be used not to add presentation logic into the view. However, for this case it makes sense to just set visibility accordingly. It would be odd for the view to render this section if the value for winner were empty. --> <LinearLayout... android:visibility="@{viewModel.winner != null ? View.VISIBLE : View.GONE}" tools:visibility="visible"> <!-- The value of the winner label is bound to the viewModel.winner and reacts if that value changes --> <TextView ... android:text="@{viewModel.winner}" tools:text="X" /> ... </LinearLayout> </LinearLayout> </layout>
MVP와 MVVM 의 좀 더 자세한 차이는 아래 링크를 참고!
1) Android API 의존성 차이. VM은 Android API에 의존적임. Presenter는 Android API에 의존적이지 않음.
MVVM is a variation of Martin Fowler's Presentation Model design pattern.[2][3] MVVM abstracts a view's state and behavior in the same way,[3] but a Presentation Model abstracts a view (creates a view model) in a manner not dependent on a specific user-interface platform.
MVVM and Presentation Model both derive from the model–view–controller pattern (MVC).
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel
2) VM은 V를 몰라도 Model 제공 가능, Presenter는 Model을 제공할 때 V를 알긴 암.
MVP : The View and the Presenter are completely separated, unlike View and Controller, from each other and communicate to each other by an interface.
MVVM : manipulating the Model as the result of actions on the View, and triggering the events in the View itself.
https://www.linkedin.com/pulse/understanding-difference-between-mvc-mvp-mvvm-design-rishabh-software
결론
위의 패턴들은 결국 C, P, VM과 같은 중간 매개자들이 사용자에게 보여줘야하는 V와 비지니스 모델인 M을 어떻게 연결 시키느냐에서 차이가 발생한다. M을 V에 제공할 때, 아래와 같이 정리할 수 있다.
1) MVC - C는 V에 의존적인 형태로 제공
2) MVP - P는 V의 interface만 알고 있어 이 interface를 구현한 View에게 listener 형태로 제공
3) MVVM - VM은 V를 모르지만 V에 필요한 Model을 observable한 형태로 제공
https://news.realm.io/news/eric-maxwell-mvc-mvp-and-mvvm-on-android/
반응형'디자인패턴' 카테고리의 다른 글
RecyclerView in MVP — Passive view’s approach -2 (0) 2017.07.26 RecyclerView in MVP - Passive view's approach -1 (0) 2017.07.26