과제3:
디자인 패턴 중 하나 골라 내 코드에 적용하기
커피메이커에 적용. 데코레이터 패턴 적용
커피 만들기
샷 넣기 (base)
에스프레소 샷 넣기
콜드브루 샷 넣기
베이스 (baseadd)
따듯한 물 넣기
차가운 물 넣기
차가운 우유 넣기
뜨거운 우유 넣기 (스팀 치기)
얼음 넣기(ice)
얼음 넣기
시럽 넣기
바닐라 시럽 넣기
초콜렛 시럽 넣기
데코레이터 패턴
데코레이터 패턴의 핵심은 장식을 하는 객체와 장식되는 기본 객체를 같은 타입으로 취급하는 것이다.
=> 클라이언트가 기본 객체를 사용하든, 장식된 객체를 사용하든 동일한 방식으로 다룰 수 있다.

커피 정보를 이용한 프로그램이다.
베이스만 만들고 그 위에 추가적인 정보를 쌓는 형식으로 작동한다. 베이스는 콜드브루 베이스와 에스프레소 베이스 두가지이다.
데코레이터들이 추가적인 정보를 담당한다.
CoffeeInform은 추상 클래스이다. 커피정보를 담고 있고,변경하는 역할을 한다.
CoffeMaker는 커피 정보를 이용해 커피를 만든다.
CoffeeCalculator는 커피 정보를 이용해 커피 가격을 계산한다.
CoffeDecorator는 추상 클래스이다. 커피 정보에 추가될 커피 정보와 추가할 정보들을 가지고 있고, getRecipe와 getPrice를 오버라이딩 해서 정보를 추가한다.
package coffemaker3;
public abstract class CoffeeInform {
protected String name;
protected String recipe;
protected int price;
protected CoffeMachine machine;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name=name;
}
protected String getRecipe()
{
return this.machine.brew();
}
protected int getPrice() {
return this.price;
}
}
CoffeeInform의 역할: 커피에 대한 정보를 가지고 있다. coffe의 이름,레시피,가격 정보를 담고 있다.
커피로는 base로만 생성하기 때문에 해당 클래스를 상속받은 base들은 레시피가 자신의 machine을 내리는 것이다.
package coffemaker3;
public abstract class CoffeDecorator extends CoffeeInform{
protected CoffeeInform decoratedCoffee;
public CoffeDecorator() {
}
public CoffeDecorator(CoffeeInform decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
protected String getRecipe() {
// TODO Auto-generated method stub
return decoratedCoffee.getRecipe()+recipe;
}
@Override
protected int getPrice() {
// TODO Auto-generated method stub
return decoratedCoffee.getPrice()+price;
}
}
핵심 코드이다.
커피 데코레이터는 자신이 쌓는 그 커피정보를 상속한다.
decoratedCoffe라는 자신이 그 위에 정보를 쌓을 정보를 받는다. (정보 쌓기 전 커피정보)
getRecipe와 getPrice는 오버라이딩 하여 쌓을 정보( decoratedCoffe )에 자신의 정보를 쌓는다.
package coffemaker3;
public class EsspressoBase extends CoffeeInform {
public EsspressoBase(CoffeMachine machine) {
super();
this.machine = machine;
this.name="에스프레소";
this.price=2000;
}
}
에스프레소 베이스이다. 모든 커피는 베이스위에 쌓는다.
package coffemaker3;
public class HotWaterDecorator extends CoffeDecorator{
public HotWaterDecorator(CoffeeInform decoratedCoffee) {
super(decoratedCoffee);
this.price=500;
this.recipe="뜨거운 물 추가. ";
// TODO Auto-generated constructor stub
}
}
데코레이터를 구체화한 클래스
뜨거운 아메리카노는 에스프레소 위에 뜨거운 물을 추가한다.
package coffemaker3;
public class CoffeeMaker {
public void makeCoffe(CoffeeInform coffee) {
String recipeSteps = coffee.getRecipe();
System.out.print( "커피 만드는 중... "+recipeSteps + " => " + coffee.getName() + " 완성!\n");
}
}
CoffeeMaker는 커피 정보를 받아서 레시피를 쓴다. 레시피로 해당 커피를 만든다.
package coffemaker3;
public class CoffeeCalculator {
public void calculatePrice(CoffeeInform coffee) {
System.out.print(coffee.getPrice()+ "원 입니다.\n");
}
}
CoffeeCaculator는 가격을 계산한다. 커피 정보를 받아서, Price를 출력한다.
package coffemaker3;
import java.util.Scanner;
public class CoffeeMakerTest {
public static void main(String[] args) {
CoffeMachine machine = new DripCoffeeMachine();
CoffeeCalculator calulaotor = new CoffeeCalculator();
CoffeeMaker coffemaker = new CoffeeMaker ();
CoffeeInform espresso=new EsspressoBase(machine); //에스프레소 생성
coffemaker.makeCoffe( espresso);
calulaotor.calculatePrice(espresso);
//뜨거운아메리카노 만들기
CoffeeInform americano=new HotWaterDecorator(espresso);
americano.setName("뜨거운 아메리카노");
coffemaker.makeCoffe(americano);
calulaotor.calculatePrice(americano);
//아이스 아메리카노 만들기
CoffeeInform icedAmericanop = new IceDecorator(espresso);
CoffeeInform icedAmericano = new ColdWaterDecorator(icedAmericanop);
icedAmericano.setName("아이스 아메리카노");
coffemaker.makeCoffe(icedAmericano);
calulaotor.calculatePrice(icedAmericano);
}
}
main코드
에스프레소를 생성하고, 위에 쌓는다.

출력 성공!
기본 커피 베이스를 생성하여, 얼음을 추가하거나, 시럽을 추가하는 동작을 수행할 수 있다.
다양한 커피를 만들 수 있다. (라떼같은 종류가 추가되더라도 이미 구현한 데코레이터를 통해 만들 수 있다.)
아이스와 핫을 쉽게 구분할 수 있어졌다.
클라이언트 코드는 데코레이터와 컴포넌트를 동일한 클래스로 다룰 수 있어 코드의 일관성을 유지하면서도 다양한 기능을 적용할 수 있다.
추가로 패턴 조사
빌더(Builder) 패턴
현재 코드에서는 커피를 조합하는 과정이 복잡하고, 어떤 순서대로 넣어야할지 클라이언트가 알아야한다.
// 클라이언트가 아이스 아메리카노의 제조법을 전부 알아야 함
CoffeeInform icedAmericanop = new IceDecorator(espresso);
CoffeeInform icedAmericano = new ColdWaterDecorator(icedAmericanop);
이러한 복잡한 객체 과정을 캡슐화 하고, 클라이언트의 역할을 단순화 시키는데 적합한 패턴이 빌더 패턴이다.
빌더 패턴은 복잡한 객체를 만드는과정을 함수로 관리하는 생성 패턴이다.
장점: 다양한 재료 조합(레시피)을 가진 객체를 동일한 생성 프로세스를 통해 만들 수 있다.
BEFORE (현재 코드)
// CoffeeMakerTest.java
// 아이스 아메리카노 만들기
CoffeeInform icedAmericanop = new IceDecorator(espresso);
CoffeeInform icedAmericano = new ColdWaterDecorator(icedAmericanop);
icedAmericano.setName("아이스 아메리카노");
AFTER (빌더 패턴 적용 후)
// CoffeeMakerTest.java
// CoffeeBuilder를 통해 아이스 아메리카노 만들기
CoffeeInform icedAmericano = new CoffeeBuilder("아이스 아메리카노")
.setBase(new EsspressoBase(machine))
.addIce()
.addColdWater()
.build();
코드가 깔끔해지고, 커피를 만드는 과정이 명확하게 보인다.
CoffeeBuilder 클래스
package coffemaker3;
public class CoffeeBuilder {
private CoffeeInform coffee;
private String finalName;
public CoffeeBuilder(String name) {
this.finalName = name;
}
public CoffeeBuilder setBase(CoffeeInform base) {
this.coffee = base;
return this; // 베이스를 설정하고 추가 메서드를 부르기 위해 자신을 반환
}
public CoffeeBuilder addHotWater() {
this.coffee = new HotWaterDecorator(this.coffee);
return this;
}
public CoffeeBuilder addColdWater() {
this.coffee = new ColdWaterDecorator(this.coffee);
return this;
}
public CoffeeBuilder addIce() {
this.coffee = new IceDecorator(this.coffee);
return this;
}
// 다른 데코레이터 추가...
public CoffeeInform build() {
if (coffee == null) {
throw new IllegalStateException("베이스 커피가 설정되지 않았습니다.");
}
this.coffee.setName(this.finalName);
return this.coffee;
}
}
클라이언트 코드에서 데코레이터를 생성하지 않고, 사용할 수 있다. 클라이언트 코드에서 볼 때 어떤 커피를 만드는지 더 명확해진다.
빌더패턴의 장점!
1. 가독성 상승
- 코드가 무엇을 하는지 명확하게 드러난다. -> 유지 보수가 쉬워진다.
2. 복잡한 객체 생성을 쉽게 처리해요.
- 필수 값과 선택 값을 유연하게 설정할 수 있습니다. 생성자처럼 모든 값을 한 번에 전달할 필요 없이, 필요한 값만 메서드를 통해 설정하면 됩니다.
3. 객체의 일관성을 보장해요.
- 객체 생성 과정을 단계별로 진행하다가 마지막에 build() 메서드를 호출할 때, 필수 값이 제대로 설정되었는지 검증할 수 있다.
4. 불변(Immutable) 객체를 만들기 쉬워요.
- 빌더 패턴을 사용하면 불변 객체를 만들 수 있. set을 사용할 일이 없다.
(클라이언트가 setName을 하지 않아도 된다.)
gemini는 여기서 빌더 패턴은 set을 만들지 않아도 된다라고 하여,완전한 불변으로 만드려면 내 코드에서 setName을 지우라고 했다. 그러면 CoffeeInform 생성자에서 이름을 받도록 하여, base를 생성할 때, 어떤 커피를 만들지 정하는 형태로 코드를 작성하면 될것같다.