티스토리 뷰
[DesignPattern] 1. Strategy Pattern에 대한 고민
오늘은 Strategy Pattern에 대해 고민을 해보았습니다.
일단 왜 이 패턴을 사람들은 Strategy Pattern이라고 부를까요? 단순히 영어를 해석해보면 계획, 전략 정도가 되지 않을까 싶습니다.
그렇다면 이 패턴은 어떠한 계획 혹은 전략을 담고 있는 패턴일까요?
위키피디아에서 이 패턴을 검색해보면 다음과 같이 정의합니다.
In computer programming, the strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime
-> 컴퓨터 프로그래밍에서, strategy pattern은(policy pattern이라고도 불리우는) 소프트웨어 디자인 패턴이다. 이 패턴은 알고리즘의 행동부가 런타임 시에 선택 될 수 있도록 하는 소프트웨어 디자인 패턴이다.
대충 감이 오시나요?
이 패턴은 즉, 우리가 구현하려는 대상을 런타임 시에 상황에 맞춰 적절한 코드와 매칭하려는 디자인 패턴입니다.
이 패턴을 명명한 사람은 아마 그러한 과정이 여러 전략을 수립하고 상황에 맞춰 선택한다는 발상을 한게 아닌가 싶습니다.
이러한 관점에서 우리가 생각해 보아야 할 부분은 객체 설계시의 객체간의 결합도 부분입니다.
저희가 처음 프로그래밍을 배울 때 주로 하던 초창기 프로그래밍은 모든 변수값, 메소드 구현부 등이 클래스 내부에 직접 선언되고는 하죠. 사실 그렇게만 해도 프로그램은 잘만 돌아갔습니다. 그런데 왜 좀 잘 만들었다 하는 코드를 보면 추상 클래스니, 인터페이스니, 상속이니 하면서 별 기술을 다 쓰면서 복잡하게 만들까요?
저는 이러한 이유가 각 객체간의 결합도를 낮춰서 어느 한 부분에 종속되지 않게 하려는 의도와 여러 객체 사이에서 나타나는 중복의 최소화를 위한 시도라고 생각합니다. 객체간의 중복되는 내용은 따로 추출해서 공통으로 사용 할 수 있는 클래스를 만들고 관계를 설정하여 여러 객체를 최소의 중복으로 표현합니다. 이렇게 하면 코드량도 줄고 파생되는 클래스를 더 빠르게 생성해낼 수 있습니다. 하지만 문제는 이렇게 추출해서 클래스간의 관계를 만드는 것이 너무 직접적이고 변화에 대응하기 힘들 다는 점입니다. 여기서 나온 개념이 객체간의 결합도 입니다. 객체간의 관계가 밀접 할수록 결합도는 올라가기 마련이죠.
보통 위와 같이 중복된 구현부를 뽑아 클래스간의 관계를 만들어 중복을 최소화 한다면, 객체의 상속을 떠올리기 쉽습니다. 이 경우 결합도는 굉장히 올라가죠. 코드 외부에서는 해당 코드를 수정할 수 없기 때문에 내부에서 모든 미래의 예측을 다해줘서 코딩을 해주어야 합니다. 어떤 input값을 받고 조건문으로 해당 코드를 선택하면 되지 않느냐고 물어볼 수 있는데, 이 또한 미래를 예측해서 해당하는 경우의 input값을 만들고 해당하는 조건 코드를 구축해야 하죠. 심지어 예상치 못한 클라이언트의 요구로 새로운 구현부를 만들어야 한다면 역시나 해당 클래스를 뜯어 고쳐야 합니다. 만약 클래스 내부에서도 여기저기 직접 호출을 통해 여러 관계들이 얽혀 있다면 수정하면서 그 부분을 모두 다시 기억해내서 에러가 안생기게 조율해 주어야 합니다. 그리고 이 클래스가 본인이 구현한게 아닌 몇 년전 회사에 다니던 사람이 자기 맘대로 코딩한거라면? ... 끔찍합니다. 클래스 하나를 바꾸기 위해 시스템 전체를 공부해야 될 수도 있어요.
위와 같은 점이 객체간의 결합도를 낮추려는 이유입니다. 지금 당장은 어떻게든 코딩을 해서 구현할 수 있지만, 추후에 변수에 대해서 수정이 용이해야 하기 때문입니다.
그럼 한번 이 패턴을 살펴보도록 합시다.
이 패턴은 위에 정의한데로 클래스에서 선택할 알고리즘을 런타임 시에도 설정 할 수 있게 만들어줍니다. 즉, 나중에 새로운 알고리즘이 나타나도 그 알고리즘을 굳이 클래스 내부에서 선택할 필요가 없다는 겁니다. 다만 클래스는 선택된 알고리즘에 대한 정보를 받고, 그 정보를 바탕으로 자신의 알고리즘을 수행합니다. 이렇게 하면 새로운 알고리즘을 개발 할 필요가 있을 때 기존 클래스는 건드리지 않고 클래스가 필요로 하는 스펙에 맞춰서 새로운 알고리즘을 클래스 외부 어딘가에 구현해주면 그만입니다. 즉, 변화에 매우 유연하게 대처 할 수 있죠.
이를 위해서는 어떻게 디자인을 해야할까요?
Strategy Pattern에서는 이를 interface 변수를 만들어 구현합니다.
무엇을 선택해야 할지 클래스 내부에서는 알지 못하니, Interface라는 최소한의 규격을 정의하겠다. 너희는 이 규격에 맞춰서 나중에 적당한 알고리즘을 나에게 내놔라.
이것이 이 디자인 패턴의 핵심입니다. 클래스와 알고리즘 부분을 Interface라는 매개체를 중간자를 둬서 분리해버린 것이죠. 이렇게 함으로서 본 클래스와 알고리즘 구현부간의 관계는 과거 직접접근이었을 때에 비해 굉장히 약한결합도를 갖게 됩니다.
후우 잘 알지도 못하면서 설명하려니 쉽지 않네요. 다음은 예제를 보면서 이해해보도록 하죠.
먼저 어떤 클라이언트가 저에게 다음과 같은 요청을 해왔다고 가정하겠습니다.
내가 어떤 RPG 게임을 만들고 싶어. 그런데 이게 게임을 업데이트 하면서 계속 무기를 추가해줘야 되고 중간중간 바뀌거나 삭제되는 경우도 있을 것 같아. 나중에 이렇게 자주 바뀌어도 대처하기 쉽게 해줘.
이 경우 설계자의 입장에서는 게임 시스템을 구현하는 부분과 그에 해당하는 여러 무기 구현 부분을 떨어뜨려 놓아야 합니다. 안그러면 나중에 무기 하나 바꾸려고 게임 시스템 내부 코드에서 무기 관련된 내용을 찾아서 수정해야 하기 때문입니다. 제가 직접 찾으면 금방 찾을 수도 있지만, 다른 개발자가 와서 찾으려면 무기 하나 바꾸려고 게임 시스템을 다 살펴봐야 할 수도 있습니다. 이럴 때는 Strategy Pattern을 이용해보도록 하죠.
Game 코드를 구현합니다. 생성자를 활용해서 기본 무기로 검을 설정합니다. 나중에 무기를 바꿀 때 사용할 setWeapon도 구현합니다.
핵심은 무기가 무엇이 될지는 모르지만 Wepon 인터페이스라면 attack이라는 코드가 구현이 되있을거라는 거죠.
즉, 클래스는 무슨 무기일지 몰라도 공격을 수행하는 코드를 weaponAttack을 통해 구현하죠.
인터페이스 조건입니다. 이로서 무기 관련 인터페이스는 모두 attack메소드를 구현해야 합니다.
검으로 공격을 하면 위와 같이 메시지가 뜨겠군요.
활로 공격하면 위와 같이 메시지가 뜨겠죠?
게임 시스템을 불러내서 공격을 실행해보고, 활로 무기를 바꿔서 공격해보았습니다.
그 결과입니다. 공격 코드가 바뀐걸 알 수 있죠?
위와 같이 구현을 하면 창이라는 무기가 게임 업데이트를 통해 생겨도 인터페이스 규격에 맞춘 창 클래스 하나를 만들면 그만입니다. 기존 클래스를 전혀 손댈 필요가 없습니다. 이게 Strategy Pattern의 핵심이죠.
이제 대충 패턴에 대해서는 둘러본 것 같은데요. 혹시나 이런 생각을 하신분은 없나요?
"이렇게 클래스를 설계하면, 필연적으로 외부에서 무기 인터페이스에 정보를 주입할 수 있게 만들어야하는 코드를 구현해줘야 하지 않을까?"
네, 맞습니다. 클래스에서 사용할 정보를 외부에 요청했으니 당연히 그에 해당하는 주입 코드를 넣어줘야 합니다. 이를 의존성 주입(Dependency Injection. DI)라고 하는데요. 이에 대해서 곰곰히 생각해 볼 필요도 있답니다. 관련 포스트가 아니니 자세히 설명은 안하겠지만, 간단히 설명하자면 객체에서 필요로하는(즉, 의존하는) 객체 정보를 받기 위해서 의존성 주입이 필요한데, 이런 방법으로 생성자를 통한 주입, get/set 함수를 통한 주입, 애노테이션을 활용한 주입, 외부 파일을 통한 주입 등등 여러가지 방법이 있습니다.(사실 get/set의 확장이라 봐도 무방하겠지만...) 아, 위 예제에서 쓴 생성자는 의존성을 주입한게 아닙니다. 클래스 내부에서 직접 접근을 했으니까요. 원래는 인자로 받아서 넣어야 하는데 생각 못했네요.. ^^;
'Dev Story > DesignPattern' 카테고리의 다른 글
[DesignPattern] 2. Observer Pattern에 대한 고민 (0) | 2015.02.06 |
---|