메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

MVC 모델과 Observer 패턴

한빛미디어

|

2004-03-24

|

by HANBIT

22,168

저자: 김대곤

이 패턴을 설명하기 전에 MVC 모델을 먼저 살펴보자. MVC 모델은 조금은 거창하게 알려져 있는 듯하다. 거의 그런 경우가 드물지만, 몇 년 전 필자가 본 누군가의 이력서에는 자신의 강점을 프로젝트에 MVC모델을 적용한다는 것이라고 적혀 있는 걸 보았다. 필자가 보기에 MVC 모델을 적용한다는 것은 달리기를 한다는 말과 같아 보인다. 필자도 달리기를 한다. 그러나 잘 하지는 못한다. 국민학교(초등학교)때부터 체육은 언제나 주저하게 되는 분야였다. 달리기를 잘하는 것과 달리기를 하는 것은 다른 문제이다.

MVC 모델에서 MVC는 각각 Model, View, Control를 말한다. Model은 데이터 또는 기본 기능을 말하고, View는 유저 인터페이스를 말한다. 이 두가지는 시스템 개발에 있어서 없어서는 안되는 부분이며, 누가 개발을 하더라도 반드시 있기 마련이다. MVC 모델은 C 모델이라고 불러도 상관없을 만큼 한마디로 말해서 Control이라는 레이어 계층을 두자는 것이다. Graphical User Interface를 사용할 때, Model 계층과 View 계층 사이에 Control 계층을 만들어서 사용하자는 것이다. 왜 그렇게 해야 하는가? 여기에는 몇가지 가정이 담겨 있다.

두 계층 사이에 필수적이지 않은 다른 계층을 두는 이유는 간단하게 말해서 두 계층이 직접적으로 Coupling되는 것을 피하려고 하기 때문이다. 즉, Control 계층은 Model 계층과 View 계층의 high Coupling을 막아준다. Model 계층과 View 계층의 high coupling이 좋지 않은 이유는 Model과 View의 생명주기가 다르기 때문이다. 일반적으로 Model 계층의 생명주기가 길며, 변경이 드물게 일어난다고 여겨진다. 예를 들어, 물건을 파는 웹 사이트에서 사이버 매장을 새 단장하는 경우를 생각해 보자. 일반적으로 View만 바뀌지 Model은 바뀌지 않는다. 같은 기능을 가진 시스템에서 사용자에 따라서 다른 View가 존재할 수도 있다. 또는 웹사이트에서도 보고, PDA에서도 보는 경우도 같은 Model이 서로 다른 View로 표현되는 예라고 할 수 있다. 직접적인 Coupling은 Model과 View를 같이 바뀌도록 만든다. 이것을 Control 계층이 막아준다. 그럼 View가 바뀌면 Control이 바뀌어야 하는 것이면 마찬가지 아닌가 하는 의문이 생긴다. Control 계층의 로직은 매우 간단한다. 아니 간단하게 설계해야 한다. 적어도 Model를 바꾸는 것보다 훨씬 쉽게. Control 계층은 단지 Coordinator에 불과해야 하기 때문이다.

MVC 모델은 Layered Style이고 Layered Style에서는 아래 Layer가 상위 Layer를 사용하는 것을 권장하지 않는다. A 계층이 B 계층을 사용하게 되면, A 계층은 B 계층에 종속된다. 즉, B 계층이 없으면 A 계층은 자신의 역할을 수행하지 못하는 것이다. B 계층은 A 계층이 없어도 문제가 없다. 이러한 의존관계를 Usage dependency 라고 부른다. Layered Style에서 상위 계층은 하위 계층에 대해 Usage dependency가 있다. 하위 계층이 더 생명주기가 길며, 변경되는 일이 적어야 한다. 그런데 하위 계층이 상위 계층에 있는 기능을 사용하면 하위 계층이 상위 계층에 종속되어 버린다. 만약, MVC 모델에서 Model이 View의 기능을 사용해야 하는 경우에 Model이 View에 종속되는 것을 어떻게 막을 것인가?

이것이 Observer 패턴을 사용하게 되는 중에 하나이다. 이제 Observer 패턴에 대해서 살펴보자. GoF의 디자인 패턴에는 Observer 패턴의 목적을 이렇게 정의하고 있다. "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and update automatically." "한 객체의 상태가 바뀔 때 다른 객체들에게 그 사실을 알려주고 자동적으로 업데이트 되도록 하고자 한다면 이 패턴을 쓰시오" 정도 될 것이다. 좀 걸리는 단어는 one-to-many dependency다. 이제 하나씩 살펴보도록 하자.

한 객체의 상태에 변화가 생기면, 그 객체의 상태에 관심있는 객체들에게 알려주는 방식이다. 객체에게 무엇인가를 알려주는 방법은 객체의 메소드를 통해서 이다. 관심 있는 객체의 메소드를 호출해주면 된다. 하지만 객체의 메소드를 사용하면, 이 객체에 대해 usage dependency가 생기게 된다. 즉, coupling이 높아진다. 이 패턴은 특정한 알려주는 방식을 제안하고 있다.

이 패턴이 어떻게 작동되는지 살펴보자. 예제 파일[Download]을 실행해 보자. 아래의 두 화면이 보인다. Input data 에 데이터를 입력하면 Observer 화면에 나타나도록 되어 있다.
그림1

그림2

"Subject.java"에 있는 Vector 데이타가 바뀔 때 자동적으로 바뀐 내용을 보여주려고 한다. Subject 객체가 Observer객체의 내용을 바꾸기 위해서는 Observer객체의 메소드를 호출해야 하고, 메소드를 호출하기 위해서는 그 객체의 Reference가 필요하다.

Subject 객체의 addObserver라는 메소드를 통해 Observer는 자신의 Reference를 제공하고, Subject는 Observer 객체(MyFrame.java)의 update(Vector v) 메소드를 통해 화면을 바꾸어 준다. 별 무리 없이 작동하고 있다. 문제점은? MyFrame 클래스를 삭제해 보면 알게 된다. Subject 클래스가 더 이상 컴파일 되지 않는다. 이유는 간단하다. 자신의 Observer였던 MyFrame 클래스를 소스안에 포함하고 있었기 때문이다. 만약, 객체의 상태에 관심을 가지고 있는 객체가 오직 하나이고, 변경될 소지가 적다면, 위와 같이 코딩해도 별 문제는 없을 것이다. 하지만 Observer가 한 개가 아니라 20개 정도 되면 20 개나 되는 타입의 클래스를 포함하고 있게 된다. 그 중에 updata(Vector v)라는 메소드를 가지고 있지 않은 클래스라도 있으면 그건 바로 에러가 된다.

이러한 문제를 해결하는 방법은 Subject가 각 Observer들의 클래스 타입 대신 인터페이스를 사용하고, 이 인터페이스는 자신을 구현하는 클래스들에게 update(Vector v)라는 메소드를 구현하도록 강제한다. [Download]

코드를 자세히 살펴보면, 이 Observer 패턴과 자바의 이벤트 처리가 같음을 알 수 있다. 리스너(ActionListener)를 만들고, 리스너를 Subject(JButton)에 등록하고 JButton의 상태가 변하면 ActionListener로 강제된 actionPerformed 메소드가 호출된다. 이 패턴도 Observer 인터페이스를 구현하는 객체를 만들고, 만든 객체를 Subject에 등록하고, Subject 객체는 자신의 상태가 바뀌면, 인터페이스에 정의된 메소드를 호출한다. 즉 Observer 패턴은 이벤트 시스템을 구현할 때 사용된다. 이벤트 시스템의 구현은 Observer 패턴이 주는 또 하나의 강점이다.

하지만 기본은 전혀 바뀌지 않았다. 객체에게 무엇인가를 알려주는 방식은 메소드이고 메소드의 호출을 위해서는 객체의 Reference가 필요하는 것. 조금 다른 방식일 뿐 이 두가지 작업은 패턴을 적용해도 피할 수 없는 것이다. 즉, 패턴은 어떤 문제들을 교묘히 피해가는, 평소에는 잘 생각나지 않는 방법을 알려주고 있다. 그래서 패턴을 언제 사용하는가에 대한 이해는 외우지 않고 있으면 잘 생각나지 않고, 사용하지 않으면 이해되지 않는 측면이 있다. 패턴은 처음 적용해 볼 때 가장 어렵다. 한 번 하고 나면 생각만큼 어렵지는 않다.

필자의 패턴 설명은 전체적으로 모든 내용을 다루고 있진 않다. 일반적으로 Subject는 attach(Obsever obs), detach(Observer obs), notifyAll() 이라는 메소드를 가지고 있다. 하지만 detach 메소드는 다루지 않았다. 인터페이스가 강제하는 메소드도 여러 형태가 될 수 있으며, 그 메소드에 내용이 전달되지 않고 Subject의 다른 메소드를 호출함으로 Subject의 상태를 가져오기도 한다. 자세한 사항은 Design Pattern 책을 참고하기 바란다. Jstorm(www.jstorm.pe.kr)에서도 Design Pattern에 관련된 자료들을 찾을 수 있으리라 생각된다.
TAG :
댓글 입력
자료실

최근 본 상품0