MVC (Model-View-Controller) Pattern 정리

테사벨로그·2025년 12월 3일

Design Pattern

목록 보기
18/19

1. 왜 MVC Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: UI와 비즈니스 로직이 섞인 코드
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;
        }
        
        // UI 로직 (화면 그리기)
        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 패턴으로 알림
// Model: 순수한 비즈니스 로직만
class Model extends Observable {
    int xPosition, yPosition;
    int xDelta = 6, yDelta = 4;
    
    void makeOneStep() {
        // 순수한 위치 계산 로직
        xPosition += xDelta;
        // ... 경계 체크 로직 ...
        
        setChanged();
        notifyObservers();  // View들에게 알림
    }
}

2. View (UI Layer)

  • "Model의 데이터를 화면에 렌더링"
  • Model을 관찰(Observer) 하며 변경 시 자동 업데이트
  • 사용자 입력을 Controller에게 전달
  • 같은 Model, 다른 View 가능 (막대그래프, 파이차트, 테이블 등)
// View: 화면 표시만 담당
class View extends Canvas implements Observer {
    Model model;
    
    public void paint(Graphics g) {
        // Model의 데이터를 화면에 그리기만 함
        g.fillOval(model.xPosition, model.yPosition, 20, 20);
    }
    
    public void update(Observable obs, Object arg) {
        repaint();  // Model 변경 시 다시 그림
    }
}

3. Controller (UI Layer)

  • "사용자 입력을 Model의 동작으로 변환"
  • Strategy 패턴 - 다른 Controller로 교체하여 다른 동작 가능
  • View와 Model을 연결하는 역할
// Controller: 사용자 입력 처리
public class Controller extends JApplet {
    Model model = new Model();
    View view = new View(model);
    
    public void init() {
        stepButton.addActionListener(e -> {
            model.makeOneStep();  // 사용자 입력 → Model 동작
        });
        model.addObserver(view);  // Model-View 연결
    }
}

3. MVC에서 사용되는 디자인 패턴들

MVC = Compound Pattern (복합 패턴)

패턴적용 위치목적
ObserverModel ↔ ViewModel 변경 시 View 자동 업데이트, 느슨한 결합
StrategyView ↔ ControllerController 교체로 다른 사용자 입력 처리 가능
CompositeView 내부중첩된 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;      // 경계값 (View 크기에서 설정)
    int xDelta = 6;
    int yDelta = 4;
    
    // 공을 한 스텝 이동
    void makeOneStep() {
        // X축 이동
        xPosition += xDelta;
        if (xPosition < 0 || xPosition >= xLimit) {
            xDelta = -xDelta;           // 방향 반전
            xPosition += xDelta;
        }
        
        // Y축 이동
        yPosition += yDelta;
        if (yPosition < 0 || yPosition >= yLimit) {
            yDelta = -yDelta;           // 방향 반전
            yPosition += yDelta;
        }
        
        // ⭐ Observer들에게 변경 알림
        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);
    }
    
    // ⭐ Model로부터 알림 받으면 다시 그림
    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);
        
        // ⭐ 사용자 입력 → Model 동작으로 변환
        stepButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                model.makeOneStep();
            }
        });
        
        // ⭐ Model과 View 연결 (Observer 등록)
        model.addObserver(view);
        view.controller = this;
    }
    
    public void start() {
        // Model에게 경계값 설정
        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 예시

// 막대그래프 View
class BarChartView implements Observer {
    Model model;
    
    public void update(Observable obs, Object arg) {
        // model.getData()를 막대그래프로 표시
    }
}

// 파이차트 View
class PieChartView implements Observer {
    Model model;
    
    public void update(Observable obs, Object arg) {
        // model.getData()를 파이차트로 표시
    }
}

// 테이블 View
class TableView implements Observer {
    Model model;
    
    public void update(Observable obs, Object arg) {
        // model.getData()를 테이블로 표시
    }
}

// 사용
Model model = new Model();
model.addObserver(new BarChartView(model));   // 막대그래프
model.addObserver(new PieChartView(model));   // 파이차트
model.addObserver(new TableView(model));      // 테이블

model.setData(60, 30, 10);  // → 세 View 모두 자동 업데이트!

7. 핵심 정리

MVC 구성 요소

요소역할사용 패턴Layer
Model데이터 + 비즈니스 로직Observable (Subject)Non-UI
View화면 렌더링Observer, CompositeUI
Controller입력 처리, M-V 연결StrategyUI

언제 사용하는가?

  • 같은 데이터를 여러 형태로 표시할 때 (그래프, 테이블, 차트)
  • UI와 비즈니스 로직을 분리하고 싶을 때
  • 다양한 UI 지원이 필요할 때 (웹, 모바일, 데스크톱)
  • Model을 독립적으로 테스트하고 싶을 때

MVC 리트머스 테스트

"Model을 수정 없이 완전히 다른 UI에서 사용할 수 있는가?"
   예: Swing GUI → Console Text Interface
   
✅ 가능하면 MVC가 잘 적용된 것
❌ 불가능하면 분리가 덜 된 것

핵심 원칙

  • 관심사 분리: Model(데이터), View(표시), Controller(입력)
  • 단방향 의존성: Non-UI 모듈은 UI 모듈에 의존하면 안 됨
  • Observer 기반 동기화: Model 변경 → View 자동 업데이트
  • 교체 가능성: Controller를 바꿔서 다른 입력 처리 가능
profile
다들 응원합니다.

0개의 댓글