소프트웨어 개발/Design Pattern

⑤ 디자인 패턴(Design Pattern) - Decorator

늘근이 2015. 8. 16. 14:22

InputStream 관련한 자바 클래스를 보고있으면 정신이 아득해진다.

무슨 비슷한 이름들이 이렇게 많은지, 그리고 버전업을 하면서 뭐가 추가되었는지 어디서 코드를 복붙하지 않으면 아예 백지에서 생각해서 어? 그게 무슨 클래스였지 생각하기 힘들다.


출처 : https://www.clear.rice.edu/comp212/02-spring/labs/12/


뭔가 비슷비슷한 이름들이 보기도 힘든 형태로 붙어있다.

자바 I/O 클래스는 데코레이터 디자인 패턴을 가지고 있는데, 이 데코레이터 패턴은 하나하나 장식을 매다는 클래스를 하나하나 만들어놓았기 때문에 위와같이 복잡한 형태의 UML이 그려질수밖에 없다.

초딩때 크리스마스 트리를 만들던 기억이 난다. 지금이야 귀찮지만, 그때는 얼마나 재미있었는지.. 일단 트리를 만들고 라이트도 달고 왕별도 달고 양말도 몇개 달아놓고 그지같은거 다 달아놓으면 제법 그럴듯한 트리가 나오곤 했다. 이렇게 추가로 붙이는 기능들을 다 클래스로 만들어 놓으면 기능이 별로없는 작은 클래스가 많이 만들어질게 뻔하다.


기본적으로는 아래와 같은 형식을 띄고있다.

예를들어 돈까스를 하나 만들어 먹는다고 하자. 돈까스에는 일본식돈까스 왕돈까스 내가만들다태워먹은돈까스 등 여러개의 종류가 잇을 수 있고, 반찬에 밥, 감자, 스파게티, 야채 뭐 이런것도 있을 수 있다. 아 쓰고보니 이게 좋은 예가아닐수도 있다는 생각이든다. 하나의 Component, 즉 기초가 되는 돈까스에는 돼지고기는 무조건 들어가야 한다. 이걸 Component라고 보자. 이걸 잘 튀겨서 내놓으면 ConcreteComponent (추상클래스 Component 의 구현) , 돈까스가 나온다. 이제 이 돈까스를 다시 Decorator를 통해 여러 반찬을 올리면 먹음직스러운 돈까스가 태어난다. 참고로 나는 분식집 돈까스가 제일 맛있다. 이 Decorator클래스는 new를 통해 계속 올려지는 구조이다.


이제 돈까스를 한번 먹어보자


Component  - 일단 돈까스가 추상적이라는 생각으로 한번 인터페이스를 만들어본다. 별게 없다. 뭘 먹는지 리턴하는 메서드

public interface Donkatsu {
	public String make();
}


ConcreteComponent - 그리고 진짜 돈까스를 튀긴다는 생각으로 추상적 돈까스를 레알 돈까스로 바꿔보자

public class RealDonkatsu implements Donkatsu {

	@Override
	public String make() {
		return "맛있는 돈까스 ";
	}
	
}


Decorator - 이제 사이드디쉬를 만들어본다. 사이드디쉬자체는 음식이 아니므로, 이를 상속하여 반찬들을 구현하여야 한다.

public abstract class SideDish implements Donkatsu{

	protected Donkatsu donkatsu;
	
	//생성자
	protected SideDish(Donkatsu donkatsu) {
		this.donkatsu = donkatsu;
	}

}


ConcreteDecorator - 이제 반찬들을 만들어 본다. 여기서 만들건 밥과 감자으깬것이다. 왜냐면 이게 제일 많은것 같은 느낌에서다.

public class Rice extends SideDish{

	private String size;
	
	protected Rice(Donkatsu donkatsu, String size) {
		super(donkatsu);
		this.size = size;
	}

	@Override
	public String make() {
		return donkatsu.make() + "밥밥 ";
	}
	
	

}
public class MashPotato extends SideDish{

	private String size;
	
	protected MashPotato(Donkatsu donkatsu, String size) {
		super(donkatsu);
		this.size = size;
	}

	@Override
	public String make() {
		return donkatsu.make() + "감자감자 ";
	}
	
	
	
}


Main - 이제 테스트를 한번 해본다.

public class Main {
	public static void main(String[] args) {
		Donkatsu d1 = new RealDonkatsu();
		System.out.println(d1.make());
		
		Donkatsu d2 = new Rice(new MashPotato(new RealDonkatsu(),"LARGE"),"HOT");
		d2.make();
		System.out.println(d2.make());
	}
}



결과는 다음과 같다.

맛있는 돈까스
맛있는 돈까스 감자감자 밥밥


맛있는 돈까스 한접시가 만들어졌다. 만약 사이드디쉬를 추가시키고 싶으면 그렇게 하면된다. 클래스를 만들어서 메서드를 구현하고, 실제로 돈까스 객체를 만들어낼때 이것저것 new를 통해 막 가져다 붙이면 된다. 언뜻보면 빌더패턴과 가까운데, 빌더패턴은 클래스의 멤버변수를 하나하나 채워간다는 느낌이 강하다면, 데코레이터 패턴은 클래스를 가져다 붙이기 때문에 이것저것 다 가져다 붙일수 있다. 굉장히 다양하게 변해야 하는 현실에서 써먹을수 있겠다.

 

 

 

이제 실제로 돈까스를 만들지 말고 자바가 구현한 대표적인 클래스인 Java I/O 클래스의 데코레이터 패턴을 엿보기로 한다. 뭔가 엄청 비스무리한 클래스들이 다 주렁주렁 달려있다. 데코레이터 패턴을 이용해 이것저것 할수 있다.

출처 : http://www.falkhausen.de/en/diagram/html/java.io.Reader.html


Reader는 파일에서 데이터를 읽어들일때 쓰는 클래스인데 역시 이것저것 많이도 달려있다. 다들 Reader를 상속받고 있기 때문에, new로 생성해서 다시 인자로 들어갈수 있는 이상한 연쇄적인 구조를 띄게된다. 따라서 다음의 문장은 정말 평이한 문장이다. 중간에 이것저것 빼고 더해도 어차피 인자도 Reader, new를 통해 만들어지는것도 Reader임을 알수있다.

Reader reader = new LineNumberReader(new BufferedReader(new FileReader("sample.json")));


참고로 또 짜증나는 InputStream 과 같은 경우도 다음의 구조를 가지고 있다.