본문 바로가기

Design Pattern

[디자인 패턴_Again] Decorator Pattern.

데코레이터 패턴은 상속과 위임을 이용한 패턴입니다.

하나의 객체를 장식하듯이 감싸고, 자신이 장식하고 있는 객체에게 행동을 위임합니다.

우선은 다시 와우로 돌아가서,

임의로 캐릭터를 하나 생성한다고 가정해보겠습니다.

직업을 고르고, 그 캐릭터에게 입힐 기본적인 방어구셋을 정합니다.

그러면 그 방어구에 따라서 캐릭터의 방어도가 결정되지요..

물론 이 캐릭터도 기본적인 방어도는 가지고 있습니다.

이것을 단순히 상속만을 이용해서 구현하려고 해보겠습니다.

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()를 호출 한 후 자신의 기본 방어도를 더하면 되겠지요..?

아래와 같이 구현 될 것입니다.

public abstract class Character {
 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 class Test {
 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]

전사와 성직자를 생성하고 전사에게는 장갑과 헬멧을 그리고
성직자는 기본적인 상태로 만든다음에 방어도와 상태를 보았습니다.

여기까지만 보면 꽤 훌륭한 것 같습니다.
코드의 중복도 없고, 상속도 잘 활용한 것 같습니다.

하지만 여기서 새로운 장비가 추가되면 어떨까요?

혹은 게임이 패치되어서 각 장비의 방어도가 달라진다면?

이렇게되면 기존의 클래스를 열어서 고치는 일을 피할 수 없습니다.

게다가 고쳐야 하는 부분도 만만치 않죠.. 실제로 게임에서 등장하는 장비들은 한두가지가 아닐 것 입니다.

그래서 이것을 데코레이터 패턴으로 변경해 보겠습니다.

public abstract class Character {
 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 class CharacterDecorator extends Character {
 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 class Test {
 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()+"]");
 }
}

마치 장식하듯이 클래스를 감싸나갑니다.
이것을 조금 다르게 표현하면,

Character warrior = new Warrior();
  warrior = new Guntlet( new Helm(warrior) );

이런식으로 나타낼 수 있습니다. 어디서 많이 본 형식 아닌가요?
자바에서 제공되는 InputStream, OutputStream 즉, java.io 클래스들이 데코레이터 패턴으로
구성되어 있습니다.

그리고, 검색 오픈소스인 루씬의 Analyzer도 1.4 버젼까지는 위와 같은 형식으로
구성되어 있었습니다.

역시 이 패턴의 가장 큰 장점 중 하나는 동적으로 클래스의 행동을 추가 할 수 있다는 것이라고 생각됩니다.