- 안드로이드 디자인 패턴(MVC,MVP,MVVM) 정리2023년 11월 16일 20시 05분 27초에 업로드 된 글입니다.작성자: Moonsu99
디자인 패턴이란?
소프트웨어 공학에서 반복적으로 발생하는 특정 문제를 해결하기 위해 일반적으로 사용되는 솔루션의 템플릿이다.
이러한 패턴은 특정 문제를 해결하는 데 도움이 되는 검증된 개발 방법론을 제공한다.
디자인 패턴은 코드를 재사용하고, 유지 보수를 용이하게 하며, 소프트웨어 설계의 복잡성을 줄이는 데 도움을 제공한다.
MVC(Model - View - Controller)

- 장점:
- 관심사의 분리: 데이터, 사용자 인터페이스, 그리고 비즈니스 로직이 분리되어 있어 관리와 유지보수가 용이하다.
- 재사용성과 확장성 가능: 각 부분을 독립적으로 재사용하고 확장할 수 있다.
- 단점:
- 복잡성: 큰 프로젝트에서 컨트롤러가 복잡해질 수 있다.
- View - Model 간의 간접적인 데이터 흐름: 뷰와 모델 간의 데이터 통신이 복잡해질 수 있다.
예시) E-Commerce
- 모델 (Model): 이 구성 요소는 데이터와 비즈니스 로직을 처리한다. 예를 들어, Model은 상품 정보, 재고 수량, 가격 등의 데이터를 포함할 수 있다. 모델은 데이터베이스와의 상호작용을 담당하며, 데이터를 조회하거나 수정하는 로직을 포함.
- 뷰 (View): 뷰는 사용자에게 정보를 표시하는 역할을 한다. 이 구성 요소는 HTML, CSS, JavaScript 등을 사용하여 사용자 인터페이스(UI)를 생성. 온라인 상점에서 뷰는 상품 목록, 사용자 장바구니, 구매 버튼 등을 표시할 수 있다. 뷰는 사용자의 입력을 받아 컨트롤러에 전달하는 역할도 수행한다.
- 컨트롤러 (Controller): 컨트롤러는 사용자의 입력을 처리하고 모델을 업데이트한다. 예를 들어, 사용자가 상품을 장바구니에 추가하는 경우, 컨트롤러는 해당 사용자의 요청을 받아 모델을 업데이트하고, 변경된 정보를 뷰에 반영하여 사용자에게 보여준다. 컨트롤러는 사용자의 요청에 따라 모델과 뷰 사이의 상호작용을 조정한다.
Android의 예시로 보여주자면 다음과 같다.
Product.kt(Model)
data class Product( val id: Int, val name: String, val price: Int, val quantity: Int )activity_main.xml (View)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/TextViewProducts" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" /> </LinearLayout>MainActivity.kt( Controller & View)
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { // 예시 데이터 private val products = listOf( Product(1, "티셔츠", 19900.0, 10), Product(2, "스니커즈", 59900.0, 5), Product(3, "백팩", 34900.0, 7) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) showProducts() } private fun showProducts() { // 뷰를 업데이트하는 로직 TextViewProducts.text = products.joinToString("\n") { "${it.name}: ${it.price}원" } } }MVP(Model - View - Presenter)

- 장점:
- 분리된 관심사: MVC와 유사하게, 관심사의 분리가 잘 되어 있다.
- 테스트 용이성: 프레젠터는 UI 로직을 분리하므로 테스팅 하기에 용이하다.
- 단점:
- 프레젠터의 과부하: 프레젠터가 뷰와 모델 사이의 모든 통신을 처리하므로 복잡해질 수 있다.
- 뷰와 강한 결합: 뷰와 프레젠터가 밀접하게 결합되어 있어 때로는 재사용성이 떨어질 수 있다.
예시 ) E-commerce
- 모델 (Model): 데이터와 비즈니스 로직을 처리. 모델은 상품 정보(예: 이름, 가격, 재고 수량)와 같은 데이터를 관리하며, 데이터베이스와의 상호작용을 담당한다. 모델은 데이터를 조회하거나 수정하는 로직을 포함.
- 뷰 (View): 사용자에게 정보를 표시하는 역할. 구성 요소는 사용자 인터페이스(UI)를 구성하고, 상품 목록, 사용자 장바구니, 구매 버튼 등을 표시한다. 뷰는 사용자의 입력(예: 버튼 클릭, 목록 선택)을 받아 Presenter에 전달.
- 프레젠터 (Presenter): 컨트롤러와 유사한 역할을 하지만, View와 Model 사이의 중간자로서 동작한다. 사용자의 입력을 처리하고, 그에 따라 Model을 업데이트한 후, 변경된 정보를 View에 반영한다. Presenter는 사용자의 요청에 따라 Model과 View 사이의 상호작용을 조정.
ProductModel.kt(Model)
data class Product(val id: Int, val name: String, val price: Double, val stock: Int) class ProductModel { // 예시 데이터 private val products = listOf( Product(1, "티셔츠", 19900.0, 10), Product(2, "스니커즈", 59900.0, 5), Product(3, "백팩", 34900.0, 7) ) fun getProducts(): List<Product> = products }ProductView.kt(View)
interface ProductView { fun displayProducts(products: List<Product>) fun displayProductDetails(product: Product) }MainActivity.kt(View 구현체)
class MainActivity : AppCompatActivity(), ProductView { private lateinit var presenter: ProductPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) presenter = ProductPresenter(this, ProductModel()) presenter.loadProducts() } override fun displayProducts(products: List<Product>) { // 상품 목록을 UI에 표시 } override fun displayProductDetails(product: Product) { // 선택한 상품의 세부 정보를 UI에 표시 } }ProductPresenter.kt
class ProductPresenter(private val view: ProductView, private val model: ProductModel) { fun loadProducts() { val products = model.getProducts() view.displayProducts(products) } fun onProductSelected(productId: Int) { val product = model.getProducts().find { it.id == productId } product?.let { view.displayProductDetails(it) } } }사용자의 입력(상품 선택 등)을 처리하고, ProductModel에서 데이터를 가져와 ProductView(해당 예시에서는 mainActivity)에 전달한다.
MVVM(Model - View - ViewModel)

내가 이해한 MVVM 패턴 - 장점:
- 반응형 프로그래밍 지원: 데이터 바인딩을 통해 뷰와 모델 간의 자동 동기화를 지원한다.
- 뷰의 독립성: 뷰가 ViewModel에만 의존하므로 재사용성이 높고 테스트하기 쉽다.
- 단점:
- 초기 학습 곡선: 데이터 바인딩과 MVVM 패턴 자체의 이해에 어려움이 있을 수 있다.
- 과도한 데이터 바인딩: 너무 많은 데이터 바인딩은 성능 문제를 일으킬 수 있다.
예시
UserProfile.kt (Model)
data class UserProfile(val id: Int, val name: String, val email: String)UserProfileDataSource.kt(데이터의 소스 정의, 데이터 접근 Method)
interface UserProfileDataSource { fun getUserProfile(userId: Int): UserProfile } class FakeUserProfileDataSource : UserProfileDataSource { override fun getUserProfile(userId: Int): UserProfile { //예시 데이터를 반환. 데이터베이스나 네트워크 호출을 수행. return UserProfile(userId, "사용자 이름", "user@example.com") } }GetUserProfileUseCase.kt (UseCase, 특정 비즈니스 로직 캡슐화)
class GetUserProfileUseCase(private val dataSource: UserProfileDataSource) { fun getUserProfile(userId: Int): UserProfile { return dataSource.getUserProfile(userId) } }UserProfileViewModel.kt (ViewModel)
class UserProfileViewModel(private val getUserProfileUseCase: GetUserProfileUseCase) : ViewModel() { private val _userProfile = MutableLiveData<UserProfile>() val userProfile: LiveData<UserProfile> = _userProfile fun loadUserProfile(userId: Int) { val profile = getUserProfileUseCase.getUserProfile(userId) _userProfile.value = profile } }UserProfileActivity.kt
class UserProfileActivity : AppCompatActivity() { private val viewModel: UserProfileViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_profile) viewModel.userProfile.observe(this, Observer { userProfile -> textViewUserName.text = userProfile.name textViewUserEmail.text = userProfile.email }) viewModel.loadUserProfile(1) } }1. Model (UserProfile.kt)
- 목적: 애플리케이션에서 사용되는 기본 데이터 구조를 정의.
- 작업: UserProfile 클래스는 사용자의 ID, 이름, 이메일과 같은 사용자 정보를 저장. 이 모델은 애플리케이션의 다른 부분에 전달되어 사용.
2. Data Source (UserProfileDataSource.kt)
- 목적: 데이터의 소스를 정의하고, 실제 데이터에 접근하는 메서드를 제공.
- 작업: UserProfileDataSource 인터페이스는 사용자 프로필을 가져오는 메서드를 정의. FakeUserProfileDataSource는 이 인터페이스의 구현체로, 예시 데이터를 반환. 실제 애플리케이션에서는 이 부분이 데이터베이스나 API 호출을 수행할 수 있다.
3. Use Case (GetUserProfileUseCase.kt)
- 목적: 특정 비즈니스 로직을 캡슐화.
- 작업: GetUserProfileUseCase는 사용자 프로필을 가져오는 작업을 캡슐화. 이 클래스는 UserProfileDataSource를 사용하여 실제 데이터를 가져온다. ViewModel은 이 Use Case를 사용하여 필요한 데이터를 요청.
4. ViewModel (UserProfileViewModel.kt)
- 목적: View와 Model 사이의 데이터 처리 및 로직을 관리.
- 작업: UserProfileViewModel은 View에 표시될 데이터를 관리. LiveData를 사용하여 UI가 데이터의 변경을 관찰하고 반응할 수 있도록 한다. loadUserProfile 함수는 Use Case를 통해 사용자 데이터를 가져온 후, 이를 LiveData 객체에 업데이트.
5. View (UserProfileActivity.kt)
- 목적: 사용자 인터페이스를 관리하고, 사용자와의 상호작용을 처리.
- 작업: UserProfileActivity는 화면에 사용자 인터페이스를 표시. 이 클래스는 ViewModel의 userProfile LiveData 객체를 관찰하고, 데이터가 변경될 때 UI를 업데이트를 한다. 또한, ViewModel에 사용자의 상호작용(예: 버튼 클릭)을 통해 데이터를 로드하도록 요청한다.
패턴 비교
- MVC vs MVP:
- MVC에서는 뷰와 모델이 직접 통신할 수 있지만, MVP에서는 모든 통신이 Presenter를 통해 이루어진다.
- MVP에서는 Presenter가 뷰의 상태와 로직을 관리한다.
- MVC/MVP vs MVVM:
- MVVM은 데이터 바인딩을 사용하여 뷰와 뷰모델 간의 의존성을 줄인다.
- MVVM에서는 뷰모델이 뷰의 상태를 관리하며, 뷰는 선언적으로 UI를 정의한다.
- 테스트 용이성:
- MVP와 MVVM은 테스트가 더 용이하다. MVP에서는 Presenter가, MVVM에서는 ViewModel이 UI 로직을 분리하여 테스트를 용이하게 한다.
각 패턴은 특정한 유형의 프로젝트와 기술 스택에 더 적합할 수 있으며, 프로젝트의 요구사항과 개발 팀의 선호도에 따라 적절한 패턴을 선택하는 것이 중요하다.
'Android [ Java, Kotlin ]' 카테고리의 다른 글
안드로이드 프로그래밍 과제(Java) - 이미지뷰어와 필터 (0) 2023.11.17 안드로이드 AAC(Android Architecture Components) 란? (0) 2023.11.17 Android Firebase FCM 정리 (0) 2023.11.15 Android DataStore로 토큰 처리하기 [Kotlin] (1) 2023.11.14 Appium을 이용해 안드로이드 테스트 자동화 환경 구축하기 (0) 2023.10.20 다음글이 없습니다.이전글이 없습니다.댓글 - 장점: