MVC와 MVP의 비교 분석

쓰리원·2022년 11월 3일
0

android architecture

목록 보기
1/3
post-thumbnail

위 글을 이해하기 위해 필요한 사전 지식

1. 의존성이란?
2. 객체 지향 SOLID란?

1. 안드로이드 디자인 (아키텍처) 패턴이란?

안드로이드에서는 View 와 Data를 관리하는 여러가지 디자인 패턴이 존재합니다. 그 디자인패턴에 대하여 가장 단순한 디자인패턴부터 시작해서 현재 현업에서 많이 쓰이는 디자인패턴까지 총 3가지에 대해서만 알아보도록 하겠습니다. MVVM의 경우는 다른글에서 따로 다루도록 하겠습니다.

1. MVC (Model - View - Controller)
2. MVP (Model - View - Presenter)
3. MVVM (Model - View - ViewModel)

위의 아키텍처를 보시면 View와 Model이 존재합니다. 앱을 제작하는데 있어서 사용자에게 보여주기위한 UI(View)와 그리고 View를 이루는 Data(Model)는 항상 존재하기 때문입니다. 그러므로 View와 Model사이의 의존성이 생길 수 있다고 유추 할 수 있습니다.

  • View : 사용자에게 제공되어 보이는 UI 부분이라고 볼 수 있습니다. 앱의 화면은 이 View에 해당합니다
  • Model : 애플리케이션에서 사용되는 데이터 입니다. 데이터 조작 로직을 처리하는 부분으로 사용되기도 합니다.

2. MVC와 MVP 비교

위 그림을 보면 MVC와 MVP의 차이점 두가지를 발견할 수 있습니다. 첫번째로 Contoller 가 Presenter 로 바뀌었다는 것이고, 두번째로 Model이 View에 직접적으로 관여를 하냐 안하냐의 차이입니다. 이것이 무슨 소리인지 아래에서 차근히 설명하도록 하겠습니다.

3. MVC 란?

  • MVC (Model - View - Controller)

Model과 View에 대한 설명은 위의 설명과 동일하기 때문에 생략하겠습니다. MVC의 경우는 Controller라는 것이 있는데 이것이 무엇인지에 대해 설명하겠습니다.

  • Controller 란?
  1. 안드로이드에서 Activity나 Fragment는 MVC 패턴에서 Controller의 역할과 View의 역할을 동시에 합니다. 그래서 일반적인 MVC 패턴처럼 View 와 Controller를 나눠서 안드로이드에 정확하게 적용하기에는 애매할 수 있습니다.

  2. Activity나 Fragment는 Controller 로써 Model의 데이터 변화에 따라 알맞은 View를 업데이트 해줍니다. 그래서 하나의 Controller (Activity나 Fragment)는 여러 개의 View (버튼, 텍스트뷰 등)를 포함하고 관리합니다. 이러한 구조 때문에 "1:n" 구조가 됩니다.

  3. 이후에 다루게 될 MVP 의 이야기를 간략히 하자면, 안드로이드에서 Activity와 Fragment가 Controller와 View의 역할을 둘 다 수행하는 부분에서 Controller의 역할 분리하고, Activity와 Fragment에 직접적으로 인스턴스화 되었던 Model을 Presenter에서 관리하게 되는 것이 MVP 라고 할 수 있습니다.

  • MVC 패턴 장점
  1. 앱을 구현하는데 가장 단순한 패턴이라서 초보자들이 기능 하나씩 구현을 연습 해보기에 적합한 구조입니다.

  2. 추가 클래스 생성이 적어 구조가 단순하기 때문에 빠르게 앱을 만드는 것에 유리합니다.

  • MVC 패턴 단점
  1. Controller가 View의 제어와 비즈니스 로직의 제어 역할을 같이 하기 때문에 케이스별 테스트가 어려워집니다.

  2. Controller에 코드가 전부 작성 되어 Activity와 Fragment가 비대해집니다.

4. MVC 패턴 코드 예제

1. User.kt

data class User(
    var emailId: String?,
    var nickname: String?,
    var password: String?
) {
    fun signIn(userEmailId: String?, userNickname: String?, userPassword: String?): Boolean {

       if (DbName == userEmailId && DbPassword == userPassword) {
       		this.emailId = userEmailId
            this.nickname = userNickname
            this.password = userPassword
            return true
        }
        return false
    }

    fun signOut() { }
    
    // 임시 DB 역할
    companion object {
        const val DbName = "user@naver.com"
        const val DbPassword = "test"
    }
}

원래 같으면 userEmailId 와 userPassword를 파라미터로 받아서 서버와의 api 통신을 통해서 true 나 false를 반환해야 하지만 companion object로 클라이언트에서 체크를 하여 간소화해서 작성을 하였습니다.

2. SignInActivity.kt

class SignInActivity : AppCompatActivity() {

    private val binding: ActivitySignInBinding by lazy {
        ActivitySignInBinding.inflate(layoutInflater)
    }
    
    private lateinit var user : User

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        user = User()

        binding.signInBtn.setOnClickListener {
        
            val isSignInSuccessful = user.signIn(
                binding.emailId.text.toString(),
                binding.nickname.text.toString(),
                binding.password.text.toString(),
            )

            if (isSignInSuccessful) {
                Toast.makeText(this, "signIn Successful", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "signIn Failed", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Controller는 View와 Model 사이의 상호작용을 책임지기 때문에, 외부에서 전달받은 입력을 처리해서 알맞은 View의 렌더링을 요청하게 됩니다. 여기서 Activity가 View의 표시와 Controller의 역할을 같이 수행하는 것을 확인할 수 있습니다. 따라서 모든 코드가 집중이 되기 때문에 Activity가 비대해질 것을 예측할 수 있습니다.

5. MVP 란?

  • MVP (Model - View - Presenter)

Model과 View에 대한 설명은 위의 설명과 동일하기 때문에 생략하겠습니다. MVP의 경우는 Presenter 것이 있는데 이것이 무엇인지에 대해 설명하겠습니다.

  • Presenter
  1. Presenter란 인터페이스를 통해서 View에서 요청한 정보를 받아 Model로 부터 가공하여 View로 전달하는 부분입니다. 즉, View와 Model의 사이를 이어주는 역할을 합니다. Presenter를 통해서만 데이터를 전달받기 때문에 View와 Model의 의존성이 없습니다.

  2. 위의 Presenter를 통해서 모든 코드가 Controller에 다 집중이 되는 MVC 패턴의 문제를 해결할 수 있습니다. 그러면 위와 같은 문제가 어떻게 해결이 되어지는지 아래의 예제를 통해서 확인해보겠습니다.

  3. Presenter와 View는 1:1 관계입니다.

  4. 앞서 이야기한 첫번째로 Controller 가 Presenter 로 바뀌었다는 것이고, 두번째로 Model이 View에 직접적으로 관여를 하냐 안하냐가 무엇을 의미하는지에 대해서 위의 글을 읽으면 알 수있는 부분이 있습니다. 즉 Activity나 Fragment 에서 하던 Controller의 역할을 Presenter 라는 클래스를 만들어서 분리하게 되는 것 입니다.

  • MVP 패턴의 장점
  1. Presenter를 통해서 View와 비즈니스 로직에 관련한 코드들을 분리해서 관리 할 수 있게 됩니다.

  2. 위의 코드의 분리가 되었기 때문에 유연한 개발 및 테스트도 용이해집니다. 유연한 개발이란, 자신이 원하는 Presenter를 채택 주입하여 Activity와 fragment를 구성할 수 있게 됩니다. 이러한 것들이 코드의 재활용과 테스트를 용의하게 해줍니다.

  • MVP 패턴의 단점
  1. Presenter가 존재하게 되면서 코드가 분리되다 보니, MVC 보다는 클래스의 갯수도 많아지고 분리된 코드의 유기적인 관계를 파악할 수 있어야 합니다. 그래서 초보자들 입장에서는 이해가 힘들 수 있습니다.

6. MVP 패턴 코드 예제

1. User.kt

data class User(
    var emailId: String?,
    var nickname: String?,
    var password: String?
) {
    fun signIn(userEmailId: String?, userNickname: String?, userPassword: String?): Boolean {

       if (DbName == userEmailId && DbPassword == userPassword) {
       		this.emailId = userEmailId
            this.nickname = userNickname
            this.password = userPassword
            return true
        }
        return false
    }

    fun signOut() { }
    
    // 임시 DB 역할
    companion object {
        const val DbName = "user@naver.com"
        const val DbPassword = "test"
    }
}

MVP의 Model 코드는 MVC와 동일합니다. 그러나 View와 직접적 의존이 사라지고 Presenter가 data와 비즈니스 로직을 중간에서 관리하도록 변경되게 됩니다.

2. Presenter 구성요소들

interface SignInPresenter {
    val user: User

    fun signIn()
}

class SignInPresenterImpl(
    private val signInView: SignInView
) : SignInPresenter {

    override val user: User
        get() = User()

    override fun signIn() {
    
        val emailId = signInView.emailId.toString(),
        val nickname = signInView.nickname.toString(),
        val password = signInView.password.toString()
        
        val isSignInSuccessful: Boolean = user.signIn(emailId, nickname, password)

        signInView.onSignResult(isSignInSuccessful)
    }
}

interface SignInView {

	val emailId: String?
    val nickname: String?
    val password: String?

    fun onSignResult(isSignInSuccessful: Boolean?)
}

Presenter은 View를 직접 참조하지 않고 interface를 통해 참조하게 되기 때문에 결합을 낮추게 됩니다. 또한 Controller와 다르게 View와 Model이 직접적인 의존성을 가지지 않기 때문에 Model을 Presenter에서 바꾸거나 혹은 비즈니스 로직을 다르게 적용하는 것도 View에 직접적인 영향을 주지않고 가능해져서 유연한 개발 및 테스트도 용이해집니다.

3. SignInActivity.kt

class SignInActivity : AppCompatActivity(), SignInView {

    private val binding: ActivitySignInBinding by lazy {
        ActivitySignInBinding.inflate(layoutInflater)
    }
    
    private lateinit var signInPresenterImpl: SignInPresenterImpl

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        signInPresenterImpl = SignInPresenterImpl(this)
        binding.signInBtn.setOnClickListener { signInPresenterImpl.signIn() }
    }
    
	override val emailId: String
    	get() = binding.emailId.text.toString()
    override val nickname: String
        get() = binding.nickname.text.toString()
    override val password: String
        get() = binding.password.text.toString()

    override fun onSignResult(isSignInSuccessful: Boolean?) {
        if (isSignInSuccessful == true) {
            Toast.makeText(this, "signIn Successful", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "signIn Failed", Toast.LENGTH_SHORT).show()
        }
    }
}

MVC에서는 View와 Controller에 책임이 있었던 Activity가 온전히 View의 역할만을 하게 됩니다. Model 참조가 없어진 대신 Presenter에 대한 참조가 생기게 됩니다.

7. MVC와 MVP 패턴의 비교 요약

MVC에서 뷰는 모델을 알고 있고, 모델에서 뷰에게 모델의 data 변경 사항을 알립니다. MVP에서 뷰는 모델에 대해 아무것도 모르고 Presenter에서 모델의 최신 데이터를 가져오고 뷰가 업데이트되어야 하는지 명령을 받아서 뷰에 새 데이터를 바인딩하게 됩니다.

그래서 MVC의 뷰는 모델의 notifications 처리도 담당하기 때문에 더 많은 logic을 포함하게 됩니다. MVP에서는 동일한 logic이 Presenter에 있어 뷰의 책임이 분리 됩니다. 뷰의 유일한 책임은 Presenter가 바인딩한 데이터를 렌더링하고 사용자 입력을 캡처하는 것이 되게 되는 것이 됩니다.

즉, MVC에서 뷰는 모델의 존재를 인식하지만 MVP에서는 뷰와 모델이 서로에 대해 아무것도 모른다는 것입니다.

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글