top bar

글 목록

2014년 7월 13일 일요일

lex & yacc




학부때 배운 lex와 yacc ... OS 시간에 유닉스 쉘 커맨드 라인을 파싱해서
시스템콜을 직접 날리는 프로그램을 작성한 적이있다.

그 기억을 잊지못해 다시한번 공부해봐야겠다고 벼르고 벼르면서
lex 와 yacc 책을 구입하려고했는데 10년도 넘은 책이다 보니
한글판은 물론 원서까지도 절판된 상태.

그런데 그 귀한책의 한글판을 yes24 에서 중고도서로 팔고있는것이 아닌가!
책이 집에 도착하고 설레는 마음으로 포장지를 뜯는순간.. 
표지에서부터 불안한 느낌이 스쳐지나갔는데.. 아니나 다를까..

영어 원서였다......

뭐, 이기회에 원서 한번 정독해보자라고 긍정적으로 생각하고있지만..
먹먹하다... 후..


2014년 7월 12일 토요일

템플릿 메서드 패턴 (template method pattern) 2 - 후크(hook)

후크(hook)는 추상 클래스에서 선언되는 메서드긴 하지만 기본적인 내용만 구현되어있거나, 아무 코드도 들어 있지 않은 메서드 이다. 이렇게 하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있다. 물론 그냥 무시하고 넘어갈수도 있지만 ㅎ

* (이전 포스트 http://asuraiv.blogspot.kr/2014/07/template-method-pattern-1_9.html 를 읽어야 아래 설명들을 이해할수 있다)

아래 CaffeineBeverage 클래스의 음료에 첨가물을 추가하는 addCondiments() 의 호출을, 고객이 원할때만 호출될 수 있도록 조건문을 새로 만들었다. 호출여부는 customerWantsCondisments() 메서드의 리턴값(boolean)이 결정한다.

기본적으로 customerWantsCondiments 메서드는 true를 리턴하지만 서브클래스에서 오버라이드 함으로써 얼마든지 조건을 변경할 수 있다. 

이제 후크를 활용하여 서브클래스를 개선해보자.
아래는 Coffee 클래스에 후크를 활용한 예이다.

후크를 활용하여 Coffee 클래스를 개선했다. 수퍼클래스 템플릿 메서드인 prepareReceipe 메서드의 각 알고리즘 단계중 addCondiments 메서드의 수행 여부를 서브클래스에서 결정하기 위해 customerWantsCondiments 를 오버라이드 했다.

물론 첨가물을 넣을지 말지는 고객이 결정해야 하므로, 입력값을 받아 그 입력값에 따라 true혹은 false를 리턴하도록 구현 했다.

이제 위 설계를 테스트 하기위한 코드를 작성해보자.


이 테스트 코드를 수행한 결과는 아래와 같다.

hook 적용

이제 hook 라는것이 정확히 어떤 용도로 쓰여야 하는지 정리해 보자

1. 알고리즘에서 필수적이지 않은 부분을 필요에 따라 서브클래스에서 구현하는 경우
2. 템플릿 메소드(위에서는 prepareReceipe메서드)에서 앞으로 일어날일 또는 막 일어난 일에 대해 서브클래스에서 반응할 기회를 제공
3. 서브클래스에 수퍼클래스에서 진행되는 작업에 대한 결정을 내리는 기능을 부여하기 위한 용도 (음료에 첨가물을 넣을건지 말건지)

정리하고 보니 더욱더 유연하고 자유도가 높은 알고리즘을 설계할 수 있을것같다.

템플릿 메서드 패턴은 이정도로 마친다.

2014년 7월 10일 목요일

정규식 - 기본 패턴 찾기 (2)

임의의 문자 찾기

정규표현식에서 임의의 문자를 찾으려면 점(.)을 이용하면 된다. 점은 온점(U+002E)이라고도 불리는데, 특별한 경우가 아니라면 개행 문자를 제외한 모든 문자를 찾는다.

아래는 global 설정을 해제하여 점(.)으로 텍스트의 처음 등장하는 패턴을 찾은 예제다.



만약 THE RIME 구문 전체를 찾고 싶다면 다음 점 여덟개를 입력하자.



하지만 이것은 별로 효율적이지 못하다. 아래의 표현식으로 같은 패턴을 찾을수 있다.

.{8}

그래도 위 표현식은 중간에 공백이 포함되어있는 첫 두단어를 찾기엔 좀 그렇다.global체크 박스를 해제해보면 처음부터 끝까지 무조건 8글자를 찾아내기때문이다. 따라서, 단어 경계와 시작 문자, 그리고 끝 문자를 사용하여 좀더 세련되 표현식을 만들자면 아래와 같다



이 정규표현식은 좀더 명확하게 문자열을 찾는다. 위의 그림처럼 ANCYENT라는 단어를 찾았다. 위의 표현식을 설명하자면,



1. 단축문자 \b 는 특정문자가 아닌 단어의 경계를 찾는다.
2. 문자 A와 T는 단어의 처음과 끝 문자를 지정한다.
3. {5}는 임의의 문자를 다섯개 찾는다.
4. \b 는 또 다른 단어 경계를 찾는다.

따라서 이것은 A로 시작하고 T로 끝나는 7글자로된 단어를 찾을 수 있는 것이다.

마지막으로 영문자나 숫자, _(언더바), 기타 스크립트 문자를 찾는 \w 라는 단축 문자로 위와 같은 패턴을 찾는 표현식을 만들어보자. 다음과 같다.

\b\w{7}\b


2014년 7월 9일 수요일

템플릿 메서드 패턴 (template method pattern) 1

템플릿 메소드 패턴을 한마디로 정의하자면
'알고리즘의 캡슐화' 라고한다. 잠깐, '전략 패턴'에도 알고리즘의 캡슐화라는 말을 사용했었 던것 같다. 하지만 무슨 차이가 있을까?

아래 Coffe 클래스가 있다

prepareRecipe 라는 메소드에는 '레시피'인 만큼 커피를 만드는 법(메서드)들이
순서대로 나열되어 있다.

또 아래는 Tea 클래스이다


마찬가지로 prepareRecipe 안에 차를 만드는 메서드들이 나열되어있다.

Coffee와 Tea 이 두가지 클래스는 매우 유사하다. 코드의 중복도 보인다.
딱 눈에 띄는것은 물을 끓이는 boilWater메서드와 컵에 따르는 pourInCup 메서드이다.
이러한 공통적인 메서드를 수퍼클래스로 끌어올리고 prepareReceipe는 수퍼클래스에서
추상 메서드로 선언해보자

그러한 작업을 거치면 1차적으로 아래와 같이 추상화를 할 수 있다.

추상화를 적용한 UML 다이어그램

하지만 이것으로 끝난 것일까? Coffe와 Tea의 메서드들을 가만히 보자.

먼저 brewCoffeCrinds 메서드와 steepTeaBag 메서드는 따지고보면 같다. 커피를 우려내는 것과 티백을 물에 넣어서 홍차를 우려내는 것은 어쨌든 '우려내는' 것은 똑같다.

addSugarAndMilk 메서드와 addLemon 메서드도 마찬가지다. 음료에 무언가를 '추가(add)' 하는것은 똑같지 않은가? 이제 서브 클래스에서 prepareReceipe 메서드를 일일히 구현할 필요없이 추상화 할수 있게 될것 같다.

위의 공통점을 묶어서 수퍼클래스인 CaffeineBeverage 클래스를 재구성하면 다음과 같다.


일단 음료를 '우려내는' brew 메서드를 선언했다. 물론 우려내는 동작은 다르므로 서브클래스에서 구현할 수 있게 추상메서드로 선언했다. 음료에 무언가를 '추가하는' addCondiments도 같은 논리로 추상메서드로 선언하였다.

이렇게 되면 Coffee와 Tea 클래스에는 brew 메서드와 addCondiments 메서드만 용도에 맞게 구현하면 되므로 아래와 같이 간략 해질 것이다.


최종적인 UML 다이어그램은 아래와 같다.

템플릿 메서드 패턴을 적용한 UML 다이어그램


이것이 바로 템플릿 메서드 패턴의 기본이다. 정확히 정리 해야 하기때문에 아래의
prepareReceipe 메서드를 살펴보자.

final void prepareReceipe() {
    boilWater();
    brew();
    pourInCup();
    addCondiments();
}

커피를 만들던, 차를 만들던 우리는 이 prepareReceipe 메서드를 가지고 만들 수 있다.
각각 CaffeineBeverage 클래스를 상속받은 구상클래스의 인스턴스를 생성하여, prepareReceipe 메서드를 호출해주면 된다. 바로 prepareReceipe 메서드가 틀(template)의 역할을 하는것이다.

다음과 같다.

ea myTea = new Tea();
myTea.prepareReceipe();

각각 서브클래스의 인스턴스로 자신이 상속한 수퍼클래스의 템플릿인 prepareReceipe 메서드를 호출하여 음료를 만들어 낸다. 하지만 일부 단계 (brew, addCondiments) 는 서브클래스의 구현에 의존하는 것이다.

이것이 템플릿 메서드 패턴인데... 데체 무엇이 장점일까? 비교해보자

Legacy
Template Method Pattern
CoffeeTea가 각각 작업을 처리한다. 두 클래스에서 각자의 알고리즘을 수행
CaffeineBeverage 클래스에서 작업을 처리한다. 알고리즘을 혼자 독점하고 있다
CeffeTea에 중복된 코드가 있다
CaffeineBeverage 덕분에 서브클래스에서 코드를 재사용 할 수 있다.
알고리즘이 바뀌면 서브클래스를 일일이 열어서 고쳐야 한다
알고리즘은 한군데 모여있기 때문에 그 부분만 고치면 된다
클래스 구조상 새로운 음료를 추가하려면 꽤많은 비용이 든다
prepareReceipe 라는 프레임워크(템플릿)을 제공함으로써 새로운 음료를 추가해도 몇가지 메서드만 추가하면 된다
알고리즘에 대한 지식과 구현 방법이 여러 클래스에 분산되어 있다.
CaffeineBeverage 클래스에 알고리즘에 대한 지식이 집중되어 있으며 일부 구현만 서브 클래스에 의존한다.

.....

템플릿 메서드 패턴을 대규모 프로젝트에 적용한다면 유지보수의 봄이 찾아올것만 같다.

다시한번 정확히 정리해 보자

템플릿 메서드 패턴 : 메서드에서 알고리즘에 골격을 정의한다. 알고리즘의 여러 단계중 일부는 서브클래스에서 구현 할 수 있다. 템플릿 메서드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 할 수 있다.

일반화된 템플릿 메서드 패턴


, 처음에 언급한 '전략패턴' 과의 차이를 간단하게 고찰해본다면, 일단 전략 패턴은 각각의 행동(알고리즘) 나타내는 인터페이스를 구현하여 '()' 형성한다. 다음으로 객체지향의 대표적인 속성중 하나인 '다형성(Polymorphism)' 이용하여 실행시간에 캡슐화된 알고리즘을 바꿔가며 유연하게 사용할 있었던 패턴이었다.

따라서 '알고리즘의 캡슐화' 라는 면에서는 템플릿 메서드 패턴과 비슷하지만, 구현 방법은 전혀 다르다. 템플릿 메서드 패턴에서 말하는 알고리즘 이라는것은 앞의 예에서 보았듯 prepareReceipe 메서드와 같은 일종의 '틀'이다. 그 알고리즘 단계에서 유연하게 바뀔수 있는 부분을 서브클래스에 의존하는것 뿐이다.

전략패턴에서의 캡슐화는 핵심 로직을 캡슐화 하는것이고, 템플릿 메서드 패턴의 캡슐화는 일련의 알고리즘 단계들을 캡슐화 하는 것이다. 정리하고 보니 꽤 큰 차이가 있는것같다.

음.. 일단 다음으로 템플릿 메서드와 함께 사용되는 후크(Hook) 메서드라는것이 있다는데,
나중에 정리해야겠다.


2014년 7월 5일 토요일

정규식 - 기본 패턴 찾기 (1)

1. 문자열 상수 찾기 그냥문자열

문자열 상수를 사용하면 해당 문자열을 찾을수있다. 예를들어 'Korea' 라고 입력하면 
텍스트파일에서 'Korea'를 찾아내는 것이다. 뭐, 딱히 설명이 필요없다.

2. 단축 문자 모음 

단축문자
설명
\a
벨 문자
[\b]
백 스페이스 문자
\c x
제어 문자
\d
숫자
\D
숫자가 아닌 문자
\d xxx
문자의 10진수 값
\f
폼 피드 문자
\h
수평 공백
\H
수평 공백이 아닌 문자
\r
캐리지 리턴
\n
개행문자
\0xxx
문자의 8진수 값
\s
공백 문자
\S
공백이 아닌문자
\t
수평 탭 문자
\v
수직탭 문자
\V
수직탭이 아닌문자
\w
영문자숫자, _, 기타 스크립트 문자
\W
영문자숫자, _, 기타 스크립트 문자를 제외한 문자
\0
널 문자
\x xxx
문자의 16진수 값
\u xxxx
문자의 유니코드 값


* 문자 클래스 안에 ' ^ ' 은 여집합의 개념이다. 예를 들어 [^0-9] 는 숫자가 아닌 문자를 찾아낸다.
\W 은 영문자와 숫자, _(언더바)를 제외한 공백, 구두점 같은 문자만 찾는다. 따라서 \W은 아래와 같다
  [^a-zA-Z0-9_]

* \s 은 공백을 찾는 정규식인데, 이것은 아래와 같은 의미이다

[ \t\n\r]

즉 위의 정규표현식은 다음과 같은 경우를 찾는다

- 공백
- 탭( \t)
- 개행 문자 (\n)
- 캐리지 리턴 (\r)

물론 반대짝도 있다 \S은 공백이 아닌 문자를 찾는다 물론 [^ \t\n\r] 과 의미가 같다.

정규식 - 무선/유선 전화번호 찾기

정규식을 처음 접할때는 항상 전화번호 찾기로 시작을 하나보다.

일단 나도 그렇게 시작한다

보통 우리나라 유선 전화번호는 

(지역번호)xxx-xxxx 
지역번호-xxx-xxxx 

이다.

중간 번호가 4자리가 있는 유선번호도있나?; 잘모르겠다 일단은 중간번호는 3자리만 있다고 가정.

그리고 모바일 번호는 당연

xxx-3자리또는4자리-xxxx

이다. 이러한 패턴을 찾아주는 정규식 표현을 작성/분석해 본다.

여기서 잠깐 - 정규식 공부하기 좋은 사이트 http://www.regexr.com/



바로 위와같은 표현이 국내 무선/유선 번호를 모두 걸러준다. 하나하나 분석해보자

1. ^ 은 처음의 시작을 나타낸다.
2. 초록색 괄호 () 는 참조그룹이다. 한번 그룹으로 묶으면 \1로 참조하여 재사용할수 있다.
3. 역슬러시(\)바로 다음에 오는 문자는 상수문자를 나타낸다. \( 은 여는괄호 ( 자체를 나타낸다
4. \d 은 digit 로서 숫자를 나타내고 중괄호는 수량자인데, \d{2,3} 이라는것은 숫자가 2개 또는 3개 나열 이라는 뜻이다
5. 초록색 괄호 안에있는 세로줄 | 은 세로줄로부터 양옆의 패턴중 하나라는 뜻으로서 or 의 개념이라고 생각하면된다.

따라서 초록색괄호는 '괄호안에 있는 2개또는3개숫자로 시작하거나, 괄호없이2개또는3개숫자다음에 .이나 -으로 시작하는' 이라는 것으로 이해하면 된다

6. 그 다음으로 숫자3개또는4개나열후에 .이나 - 이 나타나는 패턴이고,
7. 그뒤에 숫자 네개가 오는 것으로 끝나는 ($) 것을 의미한다.

이로써 (02)2234-2233 이나, 010-7777-7777 과 같은 국내 유선/무선 번호를 모두 걸러내는 정규식이 완성되었다.

하지만 이것은 정규식의 빙산의 일각일뿐.. 앞으로 갈길이 멀다.

[Linux] cat 를 이용해 텍스트파일 overwrite / append 하기

cat ("concatenate" 의 약자) 는 가장 흔하게 쓰는 리눅스 명령어다. cat을 이용해 하나, 혹은 다수의 파일을 생성할수 있고, 파일안의 컨텐츠를 출력해줄수 있으며 파일들을 이어(concatenate) 주거나 다른 터미널이나 파일로 redirect도 할수있다고 한다.

여기서는 기존의 텍스트파일을 덮어쓰거나, append 하는 방법을 알아본다

간단하다. shell 파일안에 아래와 같은 코드를 집어넣으면 된다.



dhcp 설정파일인 dhcpd.conf 에 빨간색 네모칸 안에있는 텍스트를 덮어씌우는 방법이다. 덮어씌운다는것은 기존의 있는 텍스트를
모두 지우고 새로 입력한다는 뜻이다.

여기서 덮어씌우는것이 아니라 텍스트 메시지 끝에 append 하려면, <<EOM > 이것을 <<EOM >> 로 꺽쇠를 두개 입력하면
해당파일 맨밑에 텍스트가 추가된다. 아주 간단한 방법이다.

cat 의 기본 사용법은 아래를 참고하자

커맨드 패턴 (command pattern) 2

이전 포스트에서 정리했던 커맨드패턴의 적용 사례에 대해 이야기하고자 한다.

A라는 회사가 IT 솔루션 업체인 B에게 홈 오토메이션 리모콘의 API 디자인을 요청했다.
A회사가 넘긴 리모콘 시제품엔 7개의 프로그래밍이 가능한 슬롯이 있으며, 각 슬롯엔 ON/OFF 기능이 있다.

대략 이렇게 생겼다.



각 슬롯엔 전등켜기/끄기 , 선풍기 켜기/끄기, 차고 열기/닫기 등의 기능들을 집어넣으려고하는데... 커맨드패턴을 적용시켜보자.

먼저 모든 커맨드(행동)들은 다음과같은 인터페이스를 구현한다.



전등의 불을켜는 행동을하는 커맨드 클래스의 구상클래스는 아래와같다.



이부분에서 마음에안드는 부분이있지만... 이 포스팅 말미에 털어놓겠다...
위 코드의 5번라인의 Light 클래스는 실제로 특정작업을 처리하는 로직이 담겨있는 리시버(Receiver) 객체이다.
Command 인터페이스를 구현한 이 구상클래스의 execute메서드는 해당 커맨드객체가 가지고있는 리시버의 실제 수행메서드 on()을 호출하는 역할을 한다.

리시버는 다음과같이 작성한다.



실제로 특정작업을 수행하는 메서드 on()과 off()가 작성되어있다. 
주방또는 거실등 의 전등을 구분하기위해 location 멤버변수를 가지고있다.

인보커인 RemoteControl 클래스는 아래와같다.



RemoteControl 클래스에는 7개의 슬롯이있으며, 각 슬롯마다 setCommand 메서드를 이용해 
onCommand, offCommand 클래스를 할당할수있다.

on동작은 onButtonWasPushed메서드를 호출하고, off동작은 offButtonWasPushed메서드를 호출하며, 각 메서드는 해당슬롯의 커맨드 객체의 execute 메서드를 호출하게된다.

모든 준비는 끝났고, 클라이언트 코드를 작성해보자.



전등을 키고끄는 동작을하는 리시버인 Light 클래스외에, 차고의 문을 열고닫는 GarageDoor 클래스, 오디오를 컨트롤하는 Stereo 클래스등 기타 리시버를 작성하였다. 각 리시버의 동작마다 커맨드클래스를 작성하고, 해당하는 리시버를 생성자의 매개변수로 넘겨준다. (20~29 line)

그리고 11라인에 생성한 인보커객체인 remote 인스턴스의 setCommand 메서드를 이용해 각슬롯마다 해당기능의 on/off 커맨드 객체를 넘겨준다. 여기서는 0,1,2,3 슬롯만 커맨드 객체를 할당하였다.

37라인~40라인은 리모콘의 버튼을 누르는 동작이다. 결과는 다음과같다.







UML로 정리하자.



아.. 정리가 안된다..moon_and_james-57

무튼, 맘에 안드는 부분은 바로 리시버의 동작마다 커맨드클래스를 구현해야한다는것이다. 아래와같이 Stereo 리시버 객체는 다음과같은기능들이있다.



on 과 off 외에 setCd , setDvd , setRadio , setVolume 등의 기능이있는데, 이 기능들 하나하나마다 커맨드 클래스를 작성해야한다. 꼭이래야 할까? 최초 명령을 내리는 객체와 실제로 말단의 로직을 수행하는 객체사이의 Loose coupling 으로 인한 유연성이 확보되어 한결 유지보수가 용이해졌지만, 제한없이 늘어나는 커맨드 클래스는 분명 보기 좋지 많은 않다.....

좀더 공부해봐야겠다는 결론으로 본 포스팅을 마무리한다.

커맨드 패턴 (command pattern) 1

오늘 정리할 패턴은 유명한 '커맨드 패턴' 이다.

일다 요약하면 '요구사항의 캡슐화' 라고하는데..... 이해하기가 좀 어렵다.
하나하나 다시 정리해보자.

커맨드 패턴에 언급되는 객체는 역할에 따라 이름을 가지고있다.
바로 클라이언트(Client) 객체, 리시버(Receiver) 객체, 인보커(Invoker) 객체, 커맨드(Command) 객체 이다.

하나하나 설명하자면,

인보커 : 클라이언트가 이 인보커 객체에게 명령을 내리면, 커맨드 객체에게 특정 작업을 수행해 달라는 요청을 한다.

커맨드 : 인터페이스로서, 모든 커맨드 객체가 구현한다. 모든 명령은 execute 메서드 호출을 통해 수행되며, 이 메서드에서는 리시버에게 특정작업을 수행하라는 명령을 내리게 된다. 커맨드객체는 리시버 객체를 가지고 있으며, 따라서 execute메서드는 어떤 리시버이   냐에 따라서 구현이 달라질것이다.

리시버 : 리시버객체는 요구사항을 수행하기 위한 로직을 담고있는 객체이다.

클라이언트 : 리시버와 커맨드 객체를 생성한다. 생성된 리시버는 커맨드객체에 매개변수로 전달한다.

뭐 대략적인 명령의 흐름은 클라이언트 -> 인보커 -> 커맨드 -> 리시버 인듯하다. 결국은 실제로 로직을 수행하는 객체는 리시버인 것이다

아래는 이러한 객체들의 관계를 UML로 표현한 것이다.




클라이언트에는 Invoker, Receiver, Command 객체가 모두 생성된다. 먼저 리시버와 커맨드 객체를 생성하고, 요구사항이 모듈화된 리시버를 커맨드 객체에 매개변수로 넘긴다. 그리고 인보커 객체의 setCommand 메서드를 이용해 커맨드 객체를 전달하고, 클라이언트가 인보커객체를 통해서 특정 메서드를 호출하면 그 호출된 메서드에서 커맨드 객체의 execute가 호출되고, 또 execute 메서드에서 실제로 수행하려는 리시버 객체의 메서드가 최종적으로호출되는 그림이다.

이러한 구조의 장점은 무엇일까? 일단 커맨드객체의 핵심을 다시 정리하자면 "일련의 행동(Command)를 특정 리시버와 연결시킴으로써 요구사항을 캡슐화 한것" 이다.

따라서 실제 클라이언트로부터 명령을 받는 인보커는 커맨드객체에게 다시 명령을하는데, 커맨드 객체의 세부 구조나, 그 커맨드 객체가 가지고 있는 리시버가 무슨기능을 하는지, 어떤 로직으로 이루어져있는지 알필요가 전혀없다. 인보커로선 그저 커맨드객체의 execute메서드만을 호출하면 되는 것이다. 'Loose Coupling'이 실현되는 순간이다.

[Linux] 심볼릭 링크

컴퓨팅에서 심볼릭 링크(symbolic link또는 기호화된 링크는 절대 경로 또는 상대 경로의 형태로 된 다른 파일이나 디렉터리에 대한 참조를 포함하고 있는 특별한 종류의 파일이다. http://ko.wikipedia.org/wiki/%EC%8B%AC%EB%B3%BC%EB%A6%AD_%EB%A7%81%ED%81%AC

아래부터는 사용 예시이다.

만약 maven 의 tar파일을 다운받고 압축을 푼 상태이면 디렉토리이름이 apache-maven-3.x.x 와 같이 긴이름의
디렉토리가 생길것이다. 


 이것을 아래의 명령어를 사용해 'maven'이라는 간단한 이름으로 심볼릭 링크를 걸어보자





커맨드를 날리면 아래와같이 링크가 걸린 'maven'이라는 디렉토리가 생긴다.




2014년 7월 2일 수요일

전략 패턴 (strategy pattern)

첫번째로 포스팅할 디자인 패턴은 스트래티지 (Strategy : 전략) 패턴이다.

 소프트웨어 공학에서 가장 중요시되는 것들 중 하나가 '유지보수'이다. 절차적으로 짜여져 있는
엄청나게 긴 분량의 소스코드나, 복잡하게 꼬여있는 스파게티 코드는 유지보수하기에 최악의 코드라고 말한다.
하지만 객체지향 패러다임을 따르는 객체 지향적인 언어로 소프트웨어를 설계한다면, 앞서말한 하나의 소프트웨어를 구성하는 여러가지 기능들을 '모듈화' 즉 객체지향 4대요소중 하나인 '캡슐화' (encapsulation) 를 할수있다.
따라서 기능을 추가하거나, 변경을 할때 다른기능들에 영향을 주지않고 작업할수있는 설계가 가능하다.
이러한 객체지향적인 패러다임을 충실히 따르는 디자인패턴의 기본이 바로 스트래티지 패턴이라고 생각한다.

처음 포스팅이라 서두가 길었다. 앞으로 하는 포스팅엔 중요 개념만 정리할란다...

스트래티지 패턴은 아래와같이 정의할수 있다.

"알고리즘 군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다. 
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할수 있다."

발단은 이렇다. 
 'SimUDuck' 이라는 오리 연못 시뮬레이션이 있다. 이 프로그램에는 매우 다양한 오리 종류가 존재한다.
처음 디자인한 사람들은, 표준적인 객체지향 기법을 이용하여 'Duck' 이라는 수퍼 클래스를 만들어놓고, 
이 'Duck'을 상속하여, 여러가지 오리의 종류를 만들었다.



 수퍼클래스에서는 꽥꽥거리는 기능의 quack메서드, 수영을 할수있는 swim 메서드, 날아다닐수있는 fly메서드가 있다.
아래의 MallardDuck 클래스와 RedheadDuck클래스는 위의 세가지 메서드를 그대로 상속받고, 생김새를 표현하는 display 추상 메서드를 오버라이딩 하여 각각의 모습을 표현한다.

 하지만 여기에 RubberDuck (고무오리) 클래스를 추가하면 어떨까? 날수없는 고무오리에게도 fly 메서드가 상속되어 고무오리가 날아다니는 진풍경이 오리연못 시뮬레이션에 나타날수 있다. 

Attempt 1.

 RubberDuck 클래스는 그대로 Duck 클래스를 상속하지만, 날수없어야 하므로 fly 메서드를 아무 행동도 하지 않게 오버라이딩 한다. 간단하게 해결되는 듯 하다. 하지만 여기에 또다른 오리인 "DecoyDuck" (가짜오리)이 추가된다고 가정해보자. 이 가짜오리는 날수도없고 꽥꽥거리지도 못한다. 그렇다면 fly 메서드와 quack 메서드 두개를 또다시 오버라이딩해야한다. 만약 이 시뮬레이션의 기능 변경이 잦다면, 일일히 서브클래스를 뒤져야 하고 상황에따라 오버라이딩해야하며 이것은 상당이 비용이 많이 드는 작업이다.

Attempt 2.

 결국엔 상속을 사용하여 전체 서브클래스가 상속을 받는것이 아닌 일부 서브클래스만 날거나 꽥꽥거릴수 있도록 하는방법을 찾아야한다.

인터페이스를 사용해보자.



딱 보아도 복잡해보인다. 일단은 Flyable, Quackable 인터페이스를 정의하여 필요한 오리클래스마다 implements 하여 구현할 수 있도록 하였다. 어쨌든 일부 오리만 날거나 꽥꽥거릴수 있게하는 소기의 목적은 달성했지만, 치명적인 단점이 존재한다. 

바로 '코드의 중복'

인터페이스의 메서드는 추상메서드이므로, 그것을 구현하는 구상 클래스에서 일일히 fly, quack 메서드를 구현해야한다. 따라서 엄청난 코드의 중복을 초래하고, 날아다니는 동작을 조금 바꾸기위해 100여개의 구상클래스의 fly 메서드를 일일히 다 찾아 고쳐야 한다는것이다. 결국 인터페이스도 답은 아니다.

Solution .

 '변하는 부분'을 생각해보자. fly와 quack은 오리의 클래스마다 달라지는 부분이다. 가짜오리는 날지 못하며, 고무오리는 '꽥'이 아니라 '삑' 소리를 낸다. 이러한 변하는 부분들의 알고리즘들을 '캡슐화' 한다면 다음과같은 그림이 완성된다.



 일단의 행동들(날거나, 소리를 내는)을 인터페이스로 정의했다. 그리고 그 인터페이스를 구현하는 클래스들이 일련의 '알고리즘군'을 형성하였다. 이러한 인터페이스형식의 인스턴스 변수를 Duck클래스의 멤버로 추가한다. 그리고 각각의 오리 클래스들은 'Duck'클래스를 상속받는다. 중요한부분인데, Duck클래스에는 각각의 인스턴스 변수를 실행시에 동적으로 대입하기 위해서 setter 메서드를 선언하였다.

이제 이러한 디자인이 어떻게 활용되는지 보자.

일단 소리를 낼수는 있지만 날지못하는 'ModelDuck' 클래스를 정의한다.


public class ModelDuck extends Duck {
 
 public ModelDuck(){
  flyBehavior = new FlyNoWay();
  quackBehavior = new Quack();
 }

 @Override
 public void display() {
  // TODO Auto-generated method stub
 }
}

 보는바와 같이 생성자에서 FlyBehavior 와 QuackBehavior 인스턴스를 날수없는 동작으로 구현된 FlyNoWay 클래스와, '꽥' 하는 소리를 내는 Quack 클래스의 인스턴로 할당한다.
이제 FlyBehavior의 새로운 구상클래스인인 로켓빠워로 날아가는 'FlyRocketPowered' 클래스를 정의하자.

public class FlyRocketPowered implements FlyBehavior {

 @Override
 public void fly() {
  System.out.println("로켓 추진으로 날아 갑니다");
 }
}

준비는 마쳤다. 이제 테스트를 위한 MiniDuckSimulator 클래스를 작성한다.

public class MiniDuckSimulator {
 
 public static void main(String[] args) {
  Duck model = new ModelDuck();
  model.performQuack();
  model.performFly();
  
  System.out.println();
  
  model.setFlyBehavior(new FlyRocketPowered());
  model.performFly();
 }
}

Duck 추상클래스로 선언된 변수에 ModelDuck 클래스의 인스턴스를 할당하고,
performQuack, performFly 메서드를 실행한다. 그리고 ModelDuck 인스턴스의 나는 동작을
setFlyBehavior 메서드를 이용하여 로켓추진으로 날아가는 FlyRocketPowered 클래스의 인스턴스로
갈아끼운후 다시 performFly 메서드를 실행한다. 결과는 다음과같다.



 처음에 modelDuck의 Behavior 변수에는 날수없는 FlyNoWay클래스와, 꽥소리를 내는 Quack클래스의 인스턴스를 할당하였다. 그래서 처음 두 동작은 '꽥' 소리와 '저는 못날아요' 라는 동작이 실행된것이다. 하지만, setFlyBehavior 메서드를 이용해서 실행시에 동적으로 FlyRocketPowered 클래스의 인스턴스를 대입 하였다. 다시 실행한 performFly메서드는 로켓추진으로 날아가는 동작을 보여준다.

 가만히보니..... 스프링을 공부할때 배웠던 의존성주입 (Dependency Injection) 개념,, 특히나 세터 인젝션(Setter Injection)과 매우 흡사하다. 그렇다. 스프링 프레임워크는 바로이 스트래티지 패턴을 기반으로 구성된 프레임워크인 것이다. (물론 스프링이 스트래티지 패턴으로만 이루어진것은 아니다.)

 어쨌든, 이렇게 알고리즘'군'을 각각 캡슐화 하여 정의하고, 그것들을 사용하는 클라이언트 단에서 캡슐화된 알고리즘 즉, '기능'들을 유연하게 교체해가며 사용할수 있다는것이 스트래티지 패턴의 핵심이다. 이렇게되면 각각의 서브클래스나 슈퍼클래스를 전혀 건드릴 필요없이 캡슐화된 알고리즘 클래스만을 추가하거나, 수정한다면 side effect의 risk 없이 코드를 유지보수할수 있게 될 것이다.