MVC 패턴
MVC 패턴이란?
MVC 패턴(Model-View-Controller Pattern)
은 어플리케이션을 개발함에 있어서 컴포넌트를 모델(Model)
, 뷰(View)
, 컨트롤러(Controller)
로 역할을 나누어 개발하는 디자인 패턴 즉, 설계 방식을 말합니다.
클라이언트(Client)가 뷰를 통해 컨트롤러에게 요청(Request)를 보내면 해당 요청을 컨트롤러가 받고, 컨트롤러는 모델을 통해 데이터를 가져와서 뷰를 제어합니다.
모델(Model)
모델은 애플리케이션의 데이터를 책임지는 컴포넌트 입니다. 내부 비즈니스 로직을 처리하는 역할을 하며, 다음과 같은 규칙을 가집니다.
사용자(Client)가 편집하길 원하는 모든 데이터를 가지고 있어야한다.
사용자가 게시판의 글을 수정하길 원한다면 모델은 제목, 내용, 작성자 등을 포함한 게시글에 대한 모든 데이터를 가지고 있어야 합니다.
뷰나 컨트롤러에 대한 어떤 정보도 알지 못한다.
모델은 자기 자신이 수행하는 일에 대한 정보만 알고 있어야 하며, 이를 넘어서는 작업을 해서는 안됩니다. 예를 들어 게시글 내용을 변경하는 역할을 하는 모델이 뷰의 정보를 가지고 게시글 색상을 변경하는 등의 작업을 해서는 안됩니다.
변경이 일어나면 통지에 대한 처리방법을 구현해야 한다.
모델의 데이터가 변경된다면 이를 컨트롤러에게 알리기 위한 처리 방법을 구현해야 하며, 재사용이 가능해야 합니다. 그렇기 때문에 MCV 패턴은 옵저버(Observer) 패턴
과 많은 연관점이 있습니다. 하나의 모델에 대하여 다수의 뷰가 모델을 구독(Subscribe)
하는 형태입니다.
뷰(View)
뷰는 사용자 인터페이스(User Interface, UI)
를 책임지는 컴포넌트 입니다. 모델이 가지고 있는 데이터를 기반으로 사용자에게 화면을 출력해주는 역할을 하며, 다음과 같은 규칙을 가집니다.
모델이 가지고 있는 정보를 저장해서는 안된다.
데이터 출력을 위해서는 필연적으로 모델의 정보를 전달받게 되는데, 이때 알게 된 모델의 정보를 따로 저장해서는 안됩니다.
모델이나 컨트롤러에 대한 어떤 정보도 알지 못한다.
모델과 마찬가지로 뷰 역시 자기 자신이 수행하는 일에 대한 정보만 알고 있어야 하며, 이를 넘어서는 작업을 해서는 안됩니다.
변경이 일어나면 통지에 대한 처리방법을 구현해야 한다.
모델과 같이 뷰에 변경이 있으면 통지할 수단이 필요합니다. 모델에서 설명했듯이 이는 보통 옵저버 패턴을 통해 구현됩니다.
컨트롤러(Controller)
컨트롤러는 뷰와 모델을 연결하여 실질적으로 사용자의 요청을 처리하는 컴포넌트 입니다. 사용자의 요청에 대하여 모델에게 데이터를 처리할 방법을 알려주며, 모델이 작업을 완료하면 그 결과를 뷰에 전달합니다. 컨트롤러는 다음과 같은 규칙을 가집니다.
모델이나 뷰의 정보를 알고 있어야 한다.
모델과 뷰는 서로 알지 못하기 때문에 컨트롤러를 통해 변경을 중재받습니다. 컨트롤러가 모델과 뷰를 중재하려면 모델과 뷰에 대한 정보를 가지고 있어야만 합니다.
모델이나 뷰의 변경을 모니터링 해야 한다.
위의 규칙과 마찬가지로 모델과 뷰는 서로 알지 못하기에 컨트롤러가 중재자(Mediator)
가 되어 서로의 변경을 통지해야 합니다.
옵저버 패턴(Observer Pattern)
옵저버 패턴(Observer Pattern)
은 객체의 상태를 관찰하는 옵저버를 통해 객체의 상태가 변하면 종속된 객체들에게 통지를 보내는 패턴입니다.
MVC 패턴은 옵저버 패턴(Observer Pattern)
과 많은 연관점이 있습니다. 하나의 모델에 대하여 다수의 뷰가 옵저버를 통해 모델을 구독하는 형태를 가지고 있으며, 이는 컨트롤러의 중재를 받습니다.
소스 코드
모델의 상태를 관찰하기 위한 옵저버 인터페이스 입니다. update
메소드를 통해 뷰가 변경됩니다.
Observer.java
public interface Observer {
void update(String title);
}
옵저버 인터페이스를 구현한 뷰 클래스 입니다. 아래 코드에서는 콘솔창을 통해 새로운 글의 등록을 알리도록 인터페이스를 구현했습니다.
View.java
public class View implements Observer {
private String name;
public View(String name) {
this.name = name;
}
@Override
public void update(String title) {
System.out.println(name + " > 새로운 글이 등록되었습니다 : " + title);
}
}
모델에 대한 인터페이스 입니다. notifyObservers
메소드를 통해 알림을 발행하며, registerObserver
메소드를 통해 뷰가 모델을 구독할 수 있습니다.
Observable.java
public interface Observable {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String title);
}
모델 클래스 입니다. Observable 인터페이스를 구현했으며, notifyObservers
메소드를 통해 모델을 구독하고 있는 뷰의 update 메소드를 호출합니다.
Model.java
import java.util.ArrayList;
import java.util.List;
public class Model implements Observable {
private final List<Observer> observers;
public Model() {
this.observers = new ArrayList<>();
}
@Override
public void notifyObservers(String title) {
for(Observer observer : observers) {
observer.update(title);
}
}
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
this.observers.remove(observer);
}
}
컨트롤러 클래스 입니다. 뷰와 모델은 컨트롤러에 의하여 통제됩니다. 아래 코드에서는 하드코딩을 통해 모델과 게시판1
이라는 뷰 객체를 생성하고, 1번째 글
이라는 클라이언트의 요청을 모델에 전달했습니다.
Controller.java
public class Controller {
public static void main(String[] args) {
Observable model = new Model();
Observer view = new View("게시판1");
model.registerObserver(view);
model.notifyObservers("1번째 글");
}
}
디버깅해보면 아래와 같은 로그를 볼 수 있습니다.
게시판1 > 새로운 글이 등록되었습니다 : 1번째 글
JAVA MVC 모델
자바의 MVC 패턴에는 MVC1과 MVC2가 있습니다.
MVC1 모델
MVC1 모델은 JSP 페이지가 뷰(View)와 컨트롤러(Controller) 부분을 담당하고 Java Bean이 모델(Model)과 컨트롤러(Controller)를 담당하는 구조입니다.
페이지의 흐름이 단순하기 때문에 개발이 간편하고 개발 속도가 빠릅니다.
반면 모델(비즈니스 로직)과 뷰(프레젠테이션 로직) 사이의 구분이 모호하여 디자이너와의 협업이 힘들며, 어플리케이션이 복잡해질수록 유지보수가 힘듭니다.
MVC2 모델
MVC2 모델은 JSP 페이지가 뷰를 담당하고 Servlet은 컨트롤러, Java Bean은 모델을 담당하는 구조입니다.
뷰와 모델의 구분이 명확하고, JSP 페이지의 코드가 간결해져 유지보수가 용이하고 디자이너와이 협업이 쉬워집니다.
반면 로직이 복잡해져 구조 설계를 위한 시간이 많이 소모되어 개발 기간이 증가합니다.
Spring MVC
Spring MVC는 MVC2 모델의 발전된 형태의 구조입니다.
FrontController인 DispatcherServlet
이 클라이언트의 요청을 받고 HandlerMapping
을 통해 Controller와 매핑되어 해당 컨트롤러를 실행합니다.
컨트롤러는 Model과 View가 Wrapping된 객체를 반환하고, ViewResolver
를 통해 View 객체를 처리한 후 클라이언트에게 해당 객체를 반환합니다.