글을 쓰다보니 길어져 부득히 시리즈로 나눴다. 항상 이렇게 시리즈로 나누면 중간에 쓰다 마는데.. ㅇㅎㅎ 여튼 뭐 언젠가는 마무리하겠지.. 사실 이글에 앞서 “UML로 코딩하기라는 글”이 있는데 글이 마무리가 안되서, 마무리 되면 공개하는 걸로 하고 그냥 시이작~!
자바스트립트를 보다 구조화해서 코딩 할 수 없을까?
MVC패턴은 요즘 나의 최대 관심사다. 최근 몇년간 TDD니 BDD니 하면서 날코딩과 테스트를 중심으로 작성했던 코드들도 수두룩하다. 하지만 이 코드들은 전부 유지보수에 실패했다! 물론 실패할수밖에 없었던 구구절절한 사연들이 많지만 그건 중요한게 아니므로 패스~ 여튼 결국 난 테스트 주도 프로그래밍에 대해 다시 한번 생각해볼 수 밖에 없었다. 물론 UI 개발에 한정적인 얘기지만 TDD는 뭔가 UI 개발과는 어울리지 않는다는 결론에 이르렀다. 그게 벌써 2년전이다. 그렇게 해서 찾기 시작한 대안이 바로 설계를 바탕으로 하는 프로그래밍이다. 사실 설계라는 것이 스펙에 따라 계속 변경된다고 믿었기에 초기 설계에 그렇게 큰 의미를 부여하진 않았다. 하지만 최근 1년간 MVC 패턴을 이곳저곳에 적용하면서 이른 나의 결론은 초기설계가 중요하고, 잘못된 설계는 나중에 영영 되돌릴수 없다라는 결론도 얻었다. 여기도 사실 현실적인 구구절절한 사연이 있지만.. 패스~ 하지만 확실한 것은 MVC 패턴을 이용한 프로그래밍이 가장 만족스러웠다는 사실이다. 여튼 현실 얘기는 짚어치우고 오늘은 자바스크립트를 이용해 MVC 코드를 어떻게 작성할수있을까? 만을 고민해보자. 사실 나도 MVC를 제대로 이해하고 쓰고 있는건가? 라는 생각을 종종한다. 그만큼 생각의 변화가 많았다. 아마도 공력이 더 쌓이면 또 생각이 바뀌지싶다. 여튼 각설~
자바스크립트로 MVC 구현하기
MVC를 그림으로 그려보면 그 중심에는 당연히 컨트롤러가 있다. 그래서 컨트롤러가 가장 중요한게 아닐까? 라는 생각을 하게 되는데.. 사실 그 말이 맞기도하고 그렇치 않기도하다. 내가 생각할때 MVC는 모델과 뷰 그리고 컨트롤러가 모두 중요하다. 그리고 그 각자의 역할이 매우 명확한데 이것을 최근에야 깨달았다.
모델 설계
모델은 매우 중요하다. 특히 모델 구현에 있어서 가장 중요한 것은 Observer 라고 얘기하는 관찰자를 만드는 것이다. 흔히들 MVC 패턴으로 설계된 UML들을 보면 옵저버패턴이 등장하고, 아래 그림과 같이Observer 라는 이름의 클래스도 등장한다. 참고로 난 패턴 용어를 잘 모른다. 어쩌다보니 자연스럽게 알게된 경우가 대부분이다. 혹시 이 글을 읽고 있는 분들도 책으로만 패턴을 공부하지 않기를 바란다. 여튼 훈수는 여기까지… ㅋ
여기서 Observer 라는 단어의 뜻을 한번 생각해보자. “관찰” 이라는 뜻인데,.. 관찰하다라는 말이 무슨 말일까? 스타크래프트의 옵저버를 연상해도 좋다!. 여튼 옵저버는 현재 상황을 유심히 살펴보다가 누군가에게 알려주는 역할을 한다. 이때 누군가에게 알려주려면 +notify() 메소드를 사용한다.
그럼, Subject는 무슨 뜻일까? 이건 좀 번역하기 애매하다. 여튼 UML에 그려진 대로만 해석하면 Subject 클래스는 옵저버 객체를 관리하는 녀석이다. 관리할 옵저버는 +registerObserver() 메소드를 이용해 등록한다. 그리고 이렇게 등록된 옵저버는 +observerCollection 이라는 변수에 담아 관리한다는 사실도 위 설계도를 보면 알 수 있다.
옵저버 객체 구현
여기까지가 일반적인 옵저버 패턴이고 저 설계대로 구현하려면, 저 설계의 의미를 곱씹어 봐야한다. 일단 Observer와 ConcreteObserverA와의 관계는 상속관계다. 의심할 여지없이 명확하다. 구현도 어렵지 않으므로 패스~!
다음으로 Observer와 Subject와의 관계를 살펴보자. 먼저 Subject 클래스쪽에 빈 마름모가 등장했다. 빈 마름모의 의미는 Subject 객체가 어디선가 생성된 Observer 인스턴스(혹은 객체)를 인자로 받는다는 의미다. 그리고 여기서 또하나 주목해야 할 것이 있다. 바로 의존관계다!. 보통 의존관계는 화살표로 표시하는데, 이 두관계는 눈을 씻고 찾아봐도 화살표가 없다! 그럼 서로 의존성이 없다는 얘긴가? 땡~~!! 틀렸다! 선이 연결되어 있으므로 서로가 서로를 의존한다고 봐야한다.
여튼, Observer와 Subject는 화살표 없이 실선으로 연결되어 있으므로 서로가 서로를 알고 있고, 서로 간의 호출 할수있는 참조를 가지고 있다는 의미가 된다. 그리고 보통 이런 경우는 두 객체를 하나로 합체할 수있다! 뚜둥~! 그래서 그림을 다시 그려보았다.
이제 이 설계를 기반으로 구현해보자. 다른 언어로 구현해도 마찬가지겠지만, 클래스가 많아지면 각 클래스간의 역할관계를 정의해야하기 때문에 복잡해진다. 그래서 일단 편한대로 내식대로 옵저버를 설계해봤다.
+ observe( 메시지, 행동 ) : 관찰할 메시지를 등록하고, 메시지를 받으면 행동을 정의한다.
+ notify(메시지, 정보) : 관찰하고 있던 상태가 변경하면 누군가에게 알린다.
옵저버 패턴을 구현할때 핵심은 이벤트를 어떻게 전달하냐인데, 이벤트를 전발하는 방식은 대부분 콜백으로 이루어진다. 즉 콜백함수를 등록해놓고 호출되도록 만든다. 여기서 문제는 자바스크립트가 싱글 스레드라는 사실이다. 싱글 스레드이기 때문에 함수 호출 시점에 따른 의존성도 생길수 있다. 예를 들면 아래와 같다.
옵저버 객체에서 MethodA() 호출후, notify() 함수를 통해 외부에 MessageC를 날렸다. 그리고 MethodB()를 호출한 상황인데, MessageC 를 받는 객체에서 어떤일이 행해진다면, MethodB()의 호출 시점은 MessageC 를 구현한 객체에 따라 달라진다.
MethodA -> MessageC ( 여러 메소드가 중간에 낑겨들어갈수있음…) -> MethodB
위와 같은 흐름이 보장 된다면, MessageC에서 데이터를 저장하고, MethodB에서 저장한 데이터를 전제로 구현을 해도 큰 문제가 발생하지 않는다.
하지만, MessageC에서 Ajax 호출이 발생하면 동기식 상황들은 깨지고 만다. 따라서 완간하면 notify() 함수 구현은 반드시 동기식 호출만 하거나 혹은 아예 비동기를 전제로 작성하는게 맘 편할수있다.
나는 그냥 비동기를 가정하고 notify() 호출은 setTimeout을 이용해 메시지 큐에 담아 버렸다. 따라서 위와 같이 호출시 메소드 호출순서는 아래와 같다.
MethodA -> MethodB -> MessageC
여튼 실제 구현한 Observable 객체 다음 링크를 참고한다.
옵저버 객체를 이용한 모델 구현
이제 앞에서 작성한 옵저버 객체를 이용해 모델 객체를 구현해보자. 일단 모델은 Observable 객체를 상속받는다. 자바스크립트는 프로토타입을 이용해 상속받는다. 프로토타입 상속에 관련된 내용은 자바스크립트 가든 번역본을 참고한다.
var SampleModel = function(){}; SampleModel.prototype = new Observable();
다음으로 모델에 저장할 데이터를 정의한다. 데이터를 정의할때는 public으로 정의하고 싶다면 this 객체를 이용해 정의하고, private 으로 정의하고 싶다면 var 문을 이용해 정의한다.
var SampleModel = function(){ this.publicData = {}; var privateData = {}; this.getPrivateData = function(){ return privateData; } }; this.getPublicData = function(){ return this.publicData; };
공개 데이터는 아래와 같이 프로토타입 공간에 메소드를 정의해 가져올수도 있다. 하지만 아래 방법보다는 공개 데이터이므로 프로퍼티로 직접 접근해 사용하는 것이 보다 효율적이다.
SampleModel.prototype.getPublicData2 = function(){ return this.publicData; }
옵저버 객체에 감지할 이벤트 등록
자, 그럼 옵저버 객체를 이용해 감시할 이벤트를 정의해보자.
var model = new SampleModel(); model.observe({ "CHANGE_STATE" : function(e){ console.log(e.data) } });
위 코드는 model에서 발생하는 CHANGE_STATE 메시지를 감지한다.
모델에서 이벤트 발생
자 그럼 CHANGE_STATE 메시지를 직접 발생시켜보자. 메시지는 모델에서 상태가 변경됐을때 발생함을 전제하므로 setData 함수를 만들어 정의한다.
SampleModel.prototype.setData = function(data){ this.publicData = data; this.notify("CHANGE_STATE", {data: this.publicData}); }
여기까지 모델을 살펴봤다. 사실 글쓰다 귀찮아서 마지막은 후다닥 코드만 썼는데.. 나중에 View편을 작성할때 좀더 보강하기로 하고, 오늘은 여기까지… 혹시 질문있으면 댓글 고고씽~