[Hilt 들고 MVVM 정복] 1. Design Pattern

1

Hilt 들고 MVVM 정복

목록 보기
1/4

Intro.

안녕하세요 이번에 제가 Beering 이라는 프로젝트 팀에 중도합류하게 되어 리팩토링을 진행하게 되었습니다. 처음부터 개발하는 것이 아니라 기존에 개발하던 팀에 합류하여 리팩토링을 진행하는것은 저도 처음 해보는 경험이기 때문에 그 과정을 기록해보고자 이번 시리즈를 기획하게 되었습니다.

이 시리즈의 의의
사실 예전에 프로젝트를 했을 때 Hilt와 MVVM을 사용하긴 했었습니다만, 그냥 새로운 기술을 써보는것 자체에 의의를 두다보니 내가 왜 이 기술을 쓰고 있는지에 대해 무지한 상태로 무지성 개발을 했던것 같습니다.
그래서 이번기회에 리팩토링을 차근차근 진행하면서 어떤 점이 실제로 개선되는지, 내가 사용하는 기술이 왜 필요한건지 정리해보려고 합니다.

시리즈에서 리팩토링으로 다룰 코드

Beering의 회원가입 로직을 MVVM 아키텍처에 맞게 리팩토링할 예정입니다.
현재는 디자인 패턴이 적용되어 있지 않고 Activity에 대부분의 로직이 몰려있는 상태인데 ViewModel과 LiveData 를 시작으로 해서 차근차근 MVVM에 부합하는 아키텍처로 바꿔가는 과정을 다루겠습니다.
물론 이론적인 내용위주로 풀어나갈 거에요

현재 회원가입 로직을 보면 액티비티에 모든 로직이 몰려있는 상태입니다.

class JoinActivity: AppCompatActivity() {
	// Validation을 위한 각종 전역변수들
    // ...
    // ...
    
    // 회원가입을 위한 모든 로직을 onCreate에 구현
    override fun onCreate(savedInstanceState: Bundle?) {
		// 각종 버튼들에 대한 onClick Listener
        binding.checkboxTerm1On.setOnClickListener {
            binding.checkboxTerm1On.visibility = View.INVISIBLE
            binding.checkboxTerm1Off.visibility = View.VISIBLE
            checkbox1Bool = true
            val serviceAgreement = agreementList.find {it.name == "SERVICE"}
            serviceAgreement?.isAgreed = true
            validJoin()
        }
        // ...
        // ...
        
        
        // 각종 Edit Text들에 대한 Change Listener
        passwordEdit.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            	// 어쩌구 저쩌구
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
				// 저쩌구 어쩌구
            }
        // ...
        // ...    
        
    }
    
    // 비밀번호 유효성 검사 (각종 Validation check를 위한 메서드들)
    fun validatePassword(password: String): Boolean {
        // 비밀번호가 영문자를 포함하는지 확인
        val containsEnglishChars = password.matches(Regex(".*[a-zA-Z].*"))
        if (containsEnglishChars) {
            binding.conditionEng.setTextColor(
                ContextCompat.getColor(
                    this@JoinActivity,
                    R.color.beering_black
                )
            )
            binding.check1.setImageResource(R.drawable.ic_check_dark)
        } else {
            binding.conditionEng.setTextColor(
                ContextCompat.getColor(
                    this@JoinActivity,
                    R.color.gray01
                )
            )
            binding.check1.setImageResource(R.drawable.ic_check_light)
        }
}

이제 저 덩어리 코드를 MVVM 아키텍처로 차츰차츰 바꿔나가볼건데 그전에 먼저 안드로이드에서 접할 수 있는 디자인패턴들에 대해 얘기해보고 시작할까 합니다.

Design Pattern in Android

디자인 패턴이란 객체지향을 '잘' 설계하기 위해 재사용성을 올리고, 유지보수성을 향상시키며 설계의도를 명확하게 하기 위한 일종의 소프트웨어 아키텍처 컨벤션 이라고 볼 수 있습니다.

디자인 패턴이라는 개념 자체가 너무 광범위하고 실제로 그 종류도 수십가지에 달하기 때문에 모든 디자인패턴을 다 공부할 필요는 없다고 생각합니다.
그러나 안드로이드 개발자라면 적어도 안드로이드에서 사용하는 디자인패턴들은 알고있어야겠죠?

안드로이드에서는 크게 3가지 패턴들을 사용하고 있으며 등장 시간순으로 나열해보면 아래와 같습니다.

  • MVC 패턴
  • MVP 패턴
  • MVVM 패턴

MVC 패턴

안드로이드에서 가장 먼저 접하게 되는 디자인 패턴으로 아래와 같은 구조를 하고 있습니다.
핵심은 Model 부분을 분리하여 데이터를 Model에서 컨트롤하고 뷰에서는 이를 요청하여 받아 사용한다는 점입니다. 대표적으로 스프링 프레임워크에서 많이 사용하는 아키텍처입니다.

Model View Controller 의 약자로 각각 아래와 같은 역할을 맡습니다.

  • Model

데이터를 가지며 애플리케이션에서 사용되는 데이터를 보관하고 처리합니다.
View 또는 Control에 묶이지 않아 재사용 가능하며 View와 Controller는 Model을 통해 데이터를 참조해야 합니다.

  • View

사용자에게 보일 화면을 표현합니다.
앱 및 UI와의 상호작용에서 컨트롤러와 통신하며 유저가 어떤 입력(Action)을 하든 View는 무엇을 해야 할지 몰라야 합니다. (오직 화면 그 자체만 표현)

  • Controller

사용자로부터 입력을 받고 이 입력을 모델에 전달하거나 View 업데이트를 하게 됩니다.
모델의 데이터 변화에 따라 뷰를 적절히 선택하고 업데이트 시킵니다.

Controller가 Activity나 Fragment정도가 되고 View가 xml파일 혹은 Compose 파일 정도가 되겠네요. 그런데 안드로이드를 개발하다보면 이 둘이 강하게 결합되어 있음을 느낄 수 있습니다.
View와 Controller를 보통 바인딩시켜서 Controller가 View를 직접 제어하는 형태로 개발을 하다보니 둘의 역할을 정확하게 구분짓는 것이 모호해지죠.


(사진 출처 : https://velog.io/@jojo_devstory/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%ED%8C%A8%ED%84%B4-MVC%EA%B0%80-%EB%AD%98%EA%B9%8C)

이렇게 Control과 View를 묶는 것이 조금 더 이해하기 쉬워보이기도 합니다.
액티비티에서 UI를 처리하는 로직 외에 데이터를 가공하거나 서버에 요청하는 등의 작업을 Model로 분리시키기만 하면 MVC 아키텍처의 형태로 만들 수 있겠네요.

Model을 분리하여 단위테스트가 가능해지며, 가장 간단한 형태이고 대부분의 로직이 Activity에 집중되어 개발속도가 빠르다는 장점이 있지만 컨트롤러에 많은 로직이 쌓이게 되고 View가 Model을 직/간접적으로 참조하고 있어 의존성이 생긴다는 단점이 있습니다.

MVP 패턴

MVC 패턴에서 생긴 단점을 어느정도 해결하고자 나온 아키텍처이며 Model View Presenter의 약자입니다. 안드로이드에서는 View와 Controller가 강하게 결합되어 있다는 특징이 있다고 했습니다. 따라서 이들을 하나로 합쳐 View라고 칭하며 MVC의 Controller가 담당했던 역할을 Presenter라는 부분에서 담당합니다.

MVC 아키텍처와 얼추 비슷하게 생겼지만 View와 Model간의 의존성이 제거된 형태라고 볼 수 있습니다.
Presenter가 View와 Model 사이를 중개하고 있기 때문입니다.
중요한 점은 Presenter 자체는 interface로써 어떠한 로직도 구현되어 있지 않습니다. View에서 이를 상속하여 구현해주어야 합니다. (의존성이 생기는 단점이 존재한다는 의미가 되기도 합니다.)

이와 같이 MVP로 넘어오면서 Model과 View사이의 의존성은 사라졌지만 결국 Presenter와 View 사이에 1:1 의존성이 또 생겨버리는 단점이 생깁니다. 또한 MVC에서는 Controller에 로직이 몰리기 때문에 비대해질 수 있는단점이 있다고 했는데 MVP 역시 Presenter의 구현체가 많아질 수록 비대해지는 것은 마찬가지입니다.

MVVM 패턴

이번 시리즈에서 다룰 MVVM 패턴입니다. Model View ViewModel의 약자이며 각각 맡는 역할은 아래와 같습니다.

  • Model
    데이터베이스, 네트워크 호출, 파일 시스템, 외부 API와 같은 데이터 소스와 상호 작용하여 데이터를 가져오거나 저장하는 역할을 합니다.
    애플리케이션의 핵심 데이터를 정의하고 유지하는데 사용됩니다. 비즈니스 로직과 데이터 저장 및 처리에 관련된 작업을 처리합니다.
  • View
    UI의 시각적 요소를 표시합니다.
    화면의 레이아웃, 디자인, 사용자 인터랙션을 담당합니다.
    사용자 입력을 받고, ViewModel로부터 데이터를 표시하며, 사용자와 애플리케이션의 상호 작용을 담당합니다.

  • ViewModel
    뷰모델은 뷰에 표시할 데이터를 준비하고, 들고있습니다.
    뷰에서 발생한 이벤트 또는 사용자 입력을 처리하여 데이터를 변경합니다.
    뷰에 표시할 데이터를 모델로부터 가져와 포맷팅하거나 가공하여 뷰에 알리고, 뷰에서 발생한 변경 사항을 모델에 반영합니다.

대충보면 MVP랑 비슷하게 생긴것 같습니다. 그런데 차이점은 MVP에서 Presenter가 View를 직접 변경하는것과 달리 ViewModel은 View를 직접 변경하지 않는 다는 점입니다. 점선으로 표시된 Notify State라는 표시는 직접 View에 관여하는 것이 아니라 데이터나 상태의 변경만 알리면 View가 알아서 UI를 업데이트 하는 것을 의미합니다.

안드로이드에서는 이를 ViewModel 클래스와 observable 상태를 통해 구현합니다.

요약해보자면

  • ViewModel이 어플리케이션의 비즈니스로직에 필요한 모든 데이터를 들고 있고 View는 ViewModel의 데이터들을 관찰하고 있다가 변화가 일어나면 해당 데이터에 맞게 UI를 업데이트 합니다.
  • 또한 UserAction이 View를 통해 들어오면 View는 ViewModel에게 데이터의 업데이트를 요청하고 ViewModel은 내부의 비즈니스 로직에 따라 적절히 데이터를 업데이트 하게 됩니다.
  • 그러면 다시 변경된 데이터에 의해 View가 업데이트 됩니다.
  • 만약 ViewModel에서 데이터를 서버나 DataSource에 요청해야 한다면 Model을 통해 해당 데이터를 셋팅합니다.
  • Model은 애플리케이션에서 사용되는 데이터 형식입니다.
    (예를들면 Data class)

요약해보면

MVC 패턴을 통해 Model을 분리하여 데이터를 가공하여 처리하고, 표현하는 비즈니스 로직을 따로 관리하기 시작하였습니다.

그러나 안드로이드의 특성상 View와 Controller의 경계가 모호하였고 결국 이 둘의 개념은 MVP 패턴이 등장하면서 View라는 개념으로 합쳐집니다. 대신 Presenter라는 새로운 개념이 등장하여 Model과 View의 의존성을 완전히 분리시키며 둘 사이의 징검다리 역할로써 동작하게 됩니다.

그러나 MVP 역시 Presenter가 직접 Model과 View를 제어하다보니 Presenter에 과도한 비즈니스 로직이 집중되는 문제점이 있었고 MVVM 패턴이 등장하여 ViewModel을 View가 관찰하여 스스로 업데이트 하도록 함으로써 해결하게 됩니다.

최종적으로 MVVM아키텍처로 설계함으로써 ViewModel은 데이터를 요청 및 가공하여 들고있고, Model은 데이터를 표현하고, View는 UI를 업데이트 하는 로직을 담당하여 적절한 역할의 분배가 이루어지게 됩니다.

안드로이드에서 여러가지 디자인 패턴이 등장한 배경과 각각의 장단점에 대해 알아보았는데, 더 자세한 구현방법에 대한 내용은 차차 포스팅 해나가도록 하겠습니다.

profile
안녕하세요, 인공지능에 가치를 느끼는 안드로이드 개발자 입니다.

0개의 댓글