혹시 일본식 라면집에 가보셨나요? 저는 여자친구와 갔다가 곰탕같은 라면을 시켜먹었는데(으웩...) 그게 8년전 애기입니다.
개인적으로 라면은 우리나라, 삼양라면을 좋아합니다. 가끔 너구리도 먹어주시면 흐뭇해지고요. 그리고 일요일엔 짜파게티죠~
오늘 이 자리를 빌어 저와같이 라면을 좋아하시는(돈이 없어 라면을 드시던간에-_-) 우리나라 프로그래머님들을 위해
저만의 비법을 알려드리겠습니다. 물론 어떤 댓가를 바라진 않습니다. 다만 응용한 결과를 저에게도 알려주시면 고마울뿐이죠.
아래는 제가 만든 첫번째 라면입니다.
소스를 보니 텅~ 빈 생성자 하나에 설명이라는 속성 그리고 가격이라는 메소드가 있군요.
설명이 무안 할 정도로 단순하지만.. 얼마나 복잡하게 될 수 있는지 보기로 하죠.
이제 이런 형태로 된장라면, 간장라면, 곰탕라면, 짜장라면, 김치라면, 카레라면, 조개라면, 사노라면, 짬뽕라면,
된장조개라면, 고추조개라면, 짜장된장라면, 고추짜장라면, 고추된장카레조개짬뽕라면.. 등등등..
지면상-_- 나머지 라면들을 나열하는것은 생략하고 이 정도만 만들어 보죠.
대충 복사해서 이름하고 가격만 바꿔주면 될듯 보입니다.
자.. 이제 평소에 잘하시는 ctrl+c, ctrl+v 신공을 발휘할때가 왔네요.
그런데 가만 들여다보면 단순하진 않습니다. 그렇게 쉽게 해결 될 문제가 아닌거죠.
그 이유로 고추조개라면의 경우로 예를 들면 가격()을 구현해야 하는데
아래 소스에서 보다시피 참조되는 라면들의 가격을 반영해야 합니다.
만약 고추/된장/카레/조개/간장/양파/해삼/멍게/오징어/짬뽕라면 이라면... 그외
카레/조개/간장/양파/해삼/멍게라면, 해삼/멍게/오징어/짬뽕라면 등등 다양한 조합들이 있으므로
가격이란 메소드 작성이 쉽지 않습니다.
더구나 이 글을 보신 분들의 요청에 의해 라면의 종류는 더 늘고 복잡해질지 모릅니다.
... 왜 문제가 되는지 생략 ...
여기에 이 문제를 해결 할 비법이 있습니다.
우선 라면에대해 다시 생각해 볼 필요가 있습니다.
다 알다시피 라면은 어떤 재료가 들어가냐에 따라서 종류가 달라지고 어떤 라면이 될지가 결정됩니다.
그리고 재료는 하나이상 복합적으로 들어갈 수 있고(안들어갈수도 있군요) 이에따라 라면의 가격도 달라집니다.
이 둘의 관계는 달리말하면 라면은 재료에 함수적 종속성을 가지게 됩니다. 재료가 라면을 결정짓게 되는것이죠.
따라서 우리는 라면과 재료의 관계를 재정립하고 이를 코드로 분리해서 관리 할 필요성이 있습니다.
라면은 재료에의해 설명도 가격도 달라지므로 둘을 합치거나 또는 서로가 포함관계에 있으면 코드의 중복과
복잡도가 증가하고 재료비나 또는 가격을 계산하는 로직이 변경될 경우 수정에 많은 힘이 드는 상황이 발생합니다.
해결책은 아래와 같이 라면과 재료라는 추상화된 객체를 만드는것으로부터 시작합니다.
여기서 핵심은 재료로 라면에 어떠한 행위를 한 뒤에라도 라면으로써 사용이 가능해야 합니다. (현실도 그렇죠)
기본라면에 어떤 재료를 넣어 만들어진 라면도 라면이고, 그렇게 만들어진 라면에 다시 재료를 추가해도 라면이라는 말이죠.
따라서 재료 클래스는 추상화된 라면 클래스를 구현또는 상속받아야 합니다. 재료 as 라면이 성립되어야 하니까요.
또 재료만의 속성과 메소드를 가질수 있으니 이것을 객체로 만드는데는 이의가 없습니다.
(인터페이스로 구현해도 됩니다. 하지만 여기선 추상클래스로..)
이걸 바탕으로 아래와 같이 고추라는 재료를 만들수 있습니다.
재료의 생성자에 가공(요리) 할 대상인 라면의 레퍼런스를 넘겨주는것에 주목 할 필요가 있습니다.
설명 속성은 넘겨받은 라면에 재료명을 더해주는것만으로 깔끔하게 완성된 코드입니다.
마찬가지로 가격 메소드 또한 가공(요리) 할 라면가격에 재료(고추)의 가격을 더해주면 됩니다.
이렇게 기본라면에 고추를 넣은 후에도 라면으로봐야죠. 가공(요리)된 라면에 다시 고추를 넣어도 라면입니다.
그럼 고추고추라면이 되겠군요. -_-
끝에가면 예제가 있으나 우선 사용예를 보겠습니다.
라면 고추라면 = new 고추( new 라면인스턴스() );
라면 고추고추라면 = new 고추( new 고추( new 라면인스턴스() ) );
고추고추라면.가격() 메서드를 호출하면 ( 고추재료가격 + ( 고추재료가격 + 라면가격) ) 과 같은 계산이 이뤄지게 됩니다.
그럼 고추와 같은 방법으로 조개라는 재료도 만들어보죠.
자.. 이제 재료도 대충 만들었으니 재료들을 넣을 라면을 만들어 볼 차례입니다.
아직 실체화된 라면이 없었습니다. 여기선 그냥 삼양라면을 만들어 보겠습니다.
이제서야 라면을 요리 할 시간이 되었군요.
재료도 구비되었으니 이제 라면을 재료로 새롭게 재탄생시킬수 있습니다.
라면을 고추로 감싸서 고추라면을,
라면을 조개로 감싸고 다시 고추로 감싸니까
고추조개삼양라면이 만들어집니다.
고추조개삼양라면을 양파라는 재료 클래스로 감싸주면?
양파고추조개삼양라면이 되겠죠.
실행 결과화면
어떤가요?
라면 클래스(여기서는 추상화된 라면 클래스를 구현한 삼양라면을 말합니다) 자체는 손대지 않았습니다.
단지 재료로 감싸준것 뿐이죠. 또 단가의 계산도 간결하게 해결 되었습니다.
그런데 결과화면을 보니 고추/조개/삼양라면 순이 아니라 읽고 부르기엔 웬지 이상합니다.
이 문제를 수정하기 위해 기존 클래스들을 수정해야 할까요?
우리는 방금 원본 클래스에 손도 대지않고 원하는 기능을 구현하는 방법을 배웠습니다.
따라서 이번에도 그런 방식으로 해보는게 좋겠군요.
아래와 같이 DescriptionDeco라는 클래스를 만들어봤습니다.
역시 여기서의 핵심도 라면이라는 추상화 클래스를 상속받고 생성자에선 가공해야 할 라면 클래스를 넘겨받는것이죠.
이렇게 하면 기존 코드의 변경이 일어나지 않습니다.
(단지 설명을 출력하는 기능에대한 확장이므로 가격은 변동이 없습니다. 공짜란 말이죠.)
적용 예입니다.
결과화면
* 실행가능한 전체소스를 첨부하였으니 참고하세요.
마무리
별로 쓴 내용도 없는데 이걸 작성하는데 2시간이나 걸렸네요. 원래는 대충 설명하고 비주얼한 예제를 쓸려고 했는데
이 속도로는 어렵겠군요. -_- 이외에도 좀 더 많은 할 애기가 있지만 여기서 끊는게 좋겠습니다.
아시는 분들은 이미 아셨을수도 있는데 위 내용은 Head First Design Patterns 책을 약간 패러디한것으로
데코레이션 패턴에 해당하는 내용입니다. 미숙한 글이나마 도움이 되었으면 합니다.
용어정리
추상화 클래스인 라면을 Component,
이 클래스를 상속받아 구현된 삼양라면을 ConcreateComponent라고 합니다.
Decorator는 추상화된 재료 클래스 입니다.
일반적으로 Component는 어떤 행위를 정의한 인터페이스와 같이 추상화된 객체일 수 있습니다.
이걸 상속받아 구현된 ConcreateComponent는 Decorator가 감싸게 될 클래스이고요.
Decorator Pattern의 경우 객체의 변형없이 상속또는 구현을 통해 객체의 기능을
유연하게 확장 할 수 있는 방법으로 자주 사용됩니다.
실무에서 부담없이 적용해 볼 만한 몇개 안되는 패턴중에 하나입니다.
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1627&page=1