[디자인 패턴] Decorator 패턴

한낱·2023년 9월 3일

디자인 패턴

목록 보기
3/6

Decorator 패턴 예제

문자열 주변을 '-', '+', '|' 등의 문자로 감싸 일종의 '장식틀'을 만드는 예제에 Decorator 패턴을 적용해본다.

예제 다이어그램

Display 클래스

Display 클래스 : 문자열을 표시하는 추상 클래스
Display 클래스는 3 개의 추상 메서드를 가진다.

  • getColumns : 가로 문자수를 얻는 데에 사용
  • getRows : 세로 행 수를 얻는 데에 사용
  • getRowText : row번째 행의 문자열을 얻는 데에 사용

이 외에도 show라는 메서드를 사용하여 모든 행을 표시한다.

public abstract class Display {
	public abstract int getColumns();
    public abstract int getRows();
    public abstract String getRowText(int row);
    
    public void show() {
    	for (int i = 0; i < getRows(); i++) {
        	System.out.println(getRowText(i));
        }
    }
}

StringDisplay 클래스

StringDisplay : Display의 하위 클래스

public class StringDisplay extends Display {
	private String string;
    
    public StringDisplay(String string) {
    	this.string = string;
    }
    
    @Override
    public int getColumns() {
    	return string.length();
    }
    
    @Override
    public int getRows() {
    	return 1;
    }
    
    @Override
    public String getRowText(int row) {
    	if (row != 0) {
        	throw new IndexOutOfBoundsException();
        }
        return string;
    }
}

Border 클래스

Border 클래스 : '장식틀'에 해당하는 추상 클래스, Display 클래스의 하위 클래스로 정의되어 있음.

public abstract class Border extends Display {
	protected Display display;
    
    protected Border(Display display) {
    	this.display = display;
    }
}

SideBorder 클래스

SideBorder 클래스 : 구체적인 장식틀 역할을 하는 Border 클래스의 하위 클래스

public class SideBorder extends Border {
	private char borderChar;
    
    public SideBorder(Display display, char ch) {
    	super(display);
        this.borderChar = ch;
    }
    
    @Override
    public int getColumns() {
    	return 1 + display.getColumns() + 1;
    }
    
    @Override
    public int getRows() {
    	return display.getRows();
    }
    
    @JOverride
    public String getRowText(int row) {
    	return borderChar + display.getRowTExt(row) + borderChar;
    }
}

FullBorder 클래스

FullBorder 클래스 : 상하좌우를 장식할 수 있는 Border의 하위 클래스

public class FullBorder extends Border {
	public FullBorder(Display display) {
		super(display);
	}
    
    @Override
    public int getColumns() {
    	return 1 + display.getColumns() + 1;
    }
    
    @Override
    public int getRows() {
    	return 1 + display.getRows() + 1;
    }
    
    @Override
    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) {
    	StringBuilder line = new StringBuilder();
        for (int i = 0; i < count; i++) {
        	line.append(ch);
        }
        return line.toString();
    }
}

적용 (Main 클래스)

public class Main {
	public static void main(String[] args) {
    	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("Hello, world")
                        ),
                        '*'
                    )
                )
            ),
            '/'
        );
    }
}

/* 
결과
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-------------------+/
/|+-------------+++++|/
/||*+-------------+*||/
/||*|Hello, world.|*||/
/||*+-------------+*||/
/|+-------------+++++|/
/+-------------------+/
*/

b1의 장식틀이 b2, b2의 장식틀이 b3가 되는 구조이다.

투과적 인터페이스(API)

Decorator 패턴에서는 장식틀로 내용물을 감싸도 인터페이스가 가려지지 않는다. 이러한 성질을 투과적 인터페이스라고 표현하는데, 이로 인해 b4처럼 장식틀을 여러겹으로 감싸도 인터페이스가 변경되지 않는다.

Decorator 패턴

패턴 다이어그램

Decorator의 패턴 다이어그램은 다음과 같다.

예제에서는 Display클래스가 Component 역을, StringDisplay 클래스가 ConcreteComponent 역을, Border 클래스가 Decoratro 역을, SideBorder 클래스와 FullBorder 클래스가 ConcreteDecorator 역을 맡았다.


Decorator 패턴에 필요한 클래스들을 위 사진에 비유할 수 있는데, 액자가 끼워지지 않은 사진 자체는 Component 클래스와 ConcreteComponent가, 사진을 꾸미는 액자/장식의 역할은 Decorator, ConcreteDecorator가 담당한다.

특징

  • 내용물을 바꾸지 않고도 기능을 추가할 수 있다.
  • 동적으로 기능을 추가활 수 있다.
  • 단순한 구성이어도 다양한 기능을 추가할 수 있다.
    ConcreteDecorator를 많이 준비해두면 이를 조합하여 새로운 객체를 만들어 내기가 쉽다.

JAVA에서 Decorator 패턴

JAVA에서 입출력을 담당하는 Java.io에서 Decorator 패턴을 사용한다.

//파일에서 데이터를 읽어들이는 인스턴스
Reader reader = new FileReader ("dataFile.txt");

// 파일에서 데이터를 읽어들일 때 버퍼링 일어남
Reader reader = new BufferedReader(
					new FileReader ("datafile.txt)"
				);
             
// 행 번호 관리 기능 추가
Reader reader = new LineNumberReader (
					new BufferedReader (
                    	new FileReader ("datafile.txt")
                    )
                );

마치 텍스트에 장식을 덧붙이는 예제처럼, 데이터를 읽어들이는 인스턴스에 버퍼링을 추가할지, 행 번호 관리 기능을 추가할지, 네트워크로부터 읽어들이게 할 지 등을 마음대로 붙일 수 있다.

추가로, java.nio.file.Files 클래스에는 여러 클래스들을 조합하지 않고도 자주 사용하는 기능을 사용할 수 있도록 다양한 클래스 메서드들이 준비되어 있다.

상속의 동일시 vs 위임의 동일시

책 중간중간 '동일시'라는 표현을 사용하는데, 이에 대해 정리해보자

  1. 상속 : 하위 클래스와 상위 클래스를 동일시

    // 하위 클래스를 상위 클래스로 동일시
    Parent obj = new Child();
    obj.parentMethod();

    위 예시처럼 Child 인스턴스는 Parent형 변수에 그대로 대입할 수 있고, 부모로부터 상속받은 메서드를 그대로 호출할 수 있다. 이는 하위 클래스를 상위 클래스로 간주하는 예로, 만약 상위 클래스를 하위클래스로 간주하려면 캐스트를 거쳐야 한다.

    // 상위 클래스를 하위 클래스로 동일시
    Parent obj = new CHild();
    ((Child)obj).childMEthod();
  2. 위임 : 자신과 위임할 곳을 동일시하기

    class Rose {
    	Violet obj = ...
        void method() {
        	obj.method();
        }
    }
    
    class Violet {
    	void method() {
        	...
        }
    }

    위 예제의 문제는 Rose와 Violet 클래스가 동일한 메서드인 method를 가지며 '연결'되어 있는데 이를 잘 나타내주지 않는다는 것이다. 이 명시를 해주기 위해 Flower라는 공통의 추상 클래스를 추가할 수 있다.

    abstract class Flower {
    	abstract void method();
    }
    
    class Rose extends Flower {
    	Violet ojb = ...
        @Override
        void method() {
        	obj.method();
        }
    }
    
    class Violet extends Flower {
    	@Override
        void method() {
        	...
        }
    }

    혹은 Flower를 interface로 만들어도 된다. (이 때는 extends 대신 implements를 사용하면 된다.)

profile
제일 재밌는 개발 블로그(희망 사항)

0개의 댓글