HomeBlogGuestbookLab 

JDM's Blog

온갖 테스트 결과가 기록되는 이곳은 JDM's Blog입니다. :3

데코레이터 패턴(Decorator Pattern)

지난 번 스트래티지 패턴 이후 많은 시간이 흘렀군요. 이번엔 데코레이터 패턴에 대해 포스팅 하고자 합니다. 디자인 패턴이라는건 공부할 땐 참 "아하!" 하게 되서 좋은데 막상 실무에 쓰려고 하면 기억이 잘 나지 않네요. 열심히 반복 학습이 필요할 것 같습니다.

데코레이터 패턴 정의(Decorator Pattern Definition)

객체를 동적(dynamic)으로 서브 클래스를 이용해 확장한다.

클래스를 써서 기능을 확장하는 방법이고 프로그램을 실행하는 중에도 객체를 확장 시킬 수 있는 패턴이군요. 요즘은 가끔씩 쿠키런을 하고 있는데, 이번엔 쿠키를 컨셉으로 해서 코드를 짜볼까 합니다.

만들고 싶은 최종 코드(Final Example Source)

/* main method */
public class DecoratorMain {
	public static void main(String[] args) {
		
		// 그냥 용감한 쿠키 만들기
		Cookie braveCookie = new BraveCookie();
		
		// 우유를 얹은 용감한 쿠키 만들기
		Cookie milkBraveCookie = new MilkTopping(braveCookie);

		// 그위에 초콜릿을 얹은 용감한 쿠키 만들기
		Cookie chocolateMilkBraveCookie = new ChocolateTopping(milkBraveCookie);
		
		// 그위에 우유를 한번 더 넣은 용감한 쿠키 만들기
		Cookie chocolateDoubleMilkBraveCookie = new MilkTopping(chocolateMilkBraveCookie);
		
		System.out.println(chocolateDoubleMilkBraveCookie.getName());
		
		// 소다 쿠키 만들기
		Cookie SodaCookie = new Cookie() {
			@Override
			public String getName() {
				return "소다 쿠키";
			}
		};
		
		// 초콜릿을 두번 넣은 소다 쿠키 만들기
		SodaCookie = new ChocolateTopping(new ChocolateTopping(SodaCookie));
		
		System.out.println(SodaCookie.getName());
	}
}

만들고 싶은 코드의 최종 모습입니다. 이 코드를 만들기 위해서 클래스들을 하나씩 만들어 봅시다.

쿠키 만들기(Create Cookie)

쿠키를 만들기 위해서는 쿠키 인터페이스를 이용해야 합니다. 아래처럼 인터페이스를 만듭니다.

/* Cookie Interface */
public interface Cookie {
	public String getName(); // 쿠키의 이름을 가져옵니다.
}

쿠키 인터페이스를 만들었으니 실제로 "용감한 쿠키"를 만들어봅시다.

/* Create Cookie */
public class BraveCookie implements Cookie{	
	@Override
	public String getName() {
		return "용감한 쿠키";
	}
}

용감한 쿠키가 만들어졌습니다. 이와 같은 방식으로 다양한 쿠키를 더 만들어 낼 수도 있습니다.

쿠키에 얹을 토핑을 만들자! (Create Topping)

쿠키가 있으니 이제 위에 토핑 재료를 얹어봅시다. 그 전에 토핑이라는 이름의 추상 클래스를 만들어 봅시다. 이 클래스는 앞으로 데코레이터 클래스로 활약할 거에요.

/* Topping Class */
// 쿠키 위에 얹을 토핑 클래스(데코레이터)입니다.
public abstract class Topping implements Cookie{
	protected Cookie cookie;
	
	public Topping(Cookie cookie) {
		this.cookie = cookie;
	}
	
	@Override
	public abstract String getName();
}

토핑 클래스를 상속extends받아 우유맛, 초콜릿맛 토핑을 추가해봅시다.

/* Milk Topping */
public class MilkTopping extends Topping{
	
	public MilkTopping(Cookie cookie) {
		super(cookie);
	}

	@Override
	public String getName() {
		return "우유맛 " + cookie.getName();
	}
}
/* Chocolate Topping */
// 쿠키에 초콜릿을 첨가합니다.
public class ChocolateTopping extends Topping{

	public ChocolateTopping(Cookie cookie) {
		super(cookie);
	}

	@Override
	public String getName() {
		return "초콜릿맛 " + cookie.getName();
	}
}

정리

위에 있는 예제 코드들을 전부 만들어서 제일 처음에 있던 최종 코드를 실행하면 아래처럼 콘솔에 나올겁니다.

우유맛 초콜릿맛 우유맛 용감한 쿠키
초콜릿맛 초콜릿맛 소다 쿠키

ArrayList등을 이용해서 중복된 맛을 하나로 뭉쳐서 처리할 수 있다면 더 좋았겠지만 귀찮음(…)으로 인해 간단하게 나열만 했습니다. (-_-)

이러한 데코레이터 패턴이 많이 보이는 자바 API는 파일 I/O 관련 부분입니다. 자바에서 파일을 읽어 들일 때 보통 다음처럼 사용 하잖아요?

/* read file example */
BufferedReader br = new BufferedReader(new FileReader(new File("test.txt"))); 

보다시피 데코레이터 패턴이 사용해서 유연하게 기능 확장을 할 수 있지만, 대신 각각 데코레이터 클래스들이 어떤 기능을 수행하는지 알고 있어야 하고 자잘한 클래스들이 많이 생기는 것이 단점입니다. 적절한 위치에서 활용해야 할 것 같아요.

마무리

점점 클래스이 많아지다보니 설명이 매우 짧아지는(!) 현상이 생기네요. 일단은 질러 놓고 나중에 보강을 해야겠어요(…).