2011년 5월 23일 월요일

The Decorator Pattern

9. The Decorator Pattern
Decorator 패턴은 장식과 내용을 동일하게 취급한다.
Decorator 패턴은 클래스의 상속을 사용하지 않고도 객체의 기능을 동적으로 확장할 수 있게 해줍니다.
하나의 클래스 전체가 아니라, 각각의 오브젝트에 대하여 responsibility를 추가하고 싶은 경우
상속을 사용하여 responsibility를 추가할 수도 있다. 클래스 계층 구조 내의 한 클래스에 상속을 사용하여 border를 추가하면, 그 클래스의 모든 서브클래스에 border를 추가하는 결과를 낳는다. 그러나, 이렇게 하면 정적으로 border가 선택되므로 flexibility가 떨어진다. 클라이언트가 컴포넌트를 언제 어떻게 decorate해야하는지도 조절할 수 없게 된다.
이에 비해 컴포넌트를 border를 추가하는 다른 오브젝트로 감싸는 방법이 더 flexible하다. 감싸는 주체가 되는 오브젝트를 decorator라 한다. Decorator의 인터페이스는 컴포넌트의 인터페이스와 동일하므로, 그 컴포넌트를 사용하는 클라이언트의 입장에서는 decorator의 존재가 투명해진다. Decorator는 클라이언트의 request를 컴포넌트에 전달하는데, 전달하기 전이나 후에 (border를 그리는 등의) 추가적인 동작을 수행한다. 게다가 decorator가 투명하므로 재귀적으로 포갬으로써 무한하게 responsibility를 추가할 수도 있다.
구조
*


역할
v Component의 역할
기능을 추가할 때의 핵심이 되는 역할, 케이크에 비유하면 장식하기 전의 스펀지 케이크에 해당합니다. Component 역할은 스펀지 케이크의 인터페이스(API)만을 결정합니다.
v ConcreteComponent의 역할
Component 역할의 인터페이스(API)를 구현하고 있는 구체적인 스펀지 케이크 입니다.
v Decorator의 역할
Component 역할과 동일한 인터페이스(API)를 가집니다. 그리고 또한 Decorator 역할이 장식할 대상이 되는 Component의 역할도 가지고 있습니다. 이 역할은 자신이 장식하고 있는 대상을 알고 있습니다.
v ConcreteDecorator의 역할
구체적인 Decorator 역할입니다.
의도
장식을 중복함으로써 기능을 추가해 나가는 것 - 어떤 오브젝트에 동적으로 responsibility를 추가하려할 때 subclassing을 대신하여 decorator를 사용할 수 있다.
적용시기
  • 개개의 오브젝트에 동적이고 투명하게 즉, 여타 오브젝트에 영향이 없이 responsibility를 추가하고 싶은 경우
  • 나중에 없앨 수도 있는 responsibility를 구현할 때
  • Subclassing에 의해 기능을 확장하는 것이 비실용적일 때. 서로 독립적인 기능 확장이 많은 수가 필요할 때(subclassing을 사용하여 구현하면 모든 조합을 지원하기 위해 서브클래스의 수가 폭발적으로 증가해야 한다.) 또는 확장하고 싶은 클래스의 정의를 모르거나 subclassing할 수 없을 때

결론
Decorator 패턴은 클래스에 상속 작업을 수행하지 않고서도 책임 내역을 추가할 수 있는 좀더 유연성 있는 방법을 제공한다. 그 이유는 해당 패턴이 클래스 안에서 선택된 인스턴스에 대한 책임 내역을 선별해서 추가할 수 있기 때문이다. 또한 decorator 패턴은 상속 계층 구조에서 서브 클래스를 생성하지 않고도 클래스를 최적화할 수 있다.
Decorator 패턴의 단점
v Decorator 패턴과 그것에 동봉된 컴포넌트가 다르다는 것인데, 이것 때문에 객체 유형에 대한 테스트 작업이 실패할 수도 있다.
v Decorator 패턴이 소스 코드의 내용을 유지 보수하는 개발자에게는 모두 같게 보이는 소규모의 객체들을 과도하게 생성하는 시스템 구조를 초래할 수 있다는 것이다. 이것은 유지보수 과정에서 문제가 될수 있다.

Decorator, Adapter, Composite 패턴
이 세 개의 패턴 클래스 간에는 본질적인 유사성이 있다. Adapter 클래스는 기존의 클래스를 장식하는 역할을 수행하는 것처럼 보인다. 그러나 해당 클래스의 기능은 특정 프로그램에 대하여 좀더 편리한 클래스의 인터페이스로 변경하는 작업을 수행하는 것이다. Decorator 클래스는 클래스의 코든 인스턴스보다는 특정 인스턴스에 메소드를 추가한다. 여기에서 가능성 있는 상황과 단일 항목으로 구성된 composite 클래스가 본질적으로는 decorator 클래스라는 것이다. 그러나 다시 말하지만, 그 의도는 다르다.


예제소스
*


예제 소스
public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}
public int getColumns() {
return string.getBytes().length;
}
public int getRows() {
return 1;
}
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}
public abstract class Border extends Display {
protected Display display;
protected Border(Display display) {
this.display = display;
}
}
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return 1 + display.getRows() + 1;
}
public String getRowText(int row) {
if (row == 0) {
return "+" + makeLine('-', display.getColumns()) + "+";
} else if (row == display.getRows() + 1) {
return "+" + makeLine('-', display.getColumns()) + "+";
} else {
return "|" + display.getRowText(row - 1) + "|";
}
}
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
public class SideBorder extends Border {
private char borderChar;
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return display.getRows();
}
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
public class mainClass {
public static void main(String[] args) {
mainClass client = new mainClass();
client.exec();
}
public void exec() {
Display b1 = new StringDisplay("Hello, world.");
Display b2 = new SideBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 = new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("안녕하세요")), '*'))), '/');
b4.show();
}
}


관련패턴
  • Adapter : Decorator 패턴은 내용물의 인터페이스(API)를 변경하지 않고 장식을 만듭니다. Adatper 패턴은 다른 두개의 인터페이스(API)를 연결하기 위해 사용합니다.
  • Strategy : Decorator 패턴은 장식을 교체하거나 장식을 겹쳐서 기능을 추가합니다. Strategy 패턴은 알고리즘을 교체해서 기능을 변경합니다.

댓글 없음:

댓글 쓰기

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...