데코레이터 패턴(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")));
보다시피 데코레이터 패턴이 사용해서 유연하게 기능 확장을 할 수 있지만, 대신 각각 데코레이터 클래스들이 어떤 기능을 수행하는지 알고 있어야 하고 자잘한 클래스들이 많이 생기는 것이 단점입니다. 적절한 위치에서 활용해야 할 것 같아요.
마무리
점점 클래스이 많아지다보니 설명이 매우 짧아지는(!) 현상이 생기네요. 일단은 질러 놓고 나중에 보강을 해야겠어요(…).