본문 바로가기

OOP, FP/디자인패턴

MVC, MVP, MVVM 패턴

다음주부터 시작되는 프로젝트에서 ext.js 를 사용한다고 한다.

UI 컴포넌트들을 클래스 기반으로 사용할 수 있게 해주며 다양한 테마와 환경을 제공해준다.

GPL라이센스로 무료 사용가능하고, private하게 사용하려면 라이센스를 구매해야한다.


ExtJS는 애플리케이션 아키텍처로 MVVM 패턴을 제공한다.

MVC 패턴도 버겁던 나에게 이러한 개량형 패턴은 더욱 생소했다.

추후에 다시 보기위해 내가 이해한 내용만 정리하려고 한다.


 

1. MVC 패턴(Model-View_Controller)
대학교 시절 JAVA를 공부하고 JSP로 웹을 만들게 되었을 때,

그러니까 객체고 뭐고 아무것도 모르던 시절에 팀프로젝트로 펜션 호스팅 사이트를 개발했었다.


펜션이 제공하는 방의 목록을 표시하는 JSP페이지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ page import="java.sql.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>Insert title here</title>
<script type="text/javascript" src="js/script.js"></script>
<link rel="stylesheet" href="css/styles.css">
<script src="Scripts/AC_RunActiveContent.js" type="text/javascript"></script>
<!-- jQuery library (served from Google) -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<!-- bxSlider Javascript file -->
<script src="js/jquery.bxslider.min.js"></script>
<!-- bxSlider CSS file -->
<link href="css/jquery.bxslider.css" rel="stylesheet" />
<script src="js/script.js"></script>
</head>
<body>
<%
    request.setCharacterEncoding("EUC-KR");
    String bDelete = request.getParameter("bDelete");
    String bUpdate = request.getParameter("bUpdate");
    String bRegister = request.getParameter("bRegister");
    if(bDelete!= null){
        if(bDelete.equals("fail")){
            out.println("<script>alert('자신의 게시물이 아닙니다.');</script>");
        }else if(bDelete.equals("ok")){
            out.println("<script>alert('삭제가 완료되었습니다.');</script>");
        }
    }
    if(bUpdate!= null){
        if(bUpdate.equals("fail")){
            out.println("<script>alert('자신의 게시물이 아닙니다.');</script>");
        }else if(bUpdate.equals("ok")){
            out.println("<script>alert('수정이 완료되었습니다.');</script>");
        }
    }
    if(bRegister!= null){
        if(bRegister.equals("fail")){
            out.println("<script>alert('자신의 게시물이 아닙니다.');</script>");
        }else if(bRegister.equals("ok")){
            out.println("<script>alert('등록이 완료되었습니다.');</script>");
        }
    }
    String pension_num = request.getParameter("pension_num");
    Class.forName("com.mysql.jdbc.Driver"); 
    try{
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/pension_db","root","1234"); 
        Statement stmt = conn.createStatement(); 
        String strSQL = "select room.* from room, pension where room.pension_num=pension.num and room.pension_num='"+pension_num+"'";
        ResultSet rs = stmt.executeQuery(strSQL);
        Statement stmt2 = conn.createStatement();
        strSQL = "select pension_name, register_id from pension where num = '"+pension_num+"'";
        ResultSet rs2 = stmt2.executeQuery(strSQL);
        String pension_name;
        if(rs2.next()){
            pension_name = rs2.getString("pension_name");
        %>
<a href="Room_Register_Check.jsp?pension_num=<%=pension_num %>&register_id=<%=rs2.getString("register_id")%>" class="serchBtn" style="margin-left: 780px;">방 등록</a>
<a href="Room_Update_Check.jsp?pension_num=<%=pension_num %>&register_id=<%=rs2.getString("register_id")%>" class="serchBtn" >방 수정</a>
<a href="Room_Delete_Check.jsp?pension_num=<%=pension_num %>&register_id=<%=rs2.getString("register_id")%>" class="serchBtn" >방 삭제</a>
        <%
        while(rs.next()){
        %>
    <table class="pension" style="margin-top: 30px;">
    <tr>
    <th style="width:300px;">사진</th>
    <th><%=pension_name%>방 정보</th>
    </tr>
    <tr>
    <td>
        <div id="pension_slider">
        <ul class="bxslider">
          <li><img src="Images/<%=rs.getString("pictureURL1")%>"/></li>
          <%if(!rs.getString("pictureURL2").equals("null")){
          %><li><img src="Images/<%=rs.getString("pictureURL2")%>"/></li>    
          <%}if(!rs.getString("pictureURL3").equals("null")){
          %><li><img src="Images/<%=rs.getString("pictureURL3")%>"/></li>    
          <%}if(!rs.getString("pictureURL4").equals("null")){
          %><li><img src="Images/<%=rs.getString("pictureURL4")%>"/></li>
          <%%>      
        </ul>
        </div>
    </td>
    <td><div style="height:50%;">
    <font color"#F78080"><%=rs.getString("room_name")%></font> 정보<br>호실 : <%=rs.getString("room_num")%>
    <br>인원 : <%=rs.getString("number_people")%><br>가격: <%=rs.getString("price")%>
    </div>
    <div><hr width="100%;"><%=rs.getString("content"%> </div>
    </td>
    </tr>
    </table>
    <%}}}catch(Exception e){} %>
</body>
</html>
cs


이 당시에 나는 객체지향의 객체도 모르던 시절이었다.

위 코드를 순서대로 살펴보자.


(1) <body>태그의 시작부분에 <% ... %>라는 스크립트릿 영역에 자바 코드가 존재한다.

이는 클라이언트가 서버에게 request를 전송한 뒤 서버에서 파라미터를 토대로 서버 로직을 결정하는 부분이다. 

파라미터로 자잘한 알림 메시지를 보낸다.

이후에 JDBC를 이용하여 room의 목록을 가져온 뒤 결과를 담는다.


(2) room의 개수만큼 반복문을 사용한다.

반복문 내부에서 HTML태그를 이용해 방의 기본 정보, 이미지 등을 표시한다.


위 코드의 문제점은 무엇일까?

우선 코드 파일 자체는 .jsp 확장자이며, 브라우저가 최종적으로 수신하는 페이지는 .html이다.

사용자에게 보여지는 표현계층에 비즈니스 로직(JDBC, 쿼리, 분기.....)이 함께 들어있는 것이 문제가 된다.


이 jsp파일을 수정하기 위한 이유는 표현계층의 변경, 비즈니스 로직, 표현계층의 로직과 같이 여러가지가 된다.

또한, 같은 비즈니스 로직일지라도 여러 jsp파일에 중복으로 작성해야한다.

 

 

MVC 패턴의 등장배경

표현계층과 데이터를 처리하는 비즈니스 로직을 분리하여 중복 코딩을 방지하며

여러 로직과 데이터를 재사용하기 위하여 등장하게 되었다.


MVC 패턴

표현계층, 데이터모델 그리고 이들 상호간의 흐름제어를 처리하는 비즈니스 로직을 분리하여

상호간의 영향이 없이 재사용과 확장이 가능한 패턴

위에서 표현계층을 View(V), 데이터모델, 처리를 Model(M), 상호간의 흐름제어를 Controller(C)라고 부른다.

 


MVC 패턴의 단점

뷰는 컨트롤러와, 컨트롤러는 모델과, 다시 뷰는 모델과 각각 의존을 하게 된다.

위의 흐름도를 보면 뷰에서 이벤트가 일어나면 컨트롤러에게 이를 알려야 한다.

컨트롤러는 다시 이러한 알림을 받게 되면 내부에서 흐름을 제어하고 알맞은 모델에게

로직을 요청해야 한다.

모델은 단순히 결과를 반환만 하기 때문에 어떤 컨트롤러가 자신을 호출했는지 신경쓰지 않아도 되므로 의존을 갖지 않는다.

마찬가지로 컨트롤러도 업데이트를 알림만 하기 때문에 어떤 뷰가 자신을 호출했는지 신경쓰지 않아도 된다.

마지막으로 뷰는 업데이터 알림을 받으면 VO든 Map이든 모델에서 데이터를 가져와야 한다. 


2. MVP(Model-View-Presenter)

 

MVP 패턴의 등장배경

MVC 패턴의 단점을 보완하기 위하여 등장한 패턴.

위에서 설명한 것처럼 MVC 패턴은 뷰가 모델에 강력하게 의존한다.

이러한 뷰와 모델의 각 요소를 보다 명확하게 분리하여 커플링을 낮추기 위해 등장한 패턴이 MVP패턴이다.

 

MVP 패턴

MVC에서 흐름제어를 담당하는 컨트롤러를 프레젠터로 대체한다.

프레젠터가 뷰와 모델을 참조하여 뷰와 모델은 서로 의존성을 갖지 않는다.

프레젠터가 중간자 역할을 담당하여 모델의 갱신과 뷰의 갱신을 처리한다.


 

 

MVC -> MVP
MVC 패턴의 경우 뷰는 사용자 동작이 일어나면 컨트롤러와 통신하고, 변경에 대한 알림을 받는다.

알림을 받은 뷰는 자신을 새로 표현하기 위해 모델에 대한 세부적인 정보(상태 정보)를 활용해야 한다.

이는 뷰와 모델간의 커플링을 높이므로 이를 명확하게 분리하여 커플링을 낮출 필요가 있다.

MVP 패턴은 뷰와 모델간의 커플링을 줄이기 위해 프레젠터가 모든 일의 중간자 역할을 한다.

 

3. MVVM(Model-View-ViewModel)

 

MVVM 패턴의 등장배경

MVC와 그 계량형 패턴들은 훌륭한 구조를 가지고 있지만 뷰, 로직에 이어 전체 애플리케이션에 프로그래머가 개입해야 한다.

개발자가 구현해놓은 화면에서 디자이너가 어떤 요소의 id나 class명을 바꾼다거나 하는 행동을 하면

해당 화면의 데이터나 요소가 제대로 표현되지 않을 수 있다.

 

이전의 개발방법은 위처럼 디자이너와 개발자의 관계가 너무 붙어있다.

이벤트를 실행하려면 각각의 요소에 name, id 혹은 class와 같은 식별자를 주어야 하기 때문에 복잡하게 엮일 수가 있다.

개발자가 뷰에서 #btn-file-del라는 id를 가진 버튼의 이벤트에 .file-del 이라는 요소만 삭제하도록 구현해놨는데,

디자이너가 개발자와 의논 없이 fiel-delete에 디자인을 입히거나 변경했다면 엉뚱한 위치에 표현되거나 기능이 동작하지 않는다.

UI 플랫폼이 발달하면서 모델, 컨트롤러와 프레젠터보다 뷰의 비중이 높아지면서 각 뷰의 데이터와 커맨드를 분리해야 한다.

이를해결하기 위해 MVVM 패턴이 등장한다.


 

MVVM 패턴

MVP의 프레젠터 컴포넌트가 뷰모델로 대체된다.

프레젠터가 뷰와 모델을 참조했던 것 중 뷰와의 의존이 직접적인 참조가 아니라

Event Driven 형태로 간접적인 접근을 수행함으로써 뷰와 뷰모델 사이의 연결고리를 약하게 만든다.

이로서 디자이너의 영역인 뷰와 프로그래머의 영역인 뷰모델-모델 영역이 분리되면서,

서로에게 최소한의 영향만 주며 작업을 수행할 수 있다.

 

뷰모델은 뷰를 추상화하며 뷰에 대해서는 아무것도 몰라도 되며,

단지 여러개의 뷰가 하나의 뷰모델을 참조하는 1:N 관계일 뿐이다.

 

 

 

 

View

뷰는 단순히 사용자 인터페이스를 표시하기 위한 로직만을 담당한다.

사용자의 입력정보를 수신하고, UI요소를 그린다.

뷰와 프레젠터의 역할을 모두 포함하게 된다.

 

이전에 포함되어있던 데이터와 커맨드(이벤트)는 ViewModel이 갖게 된다.

 

ViewModel

뷰모델은 View가 바인딩할 수 있는 프로퍼티와 커맨드를 담당한다.

뷰에게 보여줘야 할 데이터와 수정에 필요한 로직을 가지고 있다.

 

Model

모델은 응용 프로그램의 데이터 및 비즈니스 로직을 담당한다.

 

MVP와 매우 비슷하지만 큰 차이점이라면 비중이 View에 치중되어있다는 것이다.

프레젠터는 뷰에 상당한 의존성이 있었지만, 뷰모델은 뷰와 아무런 관련이 없다.

뷰는 하나의 뷰모델을 선택하여 데이터 바인딩을 수행하고 업데이트를 받는다.

모델이 비즈니스 로직의 순수한 모델이라면 뷰모델은 뷰를 위한 모든 커스터마이징을 제공하는 모델이다.

 

ExtJS

sencha ExtJS의 서비스 아키텍쳐는 뷰모델이 뷰모델과 뷰컨트롤러로 분리된다.

뷰는 여러가지 컴포넌트를 확장하고 조립하여 UI를 표현하며 뷰모델과 뷰컨트롤러를 지정한다.

뷰컨트롤러는 뷰를 추상화한 뷰모델의 이벤트를 정의한다.

뷰모델은 뷰를 추상화한 뷰모델의 데이터를 정의한다.

 

모델과 스토어는 별개로, 애플리케이션에서 정보의 게이트웨이 역할을 한다.

모델은 애플리케이션에서 지속 가능한 모든 형태의 데이터,

스토어는 클라이언트 측면에서 모델 클래스의 인스턴스를 캐싱한 컴포넌트로서 담고 있는 데이터의 정렬, 필터링 및 질의기능을 제공한다.

 

간단한 요약이므로 자세한 가이드는 https://wikidocs.net/2964를 참조하면 된다.