데코레이터 패턴은 클래스의 기능을 확장할 수 있게
하는 패턴이다.
그런데, 기능 확장을 생각하면 그냥 평범한 클래스의 상속 또한 생각할 수 있다. 상속을 통해 상위 클래스의 메서드를 오버라이드하여 기능을 확장할 수 있기 때문이다. 그렇다면, 상속을 사용하지 않고 데코레이터 패턴이나 composite(조립)을 사용하는 이유가 무엇일까??
상속의 단점 중 하나는 상위 클래스의 변경의 어려움을 들 수 있다. 만약 상위 클래스의 구현을 변경하거나 메서드의 시그니쳐를 변경했다면 그 여파는 하위 클래스까지 영향을 줄 수 있다.
하지만, 데코레이터 패턴
은 상속이 아닌 위임의 방식
으로 기능을 확장하기 때문에 기존 코드에 영향을 주지 않는다. 따라서 단일 책임 원칙
을 지킬 수 있도록 해준다.
예시로, 어떤 캐릭터에 공격 기능을 표현하는 코드를 예시를 생각해 보자. 이 캐릭터는 기본적으로 공격 기능을 가지고 있지만, 여기서 기본 공격에 스플래쉬 기능, 스턴 기능으로 확장해 나간다고 가정해 보자.
public interface Attack {
void attack();
}
public class AttackImpl implements Attack{
@Override
public void attack() {
System.out.println("기본 공격");
}
}
public abstract class Decorator implements Attack{
private Attack delegate;
protected Decorator(Attack delegate) {
this.delegate = delegate;
}
protected void delegate(){
delegate.attack();
}
}
public class Splash extends Decorator {
protected Splash(Attack delegate) {
super(delegate);
}
@Override
public void attack() {
super.delegate();
System.out.println("스플래쉬 추가");
}
}
public class Stun extends Decorator {
protected Stun(Attack delegate) {
super(delegate);
}
@Override
public void attack() {
super.delegate();
System.out.println("스턴 추가");
}
}
public class Character {
private Attack punch;
public Character(Attack punch) {
this.punch = punch;
}
public void punch(){
punch.attack();
}
}
public class Main {
public static void main(String[] args) {
Attack basic = new AttackImpl();
Attack splash = new Splash(basic);
Attack punch = new Stun(splash);
Character character = new Character(punch);
character.punch();
}
}
//결과
기본 공격
스플래쉬 추가
스턴 추가
main 메서드를 보면 실행하는 구조가 프록시 패턴과 거의 동일하다. 하지만, 프록시 패턴은 접근 제어에 초점을 맞추는 경우가 많고, 데코레이터 패턴은 객체의 기능 확장에 중점을 둔다.
Reference