MVC 패턴이란?

Minji Lee·2024년 12월 18일
0

디자인패턴

목록 보기
1/1

MVC 패턴 정의

소프트웨어를 Model(모델), View(뷰), Controller(컨트롤러)라는 개념으로 역할을 나누어 일련의 작업을 처리하는 개발 방법론


MVC 패턴의 관계

mvc
  1. 사용자(User)의 요청을 Controller가 받음
  2. Controller는 요청한 결과를 처리하기 위해 Model을 호출
  3. Model은 비즈니스 로직을 통해 데이터를 처리한 후 결과를 Controller에게 반환.
    Controller는 반환받은 결과를 View에 반영
    4.View는 데이터를 받아온 화면을 사용자에게 보여줌

Model(모델)

데이터 로직 처리 부분, 데이터와 관련된 부분 담당하며 값과 기능을 가지는 객체
[규칙]

  • 사용자가 처리하기 원하는 모든 데이터를 가지고 있어야함
  • View와 Controller에 대한 어떤 정보도 알면 안됨
  • 변경이 일어나면, 변경 통지에 대한 처리 방법 구현해야 함

View(뷰)

사용자 인터페이스 요소(화면), Controller에게 받은 Model의 데이터를 사용자에게 시각적으로 보여주는 역할
[규칙]

  • Model이 가지고 있는 정보를 따로 저장하면 안됨
  • Model이나 Controller를 알 필요 없음
  • 변경이 일어나면, 변경 통지에 대한 처리 방법 구현해야 함

Controller(컨트롤러)

Model과 View 사이의 데이터 흐름 제어, Model과 View의 역할을 분리하는 중요한 요소
[규칙]

  • Model과 View에 대해 알고 있어야 함
  • Model이나 View의 변경을 모니터링해야 함

JavaScript로 MVC 패턴 적용하기

  1. HTML 파일 및 Controller, View, Model 생성
  • 뼈대가 되는 HTML 파일 생성
    main 태그를 타겟으로 JS로 화면을 동적으로 생성할 예정

    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <script defer type="module" src="./main.js"></script>
      <title>JS로 MVC 패턴 적용하기</title>
    </head>
    <body>
      <div id="app">
        <main>
        </main>
      </div>
    </body>
    </html>
  • main.js에서 Model과 View를 선언하고, Controller에 주입시킴

    // main.js
    import Controller from './Controller.js';
    import Model from './Model.js';
    import View from './View.js';
    
    new Controller(new View(), new Model());
  • Controller 정의

    export default class Controller {
      constructor(view, model) {
        // view와 model 주입 받아 사용
        this.view = view;
        this.model = model;
      }
    }
  • Model 정의

    // 애플리케이션에서 사용할 데이터 소지
    export default class Model {
      constructor() {
        this.todoList = [
          {
            id: 0,
            text: 'todo',
          },
        ];
      }
    }
  • View 정의

    export default class View {
      constructor() {
        // mvc.html에 있는 main을 target으로 설정
        const target = document.querySelector('main');
        // target 복사
        this.$newEl = target.cloneNode(true);
        // 복사한 Node에 innerHTML로 템플릿코드 넣어 DOM 형태로 만들기
        this.$newEl.innerHTML = this.getTemplate();
        // target에 새로 만든 DOM으로 교체
        target.replaceWith(this.$newEl);
      }
      getTemplate() {
        return `<ul></ul>`;
      }
    }
  1. 컨트롤러에서 Model로 부터 데이터 받아 화면에 나타내기
// View.js
export default class View {
  (위의 코드와 동일)
  // todoList 배열을 받아 ul 하위로 text 뿌리기
  displayTodo(todoList) {
    const ul = this.$newEl.querySelector('ul');
    ul.innerHTML = `${todoList
      .map(
        (todo) => `
        <li data-id="${todo.id}">
          ${todo.text} ${todo.id}
        </li>
      `
      )
      .join('')}`;
  }
}
// Controller.js
export default class Controller {
  constructor(view, model) {
    // view와 model 주입 받아 사용
    this.view = view;
    this.model = model;
    this.#render();
  }
  #render() {
    // model로 부터 todoList 받아오기
    const { todoList } = this.model;
    // view에 todoList 전달
    this.view.displayTodo(todoList);
  }
}
  1. JS코드로 DOM 생성 결과
    HTML 파일에는 <div id=’app’><main></main></div> 밖에 작성 안했지만, JS 코드로 DOM 생성

여기서 View.js 파일의 $newEl은 react와 vue에서 사용하는 Virtual DOM임!

Virtual DOM이란, 가상의 DOM으로 메모리에 저장되어 있는, 아직 실체화되지 않은 DOM이다. 따라서, JavaScript를 통해 DOM을 만들었으며 DOM을 실제로 브라우저에 부착하기 전까지는 메모리상에 존재하기만 한다.

  1. Model 조작하는 기능 추가(할일 추가, 순서 뒤집기)
// Model.js
export default class Model {
  constructor() {
    this.todoList = [
      {
        id: 0,
        text: 'todo',
      },
    ];
  }

  // 투두 리스트에 할일 추가 기능
  addTodo(todo) {
    this.todoList = [...this.todoList, todo];
  }

  // 투두 리스트 순서 뒤집기
  get reverseTodoList() {
    return [...this.todoList].reverse();
  }
}
  • Model에서 만든 메서드 사용할 수 있도록 Controller에 메서드 생성
export default class Controller {
  constructor(view, model) {
    // view와 model 주입 받아 사용
    this.view = view;
    this.model = model;
    this.#render();
    // view의 AddEvent 메서드 실행
    // AddEvent: DOM에서 일어나는 이벤트 리스너를 초기화하는 함수
    this.view.addEvent({
      // .bind(this): handleAddTodo와 handleReverseTodo 메서드가 View에서 실행될때,
      // View에서 실행되는 this와 Controller의 this는 다르기 때문에 명시적 바인딩해주기 위해 사용
      handleAddTodo: this.handleAddTodo.bind(this),
      handleReverseTodo: this.handleReverseTodo.bind(this),
    });
  }
  #render() {
    // model로 부터 todoList 받아오기
    const { todoList } = this.model;
    // view에 todoList 전달
    this.view.displayTodo(todoList);
  }
  /**
   * 사용자 이벤트가 일어날 경우 실행되는 함수는 관례적으로 handle.. 또는 on.. 으로 짓는다.
   * react에서는 handle 이라는 prefix 사용
   */
  // 할일 추가
  handleAddTodo(todo) {
    this.model.addTodo(todo);
    this.#render();
  }
  // 투두 리스트 뒤집기
  handleReverseTodo() {
    const { reverseTodoList } = this.model;
    this.view.displayTodo(reverseTodoList);
  }
}
  • 이론상으로는 Controller에 사용자 요청을 받는 처리를 작성해야 하지만, View에서 이벤트를 받도록 구현
    ⇒ View에서 DOM 조작이 계속해서 일어날 것이고 그때마다 eventListener가 새롭게 생성되어야 하기때문에 View에 구현
export default class View {
  (위와 동일)
  // Controller에서 바인딩된 메서드들이 넘어옴
  addEvent(handlers) {
    this.$newEl.addEventListener('click', this.#runDomEvents(handlers), true);
  }
  // click 이벤트가 일어나면 실행될 메서드
  #runDomEvents({ handleAddTodo, handleReverseTodo }) {
    return ({ target }) => {
      // add_button을 클릭하면 Controller의 handleAddTodo 메서드 실행
      if (target.classList.contains('add_button')) {
        const $lastElement = this.$newEl.querySelector('li:last-child');
        const nextId = Number($lastElement.dataset.id) + 1;
        const todo = {
          id: nextId,
          text: `todo`,
        };
        handleAddTodo(todo);
      }
      // reverse_button을 클릭하면 Controller의 handleReverseTodo 메서드 실행
      if (target.classList.contains('reverse_button')) {
        handleReverseTodo();
      }
    };
  }

  getTemplate() {
    return `
      <ul></ul>
      <button class="add_button">Add</button>
      <button class="reverse_button">Reverse</button>
    `;
  }
  (위와 동일)
}


MVC 패턴은 왜 생겨났나?

GUI를 가진 소프트웨어를 객체 지향적으로 잘 구조화하기 위해 생겨남!

MVC 패턴의 본질적인 목표는 관심사를 분리하는 것(Model과 View를 분리하는 것)

1979년 제록스 팔로알토 연구소에서 최초의 태블릿 PC인 ‘다이나북’을 개발하는 과정에서 전문 지식 여부나 남녀노소 상관없이 모두가 직관적으로 사용할 수 있는 GUI 개발

이 과정에서 Trygve Reenskaug‘사용자가 세상을 인식하는 방법(멘탈 모델)과 컴퓨터가 정보를 인식하고 처리하는 방법(컴퓨터 모델)이 다르다’라는 점을 인식하게 됨.

GUI를 사용하는 애플리케이션은 UI 관련된 코드데이터 저장/처리 코드는 특성과 역할이 뚜렷하기 다르다고 판단함

비즈니스 로직시각적인 UI, 둘 사이를 연결해주는 부분을 코드 안에서 분리하고 역할 부여해주어야 한다고 생각함

MVC 패턴의 이점

  1. 명확한 역할 분리로 인해 서로간 결합도 낮출 수 있음

    Model, View, Controller 3가지 컴포넌트로 명확하기 구분됨

    • Model은 데이터 및 비즈니스 로직 담당
    • View는 사용자 인터페이스 표현
    • Controller는 사용자 요청 처리하기 위해 Model과 View 흐름 제어
  2. 코드의 재사용성 및 확장성

    Model과 Controller는 여러 View에서 재사용 가능하며, View도 다른 Model과 함께 재사용 가능

  3. 서비스 유지보수 및 테스트 용이

    변경이 필요한 부분을 쉽게 파악하고, 수정이나 확장할 경우 해당 부분에만 집중하여 개발 가능

  4. 개발자 간의 커뮤니케이션 효율성 높임

MVC 패턴의 한계점

  1. Model과 View의 의존성을 완전히 분리시킬 수 없음

    복잡한 구조의 애플리케이션의 경우, 하나의 Controller에 다수의 View와 Model이 복잡하게 연결되어 서로간 의존성이 커지는 상활 발생할 수 있음

  2. Controller 역할이 과도하게 커지면 Massive-View-Controller 현상 피할 수 없음

    하나의 Controller에 수많은 View와 Model이 연결되어 있어 Controller의 부하가 커지게됨

MVC 한계점을 해결하기 위해 MVVM, Flux, MVP 등 다양한 패턴 등장


MVC 패턴을 지키며 코딩하는 방법

  1. Model은 Controller와 View에 의존하지 않아야 한다.

    ⇒ Model 내부에 Controller와 View에 관련된 코드가 있으면 X

  2. View는 Model에만 의존해야 하고, Controller에는 의존하면 안됨

  3. View가 Model로부터 데이터를 받을 때는 사용자마다 다르게 보여주어야 하는 데이터만 받아야함

  4. Controller는 Model과 View에 의존해도 됨

  5. View가 Model로부터 데이터를 받을때, 반드시 Controller에서 받아야함


[참고 사이트]
여기도 MVC, 저기도 MVC! MVC 패턴이 뭐야?

MVC 창시자가 말하는, MVC의 본질

[10분 테코톡] 🧀 제리의 MVC 패턴

JavaScript MVC 패턴으로 만드는 SPA

0개의 댓글