C++을 처음 접할 때가 생각난다. 캡슐화, 상속, 다형성만 알면 OOP에 대해 될거 같았다. 아니었다. 이것들을 이용해서 어떻게 ‘잘’ 디자인할지를 훈련해야 했는데 못했다. 오랜기간 회사에서 개발자로 지냈지만, 그 때 못한 훈련이 계속 내 발목을 잡았고, 후회가 되었다. 그래서 뒤늦게 나마 디자인 패턴을 공부중이다.

나이를 먹어서인지, 경험적인 내용들은 머리에 들어오는데, 이론적인 얘기들은 휘리릭 빠져나간다. 그래서 경험적인 야매 얘기를 잠깐 해보려고.
이런 방법론은 다 사람의 한계때문에 생겨난거다. 사람 머리가 한번에 처리할 수 있는게 제약이 있거든. 예전 수업들을 때 사람의 단기기억이 7개 까지인가 그랬었다. 학생들을 대상으로 직접 테스트를 했는데, 흥미로운 지점은 그 7개 단위가 단어같은게 아니고 덩어리란거다. 교실을 예로들면, 칠판, 분필, 지우개, 교수님, 탁상, 책상, 프로젝터 이런식으로 세면 7개지만, 이걸 ‘교실’ 하나로 기억하면 그 안에 뭐가 있는지는 이미 장기기억속에 있기 때문에 기억하기 쉽다는 얘기였다. 중요한건, 이 덩어리가 7개정도를 넘어가면 한번에 처리가 안되는거지.
프로그래밍을 할 때 우리는 로직을 짜고 그걸 코드로 구현해 낸다. 그 만드는 덩치가 커질수록 복잡도는 올라가고, 고작 7개의 휘발성 메모리를 가진 사람머리로는 감당하기 힘들어진다. 디자인 패턴에서 reusable, flexible, maintainable을 얘기하는데, 사용되지 않는 죽은 코드가 아니라면, 재활용되거나 끊임없는 수정, 유지보수가 따라오게되고 그 과정에서 결국 사람머리가 처리하기 쉽게 만들란 얘기다. 다 사람이 하는 일이니까.

여기서부터 본격적인 야매얘기니까 적당히 알아서 듣기를. 사람 머리가 처리하기 쉽게 하기위해 덩어리로 만드는게 캡슐화이다. 이건 별로 어렵지 않지. 캡슐화를 잘 하려면, 완성된 concrete class는 가능한 변경이 없어야한다. 클래스 내부까지 고려해야하면 덩어리로 다룰 수 없고 사람 메모리는 부족해지니까. 대신에, 상속이나 인터페이스로 확장시킨다. 이게 Open-Close principle이다.
상속에 대해서도 잠깐 얘기해보자. 옛날에는 상속이 정말 만능인거 같았거든. 근데, 상속을 통하면 부모 클래스 수정사항이 바로 자식에게도 반영되므로, 부모와 자식간에 아주 강한 커플링으로 연결되고 상속단계가 늘어나면 복잡해지는거지. 강한 커플링이 걸렸다는건, 사람 머리에서 떼어놓고 생각할 수 없다는 얘기니까. 또한, 다중상속이라는 골치거리도 생긴다. 결국에 많은 언어들이 단일상속만 지원하고, 인터페이스만 다중상속을 지원해서 골치거리를 없애기도 했다. 이 강한 커플링을 루즈하게 만들어주는게 위임(delegate)이다. 필요한 기능을 상속을 통해 부모로부터 받는게 아니라, 다른 객체를 멤버로 갖고 있으면서(Composition) 필요한 기능들은 해당 객체의 인터페이스 메소드를 호출하여 그 객체에게 위임하는 방법이다. 이처럼 인터페이스나 추상클래스로 의존성을 만들면 커플링이 약해지고 결과적으로 프로그램이 유연해진다.

Design Pattern and Responsibility
아무것도 모를 때, 상속이나 인터페이스로 연결된 구현들은 나를 괴롭혔었다. 처음보는 코드는 코드를 따라가며 읽게되는데, 상속은 부모클래스를 봐야하니까 한눈에 들어오지가 않고, 인터페이스 연결은 거기서 갑자기 뚝 끊기고 실마리를 잃어버린다. 디버거나 코드분석툴로 따라가면 인터페이스의 실제 concrete 클래스가 숨겨져 있어 찾질 못하곤 했거든.
여기에서 알아야할 개념이 책임(responsibility)이다. 책임을 누가 가져갈건지, 다르게 말하면 구현을 누가 할거냐란 문제. MVC 모델의 예를 들면, View는 데이터를 뿌려주기만 할 뿐, 데이터를 저장하고 관리하는건 Model의 책임이다. 그렇다면, 데이터를 다루는 구현은 Model에 있겠구나 유추가 가능하다. 이처럼 디자인 구조를 이해하고 책임소재를 논리적으로 유추해내면 코드가 어디에 존재할지 알 수 있다. 어디까지나 잘 짜여진 코드라면 말이지.
결국 OOP적으로 프로그래밍을 잘한다는건 의존성을 최소화하고 책임을 누가지게 할 것인가를 푸는 문제가 된다. 디자인 패턴이란 사람들의 프로그래밍 경험이 쌓이고 “특정 형태의 문제들에 대해 이렇게 디자인해서 구현하니까 좋더라”하는 문제풀이 솔루션 카탈로그다. 이는 새로 구현하는 문제들에 적용할 수도 있지만, 다른 코드를 읽을 때에도 유용하다.
계속해서 MVC로 예를 들면, Model에서 데이터 변경이 있어도 View에서 변경사항을 반영하며 제대로 보여주기 위해 Observer pattern이 사용된다. 여기에서 Model과 View가 어떤 인터페이스를 가지고 구현되어 있을지 예측할 수 있다.
또 하나 생각해볼 부분은 언급이 없었던 Controller의 존재. 이게 필요한건가? 왜필요하지? OOP적 의문을 던지게 되면, 이 클래스의 책임과 역할이 보이게된다. 데이터를 그래프로 보여주는 경우를 생각해보면 하나의 데이터로 매우 다양한 View의 사용이 가능하다는 걸 알 수 있다. 사용자 입력에 의해 데이터가 변경되는 경우를 생각해보면, 이를 View에서 처리할경우 다양한 각 View마다 구현되어야 할 것이다. 또한, 데이터 변경이 꼭 View를 통하지 않을수도 있다. 네트웍이나 기타 외부적 접근으로 변경될 수도 있는 사항이다. 문제를 해결하려면 사용자 입력을 처리하는 Controller를 만들고 Controller가 입력을 받아 Model을 변경하는게 맞다는걸 알 수 있다. 사용자 입력은 외부 연결이 될 수도 있고, View에서 노출한 UI로 조작이 될 수도 있다. 그러므로 Controller는 View에 인터페이스를 노출하고 View를 조작할수도 있어야 한다. 이러한 일련의 과정에서 자연스럽게 Model-View의 의존성이 낮아진다. 다르게 말하면 커플링이 루즈해진다. 보다 유연한 구조가 되는 것이다.
디자인 패턴은 구현 목적에 따라 생성 패턴, 구조 패턴, 행위 패턴으로 분류하고 있다. 각각의 패턴을 공부할 때, 목적을 이루기 위해 어떻게 의존성을 줄이고 있는지, 책임은 누가 가져가는지를 유심히 본다면 좀 더 공부가 수월해질거 같다.

구현: 얼마나, 어디까지해야하나?
유념해야할 것은, 디자인 패턴은 참고용 모법답안일 뿐이지 절대적인 법칙이나 룰이 아니라는 사실. 구현하고자 하는게 무엇인지, 필요한 만큼만 구현하는게 실무의 답이다. View가 딱 하나만 필요하고 딱히 Controller의 존재가 필요없다면, Model-View 구조로 만들면 된다. 만약 일이 더 커져서 좀 더 복잡해지면 그 때, Controller를 도입해 MVC 형태로 refactoring을 하는게 정답이라는 얘기.
세줄 요약 (필요한가 싶지만…)
한번에 생각할 논리 덩어리를 줄이자.
덩어리를 분리하기 위해 의존성을 낮추자.
덩어리를 알아보기 쉽게 책임을 누가 가져갈지 고민하자.
의식의 흐름대로 단상을 떠올리며 글을 쓰다보니, 사람 단기기억의 한계부터 여기까지 왔다. 다시 읽어보면, 좀 산만하게 보일거 같은데 일단 좀 적어두고 싶었다. 나중에 기회가 된다면, 좀 더 체계적이고 코드 예제들을 이용해서 정리해볼 수도 있겠지. 어디까지나 기.회.가. 된.다.면. 후훗.