1. 왜 MVC Pattern이 생겨났는가?
문제 상황
public class BallApplet extends JApplet {
int xPosition = 0, yPosition = 0;
int xDelta = 6, yDelta = 4;
public void paint(Graphics g) {
xPosition += xDelta;
if (xPosition < 0 || xPosition >= getWidth()) {
xDelta = -xDelta;
xPosition += xDelta;
}
yPosition += yDelta;
if (yPosition < 0 || yPosition >= getHeight()) {
yDelta = -yDelta;
yPosition += yDelta;
}
g.setColor(Color.red);
g.fillOval(xPosition, yPosition, 20, 20);
showStatus("x=" + xPosition + ", y=" + yPosition);
}
}
문제점:
- 같은 데이터를 다른 형태로 표시하려면 코드 전체 수정 필요 (막대그래프 ↔ 파이차트)
- UI를 바꾸면 비즈니스 로직까지 영향 받음
- 테스트 어려움 - UI 없이 로직만 테스트 불가능
- 다중 뷰 지원 불가 - 하나의 데이터를 여러 화면에서 동시에 표시 어려움
2. MVC의 세 가지 구성 요소
1. Model (Non-UI Layer)
- "데이터와 비즈니스 규칙을 담당"
- 애플리케이션의 핵심 상태 캡슐화
- UI에 대해 아무것도 모름
- 상태 변경 시 Observer 패턴으로 알림
class Model extends Observable {
int xPosition, yPosition;
int xDelta = 6, yDelta = 4;
void makeOneStep() {
xPosition += xDelta;
setChanged();
notifyObservers();
}
}
2. View (UI Layer)
- "Model의 데이터를 화면에 렌더링"
- Model을 관찰(Observer) 하며 변경 시 자동 업데이트
- 사용자 입력을 Controller에게 전달
- 같은 Model, 다른 View 가능 (막대그래프, 파이차트, 테이블 등)
class View extends Canvas implements Observer {
Model model;
public void paint(Graphics g) {
g.fillOval(model.xPosition, model.yPosition, 20, 20);
}
public void update(Observable obs, Object arg) {
repaint();
}
}
3. Controller (UI Layer)
- "사용자 입력을 Model의 동작으로 변환"
- Strategy 패턴 - 다른 Controller로 교체하여 다른 동작 가능
- View와 Model을 연결하는 역할
public class Controller extends JApplet {
Model model = new Model();
View view = new View(model);
public void init() {
stepButton.addActionListener(e -> {
model.makeOneStep();
});
model.addObserver(view);
}
}
3. MVC에서 사용되는 디자인 패턴들
MVC = Compound Pattern (복합 패턴)
| 패턴 | 적용 위치 | 목적 |
|---|
| Observer | Model ↔ View | Model 변경 시 View 자동 업데이트, 느슨한 결합 |
| Strategy | View ↔ Controller | Controller 교체로 다른 사용자 입력 처리 가능 |
| Composite | View 내부 | 중첩된 UI 위젯들을 동일하게 처리 |
Observer 패턴이 핵심인 이유
Model이 View의 존재를 몰라도 됨!
→ Model은 "누군가 나를 관찰하고 있다"만 알면 됨
→ View가 100개여도 Model 코드 변경 없음
4. MVC 동작 흐름
┌─────────────────────────────────────────────────────────┐
│ Non-UI Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Model │ │
│ │ • 애플리케이션 상태 캡슐화 │ │
│ │ • 비즈니스 로직 수행 │ │
│ │ • 상태 변경 시 Observer들에게 알림 │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────┬────────────────────────────┬─────────────┘
│ Change │ State
│ Notification │ Change
▼ ▲
┌─────────────────────────────────────────────────────────┐
│ UI Layer │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ View │◄───│ Controller │ │
│ │ • 화면 렌더링 │ │ • 사용자 입력 처리 │ │
│ │ • Model 데이터 │ │ • 입력 → Model 동작 │ │
│ │ 표시 │────►│ 변환 │ │
│ └──────────────────┘ └──────────────────────────┘ │
│ User Gestures │
└─────────────────────────────────────────────────────────┘
동작 순서
1. 사용자가 View와 상호작용 (버튼 클릭)
↓
2. Controller가 입력 처리 (ActionListener)
↓
3. Controller가 Model 업데이트 (model.makeOneStep())
↓
4. Model이 View에게 알림 (notifyObservers())
↓
5. View가 화면 갱신 (repaint())
↓
6. 사용자 입력 대기
5. 예시 코드: Bouncing Ball Applet
Step 1: Model (Observable)
import java.util.Observable;
class Model extends Observable {
final int BALL_SIZE = 20;
int xPosition = 0;
int yPosition = 0;
int xLimit, yLimit;
int xDelta = 6;
int yDelta = 4;
void makeOneStep() {
xPosition += xDelta;
if (xPosition < 0 || xPosition >= xLimit) {
xDelta = -xDelta;
xPosition += xDelta;
}
yPosition += yDelta;
if (yPosition < 0 || yPosition >= yLimit) {
yDelta = -yDelta;
yPosition += yDelta;
}
setChanged();
notifyObservers();
}
}
Step 2: View (Observer)
import java.awt.*;
import java.util.Observer;
import java.util.Observable;
class View extends Canvas implements Observer {
Controller controller;
Model model;
int stepNumber = 0;
View(Model model) {
this.model = model;
}
public void paint(Graphics g) {
g.setColor(Color.red);
g.fillOval(model.xPosition, model.yPosition,
model.BALL_SIZE, model.BALL_SIZE);
controller.showStatus("Step " + (stepNumber++) +
", x = " + model.xPosition +
", y = " + model.yPosition);
}
public void update(Observable obs, Object arg) {
repaint();
}
}
Step 3: Controller (JApplet)
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Controller extends JApplet {
JPanel buttonPanel = new JPanel();
JButton stepButton = new JButton("Step");
Model model = new Model();
View view = new View(model);
public void init() {
setLayout(new BorderLayout());
buttonPanel.add(stepButton);
this.add(BorderLayout.SOUTH, buttonPanel);
this.add(BorderLayout.CENTER, view);
stepButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
model.makeOneStep();
}
});
model.addObserver(view);
view.controller = this;
}
public void start() {
model.xLimit = view.getSize().width - model.BALL_SIZE;
model.yLimit = view.getSize().height - model.BALL_SIZE;
repaint();
}
}
실행 결과
[Step 버튼 클릭]
Step 0, x = 6, y = 4
[Step 버튼 클릭]
Step 1, x = 12, y = 8
[Step 버튼 클릭]
Step 2, x = 18, y = 12
...
6. 같은 Model, 다른 View 예시
class BarChartView implements Observer {
Model model;
public void update(Observable obs, Object arg) {
}
}
class PieChartView implements Observer {
Model model;
public void update(Observable obs, Object arg) {
}
}
class TableView implements Observer {
Model model;
public void update(Observable obs, Object arg) {
}
}
Model model = new Model();
model.addObserver(new BarChartView(model));
model.addObserver(new PieChartView(model));
model.addObserver(new TableView(model));
model.setData(60, 30, 10);
7. 핵심 정리
MVC 구성 요소
| 요소 | 역할 | 사용 패턴 | Layer |
|---|
| Model | 데이터 + 비즈니스 로직 | Observable (Subject) | Non-UI |
| View | 화면 렌더링 | Observer, Composite | UI |
| Controller | 입력 처리, M-V 연결 | Strategy | UI |
언제 사용하는가?
- ✅ 같은 데이터를 여러 형태로 표시할 때 (그래프, 테이블, 차트)
- ✅ UI와 비즈니스 로직을 분리하고 싶을 때
- ✅ 다양한 UI 지원이 필요할 때 (웹, 모바일, 데스크톱)
- ✅ Model을 독립적으로 테스트하고 싶을 때
MVC 리트머스 테스트
"Model을 수정 없이 완전히 다른 UI에서 사용할 수 있는가?"
예: Swing GUI → Console Text Interface
✅ 가능하면 MVC가 잘 적용된 것
❌ 불가능하면 분리가 덜 된 것
핵심 원칙
- 관심사 분리: Model(데이터), View(표시), Controller(입력)
- 단방향 의존성: Non-UI 모듈은 UI 모듈에 의존하면 안 됨
- Observer 기반 동기화: Model 변경 → View 자동 업데이트
- 교체 가능성: Controller를 바꿔서 다른 입력 처리 가능