데코레이터 패턴은 상속과 위임을 이용한 패턴입니다.
하나의 객체를 장식하듯이 감싸고, 자신이 장식하고 있는 객체에게 행동을 위임합니다.
우선은 다시 와우로 돌아가서,
임의로 캐릭터를 하나 생성한다고 가정해보겠습니다.
직업을 고르고, 그 캐릭터에게 입힐 기본적인 방어구셋을 정합니다.
그러면 그 방어구에 따라서 캐릭터의 방어도가 결정되지요..
물론 이 캐릭터도 기본적인 방어도는 가지고 있습니다.
이것을 단순히 상속만을 이용해서 구현하려고 해보겠습니다.
public abstract class Character {
String desc;
public String getDesc() {
return desc;
}
public abstract int def();
}
------------------------------------------------
public class Priest extends Character {
public Priest() {
desc = "연습생 로브를 입고 있습니다.";
}
@Override
public int def() {
//성직자의 기본 방어도는 10입니다.
return 10;
}
}
---------------------------------------------------------
public class Warrior extends Character {
public Warrior() {
desc = "가죽갑옷을 입고 있습니다.";
}
@Override
public int def() {
//전사의 기본 방어도는 100입니다.
return 100;
}
}
전사와 성직자 캐릭터는 생성시 기본적인 옷을 입고 있는 상태로 생성이 되며
기본적인 방어도를 가지고 있습니다.
여기서 캐릭터를 선택 할 때 몇몇 갑옷을 선택 할 수 있다고 가정해보겠습니다.
그것을 투구,장화,장갑으로 가정해보겠습니다.
이상태에서 이 문제를 해결하기 위해서는 Character 클래스에 투구,장화, 장갑의 상태를 추가하고
이를 셋팅하기 위한 setter 메서드가 필요하고, cost() 메서드에서는 각각의 장비를 가지고 있는지
유무를 확인하여, 방어도를 계산해야 합니다.
이를 상속받은 Warrior/Priest 클래스는 자신의 방어도를 계산 할 때
super.cost()를 호출 한 후 자신의 기본 방어도를 더하면 되겠지요..?
아래와 같이 구현 될 것입니다.
String desc;
boolean helm = false;
boolean guntlet = false;
boolean shoes = false;
public String getDesc() {
return desc;
}
//이제 여기서 구현을 합니다.
public int def() {
int def = 0;
if(hasHelm()) {
def+=10;
}
if(hasGuntlet()) {
def+=10;
}
if(hasShoes()) {
def+=10;
}
return def;
}
public void setHelm() {
desc += " 헬멧을 씁니다.";
helm = true;
}
public void setGuntlet() {
desc += " 장갑을 착용합니다.";
guntlet = true;
}
public void setShoes() {
desc += " 신발을 신습니다.";
shoes = true;
}
public boolean hasHelm() {
return helm;
}
public boolean hasGuntlet() {
return guntlet;
}
public boolean hasShoes() {
return shoes;
}
}
그리고 이를 상속받은 Warrior와 Priest 클래스는 아래와 같이 변경됩니다.
public class Priest extends Character {
public Priest() {
desc = "연습생 로브를 입고 있습니다.";
}
@Override
public int def() {
//성직자의 기본 방어도는 10입니다.
int basic = 10;
basic+=super.def();
return basic;
}
}
public class Warrior extends Character {
public Warrior() {
desc = "가죽갑옷을 입고 있습니다.";
}
@Override
public int def() {
//전사의 기본 방어도는 100입니다.
int basic = 10;
basic+=super.def();
return basic;
}
}
그럼 테스트를 해보겠습니다.
public static void main(String[] args) {
Character warrior = new Warrior();
warrior.setGuntlet();
warrior.setHelm();
System.out.println("전사 : "+warrior.getDesc() + " 방어도 : ["+warrior.def()+"]");
Character priest = new Priest();
System.out.println("성직자 : "+priest.getDesc() + " 방어도 : ["+priest.def()+"]");
}
}
전사 : 가죽갑옷을 입고 있습니다. 장갑을 착용합니다. 헬멧을 씁니다.방어도 : [30]
성직자 : 연습생 로브를 입고 있습니다.방어도 : [10]
전사와 성직자를 생성하고 전사에게는 장갑과 헬멧을 그리고
성직자는 기본적인 상태로 만든다음에 방어도와 상태를 보았습니다.
여기까지만 보면 꽤 훌륭한 것 같습니다.
코드의 중복도 없고, 상속도 잘 활용한 것 같습니다.
하지만 여기서 새로운 장비가 추가되면 어떨까요?
혹은 게임이 패치되어서 각 장비의 방어도가 달라진다면?
이렇게되면 기존의 클래스를 열어서 고치는 일을 피할 수 없습니다.
게다가 고쳐야 하는 부분도 만만치 않죠.. 실제로 게임에서 등장하는 장비들은 한두가지가 아닐 것 입니다.
그래서 이것을 데코레이터 패턴으로 변경해 보겠습니다.
String desc;
public String getDesc() {
return desc;
}
public abstract int def();
}
Character 클래스가 가지고 있던 장비에 대한 정보를 모두 없애버렸습니다.
그리고 이를 상속하는 Priest와 Warrior 클래스를 만듭니다.
public class Priest extends Character {
public Priest() {
desc = "연습생 로브를 입고 있습니다.";
}
@Override
public int def() {
//성직자의 기본방어도는 10입니다.
return 10;
}
}
public class Warrior extends Character {
public Warrior() {
desc = "가죽갑옷을 입고 있습니다.";
}
@Override
public int def() {
//전사의 기본 방어도는 100입니다.
return 100;
}
}
각 클래스는 자기 자신의 방어도만을 리턴합니다. 장비에 대한 정보는 없죠.
그러면 이제 이 장비에 대한 클래스를 만들어보겠습니다.
클래스를 장비로 "장식한다(덮는다)"라는 개념으로 생각해보세요.
이 장비에 대한 클래스들은 자기가 장식해야 할 대상을 알고 있어야 합니다.
그것이 바로 Character 형이죠.
그리고, 장비 자신도 다른 장비에 의해서 장식이 될 수 있기 때문에
그 자신도 Character형이어야 합니다.
(물론 개념적으로 보면 말이 조금 안되는 얘기이긴 합니다. 장비가 장비를 장식한다? 라는 거..
그래도 그냥 Decorator의 개념을 설명하기 위한 방법으로 이해해주세요..^^)
핵심은 장식의 대상이 되는 녀석도, 그리고 장식을 하는 녀석도 모두 같은 클래스형이어야 한다는 것입니다.
일단, 장비를 나타내는 클래스를 만들어보겠습니다.
public abstract String getDesc();
}
Character형을 상속합니다.
이제 장비 클래스들을 만들어보겠습니다.
public class Guntlet extends CharacterDecorator {
Character character;
public Guntlet(Character c) {
this.character = c;
}
@Override
public String getDesc() {
return character.getDesc() + " 장갑을 착용합니다.";
}
@Override
public int def() {
return 10 + character.def();
}
}
-------------------------------------------
public class Helm extends CharacterDecorator {
Character character;
public Helm(Character c) {
this.character = c;
}
@Override
public String getDesc() {
return character.getDesc() + " 투구를 씁니다.";
}
@Override
public int def() {
return 10 + character.def();
}
}
---------------------------------------
public class Shoes extends CharacterDecorator {
Character character;
public Shoes(Character c) {
this.character = c;
}
@Override
public String getDesc() {
return character.getDesc() + " 장화를 신습니다.";
}
@Override
public int def() {
return 10 + character.def();
}
}
보시면 아시겠지만 이 클래스들은 자신의 일만 합니다.
일단 누가 자기를 장식하고 있던, 자기가 누구를 장식하고 있던 그 형이
Character 형이라면 상관하지 않습니다.
단지 자기가 감싸고 있는 클래스의 getDesc()와 def() 메서드를 호출하여 거기다가
자신의 행동을 더할뿐입니다.
사용하는 방식은 아래와 같습니다.
public static void main(String[] args) {
Character warrior = new Warrior();
warrior = new Helm(warrior);
warrior = new Guntlet(warrior);
System.out.println("전사 : "+warrior.getDesc() + " 방어도 : ["+warrior.def()+"]");
Character priest = new Priest();
System.out.println("성직자 : "+priest.getDesc() + " 방어도 : ["+priest.def()+"]");
}
}
마치 장식하듯이 클래스를 감싸나갑니다.
이것을 조금 다르게 표현하면,
warrior = new Guntlet( new Helm(warrior) );
이런식으로 나타낼 수 있습니다. 어디서 많이 본 형식 아닌가요?
자바에서 제공되는 InputStream, OutputStream 즉, java.io 클래스들이 데코레이터 패턴으로
구성되어 있습니다.
그리고, 검색 오픈소스인 루씬의 Analyzer도 1.4 버젼까지는 위와 같은 형식으로
구성되어 있었습니다.
역시 이 패턴의 가장 큰 장점 중 하나는 동적으로 클래스의 행동을 추가 할 수 있다는 것이라고 생각됩니다.