본문 바로가기

OOP, FP/디자인패턴

헤드퍼스트 디자인 패턴: 15. MVC 패턴


지난 포스팅에선 여러 패턴을 섞어서 강력한 객체지향 디자인을 만드는 컴파운드 패턴에 대해 알아보았다.


이번에는 매우 강력한 컴파운드 패턴 중 하나인 MVC 패턴에 대해 복습한다.


MVC 패턴




(1) View

- 모델을 표현하는 방법을 제공하는 사용자 인터페이스.

  일반적으로 화면에 표시하기 위해 필요한 상태 및 데이터를 모델에서 직접 가져온다.


(2) Model

- 모든 데이터, 상태 및 어플리케이션 로직이 들어있다.

  뷰와 컨트롤러에서 모델의 상태를 조작하거나 가져오기 위한 인터페이스를 제공하고

  모델에서 자신의 상태 변화에 대해서 옵저버들에게 알려주긴 하지만

  기본적으로 모델은 뷰와 컨트롤러에 별 관심이 없다.


(3) Controller

뷰와 모델 사이에서 위치하며 사용자로부터 입력을 받아서 그것이 모델에게 어떤 의미가 있는지 파악한다.

  단순히 모델한테 전달하는 역할만 하는것이 아니라, 사용자가 입력한 것을 해석해서

  그것을 바탕으로 모델을 조작하는 임무를 맡고있다.


- 뷰에서 컨트롤러의 역할을 직접 맡아도 되지만, 그렇게 하지 않는 것이 좋다.

  첫 번째로 뷰가 두 가지 역할을 맡기 때문에 코드가 복잡해지고 유지보수에 좋지 않다.

  두 번째로 뷰를 모델에 너무 밀접하게 연관시켜야 한다는 문제가 있다.

  때문에 뷰를 다른 모델하고 연결해서 재사용하기가 어려워진다.


- 컨트롤러는 뷰와 모델의 결합을 끊어주는 역할을 한다.

  뷰와 컨트롤러를 느슨하게 결합시켜 놓으면 더 유연하고 확장하기 좋은 디자인을 만들 수 있다.


* 사용자는 뷰하고만 접촉할 수 있다.

- 뷰는 모델을 보여주는 창이라고 할 수 있다.

  재생 버튼을 누른다든가 하는 식으로 뷰에 대해서 어떠한 행동을 하면

  뷰에서 컨트롤러한테 사용자가 어떤 일을 헀는지 알려준다.


* 컨트롤러에서 모델한테 상태를 변경하라는 요청을 한다.

- 컨트롤러에서는 사용자의 행동을 받아서 해석한다.

  사용자가 버튼을 클릭한다든가 하는 어떤 행동을 하면 컨트롤러에서는 그것이 무엇을 의미하는지 분석하고

  그 행동에 따라서 모델을 어떤식으로 조작해야 하는지 결정한다.


* 컨트롤러에서 뷰를 변경해달라고 요청할 수 있다.

- 컨트롤러에서 뷰로부터 어떤 행동을 받았을 때, 그 행동의 결과로 뷰를 바꿔달라고 할 수 있다.

  예를 들어, 컨트롤러에서 인터페이스에 있는 어떤 버튼이나 메뉴를 활성화 또는 비활성화 시킬 수 있다.


* 상태가 변경되면 모델에서 뷰한테 그 사실을 알린다.

- 사용자의 행동 때문이든 다른 내부적인 변화 때문이든, 모델에서 무언가가 변경되면

  모델에서 뷰한테 상태가 변경되었음을 알린다.


* 뷰에서 모델한테 상태를 요청한다.

- 뷰에서 화면에 표시할 상태는 모델로부터 직접 가져온다.

  예를 들어, 모델에서 뷰한테 새로운 곡이 재생되기 시작했다고 알려주면

  뷰에서는 모델한테 곡 제목을 요청하고, 그것을 받아서 화면에 표시한다.

  컨트롤러에서 뷰를 바꾸라는 요청을 했을 때도 뷰에서 모델한테 상태를 알려달라고 요청할 수 있다.


예제

MVC 패턴을 공부하는 가장 좋은 방법을 MVC를 여러 개의 패턴이 함께 적용되어서 완성된 하나의 디자인으로 보는 것이다.

모델에서는 자신의 상태를 알려야 하기 때문에 옵저버 패턴을 사용해서 상태가 바뀔 때마다 뷰와 컨트롤러에게 연락한다.

컨트롤러는 뷰의 행동에 해당하기 떄문에 스트래티지 패턴을 이용해서 다른 행동을 원할 시 다른 컨트롤러로 교환하기만 하면 된다.

뷰에서는 내부적으로 컴포지트 패턴을 써서 윈도우, 버튼 같은 다양한 구성요소를 관리한다.


뷰와 컨트롤러

뷰와 컨트롤러는 고전적인 스트래티지 패턴으로 구현되어 있다.

때문에 뷰 객체를 여러 전략을 써서 설정할 수 있으며 컨트롤러가 전략을 제공한다.

뷰에서는 애플리케이션의 겉모습에만 신경쓰고, 인터페이스의 행동에 대한 결정은 모두 컨트롤러에게 맡긴다.

스트래티지 패턴을 이용하므로 뷰를 모델로부터 분리시키는 데에도 도움이 된다.

사용자가 요청한 내역을 처리하기 위해서 모델하고 얘기를 해야 하는 부분이 컨트롤러기 떄문에 뷰는 그 방법을 전혀 알지 못한다.


뷰에 속하는 디스플레이는 여러 단계로 겹쳐져 있는 일련의 윈도우, 패널, 버튼, 텍스트 레이블 등으로 구성된다.

각 디스플레이 항목은 복합 객체(윈도우 등) 또는 잎(버튼 등)이 될 수 있다.

컨트롤러에서 뷰한테 화면을 갱신해 달라고 요청하면 최상위 뷰 구성요소한테만 갱신하라고 얘기하면 된다.

나머지는 컴포지트 패턴에 의해 자동으로 처리된다.


모델

모델에서는 옵저버 패턴을 써서 상태가 변경되었을 때 그 모델하고 연관된 객체들에게 연락을 한다.

옵저버 패턴을 이용해서 뷰 및 컨트롤러로부터 완전히 독립시킬 수 있다.

한 모델에서 서로 다른 뷰를 사용할 수도 있고, 여러 개의 뷰를 동시에 사용하는 것도 가능하다.


MVC와 웹 환경

웹의 등장으로 여러 개발자들이 MVC를 브라우저/서버 모델에 맞게 변형시켜서 사용하기 시작했다.

"모델2(Model 2)" 라는 방법이 가장 많이 쓰이는데, 서블릿과 JSP 기술을 사용하여

일반적인 GUI와 마찬가지로 모델, 뷰, 컨트롤러를 분리해서 디자인할 수 있다.


(1) 사용자가 HTTP 요청을 하면 서블릿에서 그 요청을 수신한다.

- 사용자가 웹 브라우저를 이용해 HTTP 요청을 한다.

  보통 사용자 IT와 비밀번호와 같은 폼 데이터가 함께 전달된다.

  서블릿에서는 이런 폼 데이터를 받아서 파싱한다.


(2) 서블릿이 컨트롤러 역할을 한다.

- 서블릿은 컨트롤러 역할을 맡아서 사용자 요청을 처리하고, 대부분의 경우에

  모델(DB 등)에 어떤 요청을 하게 된다. 요청을 처리한 결과는 일반적으로 자바빈 형태로 포장된다.


(3, 4) 컨트롤러에서는 컨트롤을 뷰한테 넘긴다.

- 뷰는 JSP에 의해 표현된다. JSP에서는 (4 자바빈을 통해 얻은) 모델의 뷰를 나타내는 페이지만 만들어주면 된다.

  그 페이지를 만드는 과정에서 다음 단계의 작업을 위해 몇 가지 더 제어해야할 일이 있을 수도 있다.


(5) 뷰에서 HTTP를 통해서 브라우저한테 페이지를 전달한다.

- 페이지가 브라우저한테 전달되면, 그 웹 페이지가 사용자의 화면에 표시된다.

  사용자가 또 다른 요청을 할 수도 있으며, 새로운 요청도 지금까지의 과정과 같은 방식으로 처리한다.


모델2의 장점

제작 책임까지도 분리시켜주기 때문에 모델 2의 장점은 단순히 디자인적인 면에서 각 구성요소를 분리해주는 것에 그치지 않는다.

옛날에는 JSP에 접근할 수 있는 사람이라면 JAR에 대해서는 전혀 몰라도 누구든 자바 코드를 마음대로 만들 수 있었다.

하지만 대부분의 웹 제작자들은 컨텐츠와 HTML에 대해서는 잘 알지만 소프트웨어에 대해서는 그리 잘 알기 힘들다는 문제가 있었다.


모델 2가 등장하면서 개발자들은 서블릿에만 전념하면 되고, 웹 자작자들은 간단한 모델2 스타일의 JSP만 다루면 되는 환경이 조성되었다.

그래서 웹 자작자들은 HTML과 간단한 자바빈즈만 만지면 된다.



(1) 옵저버 패턴

- 고전적인 의미에서 본다면 뷰는 더 이상 모델의 옵저버라고 할 수 없다.

  모델한테 등록해서 모델의 상태가 바뀌었다는 연락을 받는다든가 하지 않기 때문이다.

  하지만 여전히 모델의 상태가 바뀔 떄 컨트롤러를 통해서 간접적으로나마 연락을 받긴 한다.

  게다가 컨트롤러에서는 뷰한테 빈을 건네주기 때문에 뷰에서 모델의 상태를 알아낼 수 있다.


- 브라우저 모델을 생각해보면, 뷰에서는 브라우저로 HTTP 응답을 할 때만 모델의 상태에 대한 정보가 필요하다.

  HTTP 응답을 하지 않는 상황에서는 모델로부터 연락을 받아봤자 할 일이 없다

  페이지를 만들어서 브라우저로 보낼 때만 모델의 상태를 반영하여 뷰를 생성하는 것이 의미 있는 행동이 된다.


(2) 스트래티지 패턴

- 컨트롤러 서블릿이 여전히 전략 객체로 쓰인다.

  하지만 고전적인 MVC에서처럼 뷰 객체에 컨트롤러 객체에 대한 래퍼런스가 들어가는 방식으로 구현되지 않는다.

  그럼에도 불구하고 컨트롤러 서블릿이 뷰의 행동을 구현하는 객체라는 점과 다른 행동릉 원하는 경우에

  다른 컨트롤러로 바꿀 수 있다는 점에서 보면 여전히 스트래티지 패턴을 따른다고 볼 수 있다.


(3) 컴포지트 패턴

- GUI와 마찬가지로 웹의 뷰도 결국은 중첩된 그래픽 구성요소로 이루어진다.

  이 경우, HTML 코드를 통해서 웹 브라우저에서 렌더링된다는 차이점이 있긴 하지만

  그 밑에는 결국 복합 객체 형태의 객체 시스템이 있을 가능성이 매우 높다.


의문점

(1) 컨트롤러에서 애플리케이션의 로직을 구현하는 경우가 있는지?

- 컨트롤러에서는 뷰를 위한 행동을 구현하기만 한다.

  사용자가 뷰에 대해 취한 행동을 모델에 대한 요청으로 바꿔주는 것이 바로 컨트롤러의 역할이다.

  모델에서는 그러한 요청을 받아서 필요한 작업을 처리하는 애플리케이션의 로직을 구현한다.

  컨트롤러에서 모델의 어떤 메소드를 호출해야할 지 결정하기 위해 분기문과 같이 어느 정도 간단한 작업을 처리할 수는 있지만,

  그런 부분은 데이터를 관리하고 조작하기 위한 애플리케이션 로직이라고 할 수 없다.


(2) 푸시 방식을 사용해서 모델이 갱신되었다는 연락을 할 때 모델의 상태도 같이 전달하면 되지 않나?

- JSP/HTML 뷰에서는 실제로 그런 방식을 사용하고 있다.

  모델 자체를 빈으로 보내면 빈 속성을 사용해서 필요한 상태를 알아낸다.

  하지만 옵저버 패턴에 대해 복습할 때 푸시 방식에 몇 가지 단점을 배웠었다.

  옵저버의 재사용성, 불필요한 정보 전달등의 단점이 있었다.



매우 강력한 컴파운드 패턴 중 하나인 MVC 패턴에 대한 복습을 마치겠다.

드디어 이펙티브 자바를 읽을 수 있다!


1. 스트래티지 패턴

2. 옵저버 패턴

3. 데코레이터 패턴

4-1. 팩토리 메서드 패턴

4-2. 추상 팩토리 패턴

5. 싱글턴 패턴

6. 커맨드 패턴

7. 어댑터 패턴

8. 퍼사드 패턴

9. 템플릿 메소드 패턴

10. 이터레이터 패턴

11. 컴포지트 패턴

12. 스테이트 패턴

13. 프록시 패턴

14. 컴파운드 패턴

15. MVC 패턴