ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] MVC VS MVP VS MVVM (+ SVC)
    Mhwan's Study/Architecture & Design Pattern 2021. 8. 12. 18:13

    아키텍쳐 디자인 패턴을 공부하면서 플랫폼마다 조금씩 패턴이 다르다는 것을 느꼈고, 안드로이드에 맞는 MVC, MVP, MVVM은 무엇일까 고민을 했습니다. 이 글을 보시기 전에 기본적인 MVC, MVP, MVVM을 찾아보시고 읽으시면 더 도움이 될 것입니다.

     

    이는 아래의 링크를 바탕으로 작성한 게시글입니다.
    https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

     

    안드로이드의 MVC, MVP, MVVM 종합 안내서

    MVC vs. MVP vs. MVVM 안드로이드 앱을 논리적 구성 요소로 체계화하려는 베스트 프랙티스 접근법은 최근 몇 년간 지속적으로 발전했습니다. 모델 뷰 컨트롤러(Model View Controller, MVC) 패턴을 초석으로

    academy.realm.io

     

    위 게시글의 예시인 Tic-Tac-Toe 앱을 그대로 갖고와 설명한 포스팅입니다.

    Model : Tic-Tac-Toe앱의 데이터 + 상태 + 비즈니스 로직입니다. (https://github.com/ericmaxwell2003/ticTacToe/tree/mvc/app/src/main/java/com/acme/tictactoe/model)

    위 코드를 보시면 아시겠지만 Board라는 클래스에 비즈니스 로직이 담겨 있으며, 각 패턴에서 Model은 모두 동일하게 사용됩니다.

     

    # MVC

    View & Controller : 어떻게 보여줄지를 결정하는 UI인 View 레이어와 View와 Model사이의 처리와 통신을 담당하는 Controller는 하나의 Activity로 묶여있습니다. MVC에서 Controller의 역할인 사용자의 입력이 Activity로 들어오고, View를 그려주는 것도 Activity가 모두 담당하기 때문입니다. (Fragment가 될 수도 있습니다.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    public class TicTacToeActivity extends AppCompatActivity {
     
        private static String TAG = TicTacToeActivity.class.getName();
     
        private Board model;
     
        private ViewGroup buttonGrid;
        private View winnerPlayerViewGroup;
        private TextView winnerPlayerLabel;
     
        @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();
        }
     
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.menu_tictactoe, menu);
            return true;
        }
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.action_reset:
                    reset();
                    return true;
                default:
                    return super.onOptionsItemSelected(item);
            }
        }
     
        public void onCellClicked(View v) {
     
            Button button = (Button) v;
     
            String tag = button.getTag().toString();
            int row = Integer.valueOf(tag.substring(0,1));
            int col = Integer.valueOf(tag.substring(1,2));
            Log.i(TAG, "Click Row: [" + row + "," + col + "]");
     
            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);
                }
            }
     
        }
     
        private void reset() {
            winnerPlayerViewGroup.setVisibility(View.GONE);
            winnerPlayerLabel.setText("");
     
            model.restart();
     
            forint i = 0; i < buttonGrid.getChildCount(); i++ ) {
                ((Button) buttonGrid.getChildAt(i)).setText("");
            }
        }
     
    }
    cs

    보시다시피 Activity에서 View와 Controller의 역할을 모두합니다.

    단점 : MVC패턴은 Acitivty에서 모든 걸 다 넣어버리는 참사가 일어나고, 이는 나중에 기능이 추가될 때마다 Activity가 더 커지고, 복잡해져서 유지보수의 유연성도 굉장히 안좋아지며(심지어 Activity가 Model을 직접 참조하고 있습니다.), Controller가 Android의 Context와 생명주기에 연관이 되어 있어 따로 단위테스트를 수행하지 못합니다.

     

    # MVP

    View : MVP에서의 View는 사용자 입력이 View레이어에 들어오기 때문에, Activity는 완전히 View로 간주됩니다.

    Presenter : MVC의 Controller와 달리 View와 Model을 이어주는 매개체 역할만 수행합니다.

    참고 : https://github.com/ericmaxwell2003/ticTacToe/tree/mvp/app/src/main/java/com/acme/tictactoe

    Activity와 Presenter가 직접 의존하지 않고, 서로의 추상화된 interface와 의존함으로써 상호작용을 합니다. View는 Presenter에 의해서만 업데이트를 수행하는 수동적인 형태로 바뀌었습니다. 또한 MVC패턴의 유연성 문제와 테스트 문제를 해결하게 됩니다. (Presenter는 인터페이스를 통해 View와 의존관계를 형성하므로 View의 mock객체를 생성해 테스트를 수행할 수 있게 됩니다.)

    단점 : View와 Presenter가 1:1관계를 가지는 문제가 있습니다. 이 때문에 앱이 복잡해질수록 View와 Presenter의 의존관계가 강해지고,  View가 많아지면 그에 따라 Presenter도 많아집니다. 특히 이 TicTacToe를 보여주는 다른 View가 새로 생기게 된다면 이를 위해 중복의 Presenter가 생기고, 코드 중복이 발생할 수 밖에 없습니다.

     

    # MVVM

    ViewModel : Presenter가 사라지고 ViewModel이 생겼습니다. ViewModel은 View를 위한 Model로 Model을 래핑하며, 실제 데이터는 Observable 데이터(LiveData, StateFlow 등)를 통해 ViewModel이 View를 알지 않아도 해당 데이터가 업데이트 되면 View가 통지받을 수 있게 합니다.

    참고 : https://github.com/ericmaxwell2003/ticTacToe/tree/mvvm/app/src/main/java/com/acme/tictactoe

    물론 위에 링크는 Android AAC가 만들어지기 이전의 코드이다보니 현재 Android에서 권장하는 ViewModel을 상속 받거나, LiveData를 사용하지 않습니다.

    여기서 가장 큰 의의는 바로 View와 ViewModel의 의존성이 줄어들었다는 것입니다. 먼저 View는 보여줄 Model 데이터가 여러개라면 그에 맞는 ViewModel을 각각 만들어 1:N관계를 형성할 수도, 동일한 로직을 여러개의 View에 보여줄때는 View와 ViewModel의 관계가 N:1 관계도 가능하게 됩니다. 

    또한 View와 ViewModel의 관계는 DataBinding을 통해 의존성을 줄일 수 있게 됩니다. DataBinding을 통해 Activity라는 클래스에 직접 View를 끌고와 데이터를 할당하지 않아도 되고, 반대로 ViewModel의 데이터 변화가 해당 View의 변화가 될 수 있도록 할 수 있습니다. (2-Way DataBinding)

    + Dagger-Hilt같은 DI를 사용하게 된다면 둘의 의존관계를 더욱 줄일 수 있습니다.

    MVP 패턴 대신 MVVM패턴을 사용하는 이유가 View와 Presenter(ViewModel)의 분리에 있는 만큼 데이터 바인딩 만큼은 반드시 사용하는게 좋다고 생각합니다.

     

    * DataBinding?

    내부적으로 DataBinding이 어떻게 동작하는지 잠깐 얘기해보겠습니다. 예를들어 데이터 바인딩을 통해 XML에 만든 EditText의 변화가 ViewModel의 LiveData<String>의 변화를 야기할 수 있습니다. (2-Way이면 반대도 가능) Layout XML을 작성하게 되면 ActivityMainBindingImpl같은 클래스가 자동으로 생성되게 됩니다. ViewModel을 등록할때 EditText의 TextWatcher를 등록하게 되고, EditText의 변화를 감지한 TextWatcher에서 InverseBindingListener를 호출해 EditText의 Text값을 LiveData에 setValue해주는 방식으로 동작하게 됩니다. 즉, 내부적으로 만들어지는 BindingAdapter가 View와 ViewModel 사이에서 동작하는 방식이라 생각하면 좋을 것 같습니다. 

    더 자세한 내용은 https://improve777.medium.com/데이터바인딩-two-way-binding-원리-4317396728ff를 참고하세요.

     

    * 왜 안드로이드에서 MVVM을 밀고 있을까?

    한때는 MVP패턴이 대세였던 시절이 있던 것으로 기억합니다. 하지만 앱의 크기가 점점 커지고, 유지보수의 중요성이 그만큼 대두되면서 MVP패턴보다 더 확장과 유지보수에 유연한 MVVM패턴이 대세가 되지 않았나 생각합니다. (ViewModel이 Model이라는 DataSource를 직접 의존하는게 아니라 Repository를 통해 의존하게끔 하는걸 보면 알 수 있습니다.)MVVM이 MVP보다 가지는 단점은 아마 구조가 더 복잡해진다는 점일텐데 이를 위해 안드로이드에서 내놓는 JetPack의 LiveData라던지, ViewModel이라던지, Dagger-Hilt, Flow 등등의 API를 통해 개발자가 직접 코딩해야하는 부담을 줄여주고 있습니다.

     

    # SVC

    위 패턴을 모두 보면 안드로이드에 Activity와 Fragment를 View라고 칭합니다. 하지만 실제 Activity와 Fragment는 그 역할이 너무 크다는 단점이 있고, Activity와 Fragment 안에서의 역할을 나누기 위한 패턴입니다.

    - Screen 

    Screen은 Views와 Control Tower를 포함합니다.

    Activity와 Fragment의 생명주기 관련 함수들 (onCreate, onStart, onDestroy 등등), 화면 전환 로직, 화면전환에서 데이터를 저장, 복원하는 로직, 다른 액티비티로 이동하고, 그 결과를 받는 로직등등

    이와 같이 전체 Activity, Fragment와 관련된 로직이 있습니다.

     

    - Views

    Activity에 있는 View들의 집합으로, setContentView()에서 리턴되는 View들의 집합입니다. 이는 각각의 View들을 컨트롤하고, 각 View에서 이벤트가 발생한 경우 Control Tower로 보내는 역할을 합니다.

     

    - Control Tower

    Activity내에서 발생하는 모든 이벤트 변화를 받고, 어떻게 처리할지 결정합니다. SVC패턴과 MVVM패턴을 함께 사용한다면 ViewModel을 통해 데이터를 가져오고 갱신할 수도 있습니다.

    액티비티안에서 발생하는 모든 이벤트를 생각해보면 액티비티의 라이프 사이클 변화, View들의 이벤트 (사용자와 상호작용하는 이벤트들), 각종 센서의 변화 등등.. 많은 것들이 있습니다. 즉 이들의 변화에 따라 어떻게 데이터를 처리할지 결정하는게 Control Tower의 역할입니다.

     

    SVC는 Activity, Fragment의 역할을 덜어주기 위한 것으로 MVVM패턴과 MVP패턴과 함께 사용할 수 있습니다. 특히 MVVM패턴과 함께 사용한다면 저는 아래와 같은 로직이 가장 적합하다고 생각합니다.

    자세한 내용은 https://medium.com/@bansooknam/svc-패턴의-소개와-가이드-7a112c1e3f86를 참고하세요!

    'Mhwan's Study > Architecture & Design Pattern' 카테고리의 다른 글

    MVC VS MVP VS MVVM  (0) 2021.08.04
    객체지향 프로그래밍 & 특징  (0) 2021.07.29

    댓글

Designed by Mhwan.