본문 바로가기
Book

[독서 기록] 헤드퍼스트 디자인 패턴 3장 | 객체 꾸미기 - 데코레이터 패턴

by Renechoi 2022. 11. 14.
 
헤드 퍼스트 디자인 패턴
이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나의 패턴에 하나의 이야기를 담았습니다. 틀에 박히지 않아 지루할 틈이 없는 구성과 친구와 이야기하듯 편안한 대화체로 이야기를 풀어냅니다. 이야기 속에 다양한 방법으로 해결할 수 있는 질문과 90개 이상의 연습문제를 담았습니다. 마치 게임 퀘스트를 해결하듯 문제를 하나하나 해결하다 보면 학습한 내용이 머릿속에 강렬하게 남습니다. 이유 2. 원스톱으로 배우는 14가지 GoF 핵심 디자인 패턴과 9가지 객체지향 디자인 원칙! 현장에서 자주 사용되는 옵저버, 어댑터, MVC 패턴 등 14가지 GoF 객체지향 패턴을 중점으로 패턴의 정의, 사용 시기, 사용처, 사용 이유, 즉시 디자인에 적용하는 방법을 알려줍니다. 이와 더불어 객체지향 프로그래밍에 광범위하게 적용할 수 있는 OCP, 할리우드 원칙 등 9가지 객체지향 디자인 원칙과 패턴으로 생각하는 방법도 알려줍니다. 이유 3. 시대의 변화에 맞춘 개정과 한국 독자만을 위한 특별판! 자바 8과 자바 16 이상에서 무리 없이 동작할 수 있도록 예제 코드를 수정했으며, 부가적인 설명과 Q&A 질문을 추가했습니다. 또한 16여 년 만의 개정을 기념해 오직 한국 독자만을 위한 새로운 삽화를 사용하고 한글 친화적인 구성했습니다. 원서를 읽을 때보다 더욱 편안하게 디자인 패턴을 학습할 수 있습니다. ▶ 이 책을 읽어야 하는 당신! ● 소프트웨어 출시는 완벽 그 자체! “어?~ 코드 수정하려고 다시 보니까 난리…” → 유지보수만 생각하면 그저 눈물인 주니어 (자바) 개발자 ● 코딩 실력은 장판파의 장비! “어?~ 팩토리 메소드 패턴을 이렇게 적용했던가?” → 디자인 패턴을 다시 한번 살펴보고 싶은 시니어 (자바) 개발자 ● 혼자 공부해서 다진 프로그래밍 언어 실력! “어?~ 근데 패턴이 뭐야?” → 개발 현장의 소프트웨어 디자인 방법이 궁금한 개발자 지망생
저자
에릭 프리먼, 엘리자베스 롭슨, 케이시 시에라, 버트 베이츠
출판
한빛미디어
출판일
2022.03.16

 

헤드퍼스트 디자인 패턴 3장 | 객체 꾸미기 - 데코레이터 패턴

(에릭 프리먼 외 4인, 서환수 옮김, 한빛미디어)

 


 

데코레이터 패턴을 배우면 기존 클래스 코드를 바꾸지 않고도 객체에 새로운 임무를 추가할 수 있습니다.

- 113p 

 

 

스승 : 상속에 관해서 깊이 생각해 보았나요?

제자 : 예, 스승님. 상속이 강력하긴 하나, 상속을 사용한다고 해서 무조건 유연하거나 관리하기 쉬운 디자인이 만들어지지는 않는다는 사실을 깨달았습니다.

스승 : 그래요... 뭔가 깨달음을 얻었군요. 그러면 상속 말고 어떤 걸로 재사용이라는 목표를 달성할 수 있을까요?

제자 : 구성과 위임으로 실행 중에 행동을 '상속'하는 방법이 있다는 사실을 배웠습니다. ... 서브클래스를 만드는 방식으로 행동을 상속받으면 그 행동은 컴파일할 때 완전히 결정됩니다. 게다가 모든 서브클래스에서 똑같은 행동을 상속받아야 합니다. 하지만 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 행동을 설정할 수 있습니다.

스승 : 훌륭합니다. 드디어 구성의 위력에 눈을 떴군요!

제자 : 예, 이 기술을 활용하면 객체에 여러 임무를 새로 추가할 수 있습니다. 심지어 슈퍼클래스를 디자인했던 사람이 전혀 생각하지 못했던 내용을 추가할 수도 있습니다. 클래스 코드를 전혀 건드리지 않고도 말입니다.

스승 : 코드 관리에 구성이 어떤 영향을 미치는지도 알고 있나요?

제자 : 예, 방금 그 내용을 말씀드리려고 했습니다. 객체를 동적으로 구성하면 기존 코드를 고치는 대신 새로운 코드를 만들어서 기능을 추가할 수 있습니다. 기존 코드는 건드리지 않으므로 코드 수정에 따른 버그나 의도하지 않은 부작용을 원천봉쇄할 수 있습니다.

스승 : ... 코드는 밤의 연꽃처럼 변경에는 닫혀 있고 아침의 연꽃처럼 확장에는 활짝 열려 있어야 한다는 사실을요.

- 119p 

 

 

디자인 원칙 : 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다. (OCP-Open-Closed Principle) 

- 120p

 

 

상속을 써서 음료 가격과 첨가물(샷, 시럽, 우유, 휘핑크림 등) 가격을 합해서 총 가격을 산출하는 방법은 그리 좋은 방법이 아니었습니다. 클래스가 어마어마하게 많아지거나 일부 서브클래스에는 적합하지 않은 기능을 추가해야 하는 문제가 있었죠.

 

다른 방법을 한번 생각해봅시다. 일단 특정 음료에서 시작해서 첨가물로 그 음료를 장식(decorate)해 볼까요? 예를 들어 어떤 고객이 모카와 휘핑크림을 추가한 다크 로스트 커피를 주문한다면 다음과 같이 장식할 수 있습니다. 

 

1. DarkRoast 객체를 가져온다.

2. Mocha 객체로 장식한다.

3. Whip 객체로 장식한다.

4. cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임한다. 

 

- 122p 

 

 

- 124p

 

 

커피 주문 시스템 코드 만들기

 

Beverage 클래스 

public abstract class Beverage {		=> Beverage는 추상 클래스이며 getDescription()과 cost()라는 2개의 메소드를 가집니다.
	String description = "제목 없음";
    
    public String getDescription() {	
    	return description;
    }
    => getDescript은 이미 구현, cost()는 서브클래스에서 구현
    
    public abstract double coust();
}

 

첨가물(condiment)을 나타내는 추상클래스(데코레이터 클래스)를 구현

 

public abstract class CondimentDecorator extends Beverage {	=>Beverage 객체가 들어가야 하므로 Beverage 클래스를 확장
	Beverage beverage;	=> 각 데코레이터가 감쌀 음료를 나타내는 Beverage 객체를 여기서 지정. 어떤 음료든 감쌀 수 있도록 Beverage 슈퍼 클래스 유형을 사용
    public abstract String getDescription(); 
    => 모든 첨가물 데코레이터에 getDescription() 메소드를 새로 구현하도록 만들 계획. 그래서 추상 메소드로 선언.
}

 

- 129p 

 

 

 

음료 코드 구현 

public class Espresso extends Beverage {

	public Espresso() {
    	description = "에스프레소";
    }	=> descprtion이라는 변수는 Beverage로부터 상속
    
    public double cost() {
    	return 1.99;
    }
    => 첨가물 가격을 걱정할 필요 없이 그냥 에스프레소 가격인 1.99 달러를 리턴
 }

- 130p 

 

 

 

첨가물 코드 구현하기

 

public class Mocha extends CondimentDecorator {	=> Mocha는 데코레이터라서 Condiment를 확장 (Condiment에서 Beverage를 확장) 

	public Mocha(Beverage beverage) { 
    	this.beverage = beverage; 
    } => Mocha 인스턴스에는 Beverage의 레퍼런스가 들어있습니다. 
      => 1) 감싸고자 하는 음료를 저장하는 인스턴스 변수 
      => 2) 인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자 


    public String getDescription() {
    	return beverage.getDescription() + ", 모카";
    } 
    => 설명에 음료 이름(다크 로스트)만 들어있으면 안 되겠죠. 첨가되는 각 아이템도 설명에 추가합시다.
    => 예를 들면 '다크 로스트, 모카' 같은 식으로 말이죠. 
    
    public double coust() { 
    	return beverage.cost() + .20'
    }
    => 음료 가격에 모카를 추가한 가격을 계산
    => 우선 장식하고 있는 객체에 가격을 구하는 작업을 위임해서 음료값을 구한 다음, 거기에 모카 가격을 더하고, 그 합을 리턴 
}

- 131p 

 

public class StarbuzzCoffee {
 
	public static void main(String args[]) {
		Beverage beverage = new Espresso();	=> 아무것도 넣지 않은 에스프레소를 주문하고 설명과 가격 출력
		System.out.println(beverage.getDescription() 
				+ " $" + beverage.cost());
 
		Beverage beverage2 = new DarkRoast();	=> 다크로스트 객체를 만듬
		beverage2 = new Mocha(beverage2);		=> Mocha로 감쌈
		beverage2 = new Mocha(beverage2);		=> 모카샷 하나 더 추가
		beverage2 = new Whip(beverage2);		=> whip으로 감싸기 
		System.out.println(beverage2.getDescription() 
				+ " $" + beverage2.cost());
 
		Beverage beverage3 = new HouseBlend();	=> 두유와 모카를 추가하고 휘핑크림을 얹은 하우스블렌드 커피를 주문
		beverage3 = new Soy(beverage3);
		beverage3 = new Mocha(beverage3);
		beverage3 = new Whip(beverage3);
		System.out.println(beverage3.getDescription() 
				+ " $" + beverage3.cost());
	}
}

- 132p

 

 

데코레이터가 적용된 예: 자바I/0 

- 134p 

 

 

반응형