
MVC, MVP, MVVM 패턴은 모두 UI 코드와 비즈니스 로직을 분리하기 위한 아키텍처 패턴
초기 프로그램은 로직과 UI가 한 파일에 섞여 있었기 때문에 UI 복잡성 증가, 코드 관리에 어려움
-> UI를 조금만 바꿔도 비즈니스 로직까지 수정해야 하는 유지보수 문제 발생, 테스트에 어려움
따라서 UI와 비즈니스 로직을 분리가 필요!
UI(View), 데이터 처리(Model), 로직 처리(Controller, Presenter, ViewModel) 을 분리하여 각 부분을 독립적으로 수정하고, 테스트 진행
Model-View-Controller의 약자로 UI, 데이터, 제어 로직을 각각 분리하여, 유지보수성과 확장성을 높이는 소프트웨어 디자인 패턴
Controller로 사용자의 Action이 들어옴Controller는 사용자의 Action을 확인하고, Model 업데이트Controller는 Model을 나타내 줄 View를 선택View는 Model을 이용하여 화면을 나타냄
Controller는 여러 개의 View를 선택할 수 있는 1:n 구조
Controller는 View를 선택할 뿐 직접 업데이트 하지는 않음 (View는 Controller를 알지 못함)
//Controller
@Controller
public class BookController {
@GetMapping("/book")
public String getBook(Model model){
Book book = new Book("Clean Code", "Robert Martin");
model.addAttribute("book", book);
return "bookView";
}
}
//Model
public class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
}
//View
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Book Info</title>
</head>
<body>
<h1>📚 Book Information</h1>
<p>Title: <span th:text="${book.title}"></span></p>
<p>Author: <span th:text="${book.author}"></span></p>
</body>
</html>

관심사를 분리하여 코드의 역할이 명확
유지보수와 테스트에 용이
UI, 데이터, 제어 로직을 독립적으로 수정 가능하며 비즈니스 로직(Model) 테스트가 쉬움
유연한 UI 교체 가능
Model은 그대로, View만 변경 가능한 완벽하게 분리된 구조
하지만 Model과 View의 의존성이 굉장히 크다는 단점
역할별로 코드를 분리하여 유지보수를 쉽게 만들고, UI와 비즈니스 로직을 유연하게 관리할 수 있게 해주는 패턴
하지만 Model과 View의 의존성이 크기 때문에, Controller가 불필요하게 커질 수 있음 -> 이를 보안하기 위해 MVP 패턴 등장
Model-View-Presenter의 약자로 Model과 View는 MVC 패턴과 동일하지만 Controller 대신 Presenter가 존재
View에서 이벤트 발생View가 이벤트를 Presenter에게 전달Presenter가 Model을 호출하여 데이터 처리Presenter가 View를 갱신 (View에 직접 명령)
Presenter는 View와 Model의 인스턴스를 가지고 있어 둘을 연결하는 접착제 역할
View와 Presenter는 1:1 관계로 완전히 의존하며, View는 단순 UI 렌더링만 담당
계산을 담당하는 Model과 결과를 반환하는 View
public class CalculatorModel {
public int add(int a, int b){
return a+b;
}
}
public interface CalculatorView {
void showResult(int result);
}
public class CalculatorConsoleView implements CalculatorView{
@Override
public void showResult(int result) {
System.out.println("Result : " + result);
}
}
View과 Model을 이어주는 Presenter
public class CalculatorPresenter {
private final CalculatorModel model;
private final CalculatorView view;
public CalculatorPresenter(CalculatorModel model, CalculatorView view) {
this.model = model;
this.view = view;
}
public void onAddButtonClicked(int a, int b){
int result = model.add(a,b);
view.showResult(result);
}
}
public class CalculatorMain {
public static void main(String[] args) {
CalculatorModel model = new CalculatorModel();
CalculatorView view = new CalculatorConsoleView();
CalculatorPresenter presenter = new CalculatorPresenter(model, view);
presenter.onAddButtonClicked(10,20);
}
}
/*
Result : 30
*/
View, Model의 완벽 분리View는 Presenter의 명령만 수행 -> UI 코드 단순화Presenter는 View를 인터페이스로 참조 -> 플랫폼 독립적 설계 가능View Interface, Presenter 등 코드가 많아지며, 복잡한 화면 구조에는 Presenter가 비대해질 수 있음View와 Model을 철저히 분리하여 테스트 가능한 구조를 만드는 패턴
MVC보다 View의 역할이 훨씬 더 축소되고, View와 Model의 의존성은 해결되었지만, View와 Presenter의 의존성이 강해지는 문제 발생
-> 이를 보안하기 위해 MVVM 패턴의 등장
Model-View-ViewModel의 약자로, View와 Model을 데이터 바인딩으로 완전히 분리하는 구조로, 주로 양방향 데이터 바인딩이 지원되는 프레임워크에서 사용되는 디자인 패턴
View에 요청Command 패턴으로 ViewModel에 명령을 함ViewModel은 필요한 데이터를 Model에 요청 및 응답ViewModel은 응답 받은 데이터를 가공하여 저장View는 ViewModel과의 Data Binding으로 인해 자동 갱신
View와 Model 사이의 의존성이 없으며, Command 패턴과 Data Binding을 사용하여 View와 ViewModel의 의존성도 제거 (View는 ViewModel의 값을 감시만 하고, 직접 제어하지는 않는 직접 동기화된 구조)
Data Binding : 데이터와 화면을 실시간으로 동기화하는 기술
ViewModel이 Model을 직접 사용하여 데이터를 주입하거나 가져옴
// 💡 Model: 데이터 객체 (비즈니스 로직을 담을 수도 있음)
data class User(val name: String, val age: Int)
ViewModel은 UI를 위한 상태 관리자
LiveData를 사용해 데이터가 바뀌면 View에 자동으로 반영되도록 구성
loadUser() 호출 시 -> ViewModel의 데이터 갱신 -> View 자동 업데이트class UserViewModel : ViewModel() {
// 💡 View에 실시간으로 반영될 데이터를 LiveData로 선언
val userName = MutableLiveData<String>()
val userAge = MutableLiveData<Int>()
// 💡 Model의 데이터를 ViewModel에 로드하는 메서드
fun loadUser() {
val user = User("채영은", 25)
userName.value = user.name // View에 실시간으로 반영됨
userAge.value = user.age // View에 실시간으로 반영됨
}
}
View는 ViewModel을 바인딩하여 데이터 변경을 감시
viewModel.loadUser()가 호출되면 → 데이터가 바뀌고 → View가 즉시 갱신됨.class MainActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 💡 ViewModel 생성 및 바인딩
viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
// 💡 ViewModel을 View에 연결 → 이걸 통해 데이터 자동 동기화
binding.viewModel = viewModel
binding.lifecycleOwner = this // LiveData를 위한 필수 설정
// 💡 사용자 화면 진입 시 데이터 로드 요청
viewModel.loadUser()
}
}
XML에서 ViewModel의 데이터를 실시간으로 구독하고 있음.
ViewModel의 userName이 변경되면 → TextView의 텍스트가 즉시 자동 갱신됨.<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.userName}" /> <!-- 💡 LiveData 바인딩 -->
View와 Model이 철저히 분리되며 ViewModel이 UI 상태를 관리하여 View가 훨씬 간결해짐
양방향 데이터 바인딩을 통해 자동 UI 업데이트 가능
모든 모듈이 독립적인 상태로 테스트 용이
하지만 ViewModel의 설계가 쉽지 않으며 비대해질 위험이 있음
결국 ViewModel을 UI처럼 사용하고, 데이터 바인딩을 통해 View와 ViewModel을 실시간으로 동기화하는 패턴.
View는 단순히 ViewModel을 관찰만 하는 역할
Controller가 다양한 View를 제어
하지만 View에 반환되는 Model이 정해져있기 때문에 상당히 의존적임
따라서 View와 Model의 의존성을 낮추기 위해 ~
Presenter가 View와 Model 사이에서 View를 직접 제어.
하지만 View와 Presenter 사이의 의존성이 커지는 문제 발생
따라서 View와 Presenter의 의존성을 낮추기 위해 ~
ViewModel을 통해 View와 데이터 실시간 동기화
View, Model, ViewModel 모두 의존성이 낮고, 독립적인 모듈로 구성 가능
하지만 그만큼 점점 코드를 구현하는 복잡도가 높아진다는 단점 존재
| MVC | MVP | MVVM |
|---|---|---|
| 서버 UI | 수동 바인딩 UI | 자동 바인딩 UI |
사실상 어느 패턴을 사용하는 것에 정답은 없으며, 현재 프로젝트의 크기와 상황에 알맞게 비교하고 사용하는 것이 좋다!
https://magi82.github.io/android-mvc-mvp-mvvm/
https://beomy.tistory.com/43
https://goharry.tistory.com/46