[디자인 패턴] MVP, MVVM, Flux (feat. React)

Narcoker·2023년 7월 31일
1

디자인 패턴

목록 보기
3/8

MVP 패턴이 생기된 원인

MVC 패턴의 다음 단계로, MVC의 단점을 해결하기 위함이다.

MVC 패턴에서 Controller는 View 를 선택만 할 뿐이다.
즉, 직접 업데이트하지 않는다.

View 를 업데이트 하기 위해서 아래와 같은 방법들이 있다.

  • View가 Model을 직접 이용하여 업데이트
  • Model에서 View에게 Notify 하여 업데이트
  • View가 Polling 하여 Model의 변화를 감지해서 업데이트

즉 View 를 업데이트 하기 위해서
결국은 Model과 View 사이에서 의존성이 필요해진다.

이를 해결하기 위한 기법이 MVP 이다.

React 에서의 MVC

🔴 React.js는 기본적으로 MV* 패턴을 따르지 않지만,
Hook 등의 기능을 통해 유사한 구조를 만들 수 있다.

지금부터 나올 예시중 일부(MVC,MVP,MVVM) 본인이 이해를 위해서
React 코드를 첨부한 것이니 오해없길 바란다.

Redux를 사용하면 MVC 패턴을 적용할 수 있다.

Model - Redux의 Reducers

// reducers/todo.js
export default function todo(state = [], action) {
    switch (action.type) {
        case 'ADD_TODO':
            return [...state, action.payload];
        default:
            return state;
    }
}

View - React Components

// components/TodoList.js
import React from 'react';
import { useSelector } from 'react-redux';

function TodoList() {
    const todos = useSelector(state => state.todos);
    return (
        <ul>
            {todos.map(todo => (
                <li key={todo}>{todo}</li>
            ))}
        </ul>
    );
}

export default TodoList;

Controller - Redux Actions and React Component Dispatches

// actions/todo.js
export function addTodo(todo) {
    return {
        type: 'ADD_TODO',
        payload: todo
    };
}

// components/AddTodo.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from '../actions/todo';

function AddTodo() {
    const [todo, setTodo] = useState('');
    const dispatch = useDispatch();

    const handleSubmit = (event) => {
        event.preventDefault();
        dispatch(addTodo(todo));
        setTodo('');
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                value={todo}
                onChange={event => setTodo(event.target.value)}
            />
            <button type="submit">Add</button>
        </form>
    );
}

export default AddTodo;

Controller에서 Model(reducer)를 업데이트하고 View 를 변경하는 구조

MVP

MVC 패턴에서 파생된 모델로,
Model과 View 간의 의존성이 없는 아키텍처 패턴

MVP (Model-View-Presenter) 패턴은 MVC와 비슷하지만,
Controller 대신 Presenter를 사용한다.

Presenter는 사용자의 입력을 Model로 전달하고,
Model로부터 받은 데이터를 View에 전달하여 UI를 업데이트한다.

Presenter는 해당 View를 참조하고 이다.
이때 View와 Presenter는 1:1 관계이다.

Presenter는 View와 Model 인스턴스를 가지고, Model과 View 사이의 매개체 역할을 한다.

장점

  • Presenter가 M-V 사이에서 관리를 해주기 때문에, MVC 패턴과는 달리 M-V 사이의 의존성이 없다.

단점

  • 앱이 커지거나 복잡해질수록 V-P 간 의존성이 강해지는 문제점이 발생한다.
  • View와 Presenter가 1:1 관계이기 때문에 서로 간 의존성이 커진다.
  • 필요한 클래스 개수가 많다.

React에서의 MVP

Model

TodoModel은 일정 목록을 관리하는 모델

// todoModel.js
export default class TodoModel {
  constructor() {
    this.todos = [];
  }

  addTodo(title) {
    const newTodo = {
      id: Math.random().toString(36).substr(2, 9),
      title,
      completed: false,
    };
    this.todos = [...this.todos, newTodo];
  }

  removeTodo(id) {
    this.todos = this.todos.filter((todo) => todo.id !== id);
  }

  completeTodo(id) {
    this.todos = this.todos.map((todo) => (todo.id === id ? {...todo, completed: !todo.completed} : todo));
  }
}

View

TodoView는 사용자 인터페이스를 담당하는 React 함수형 컴포넌트

사용자의 입력에 대한 처리는 Presenter에 위임하고,
Presenter로부터 일정 목록의 업데이트를 받아 화면을 갱신한다.

// React Component
import React, { useState } from 'react';
import TodoPresenter from './TodoPresenter';

const TodoView = () => {
  const [todos, setTodos] = useState([]);
  const todoPresenter = new TodoPresenter();

  const addTodo = (title) => {
    const newTodos = todoPresenter.addTodo(title);
    setTodos(newTodos);
  };

  // Render todo list here
  // ...

  return (
    <div>
      {/* Implement todo input and submit button here */}
      {/* On submit, call `addTodo` with input value */}
    </div>
  );
};

export default TodoView;

Presenter

TodoPresenter는 뷰에서 발생하는 이벤트를 처리하고 모델을 업데이트한다.
MVC 에서의 Controller와 유사하나 View 를 업데이트하지 않는다.

// todoPresenter.js
import TodoModel from './TodoModel';

export default class TodoPresenter {
  constructor() {
    this.model = new TodoModel();
  }

  addTodo = (title) => {
    this.model.addTodo(title);
    return this.model.todos;
  };

  removeTodo = (id) => {
    this.model.removeTodo(id);
    return this.model.todos;
  };

  completeTodo = (id) => {
    this.model.completeTodo(id);
    return this.model.todos;
  };
}

MVVM

MVC에서 파생된, Model과 View 간의 의존성뿐만 아니라

Controller와 View 간의 의존성도 고려하여
각 구성 요소가 독립적으로 작성되고 테스트될 수 있도록 설계된 아키텍처 패턴

  1. 모든 입력(Input)들은 View로 전달된다.
  2. ViewModel은 입력에 해당하는 Presentation Logic을 처리하여 View에 데이터를 전달한다.
  3. ViewModel은 View를 참조하지 않기 때문에 독립적이다.
    ViewModel과 View는 1:n 관계이다.
  4. 따라서 View는 자신이 이용할 ViewModel을 선택해 바인딩하여 업데이트를 받게 됩니다.
    Command 패턴이나 Data Binding을 이용하여 V-VM 간 의존성을 없앨 수 있습니다.
  5. Model이 변경되면 해당하는 ViewModel을 이용하는 View가 자동으로 업데이트됩니다..
  6. ViewModel은 View를 나타내 주기 위한 Model이자, View의 Presentation Logic을 처리합니다

React 에서의 MVVM

React는 단방향 데이터 전달이기 때문에 MVVM을 구현할 수 없다.

MVVM 은 Model과 View 사이에서 위처럼 양방향 데이터 전달을 지원한다.
이 경우 한 View에서 일어난 상호작용 때문에 여러 모델이 변경되거나
그 반대의 일도 벌어질 수 있다.

Flux 패턴

위 문제점을 해결하기 위해서 FaceBook에서 Flux 패턴을 고안해냈다.

어디서 어느 방향으로 데이터가 전달될지 알지 못할 정도로
혼란한 MVC 패턴의 복잡성을 해소하기 위해,
Flux 패턴에서는 데이터가 한 방향으로만 흐르도록 했다.

이러한 것을 지원하기 위해 Redux 라는 것을 만들어냈다

React 에서의 Flux

Model

Model은 애플리케이션의 상태(state)와 상태를 변경하는 함수(reducer)를 포함

// Model
const initialState = { todos: [] };

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    default:
      return state;
  }
};

View

View는 사용자에게 보여지는 UI를 담당하고, 사용자의 입력을 ViewModel에 전달합니다:

// View
const TodoList = ({ todos, addTodo }) => {
  const [input, setInput] = useState('');

  const handleAddTodo = () => {
    addTodo(input);
    setInput('');
  };

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

ViewModel

ViewModel은 Model의 상태를 View에 전달하고, View의 요청을 Model로 전달하는 역할

// ViewModel
const mapStateToProps = (state) => {
  return { todos: state.todos };
};

const mapDispatchToProps = (dispatch) => {
  return {
    addTodo: (todo) => dispatch({ type: 'ADD_TODO', payload: todo })
  };
};
profile
열정, 끈기, 집념의 Frontend Developer

0개의 댓글