Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .idea/sonarlint/issuestore/index.pb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added img/decorator/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/decorator/img2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/decorator/img3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions src/decorator/DecoratorPattern.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package decorator;

import decorator.beverage.DarkRoast;
import decorator.beverage.Espresso;
import decorator.beverage.HouseBlend;
import decorator.beverage.base.Beverage;
import decorator.beverage.enums.Size;
import decorator.decorator.Mocha;
import decorator.decorator.Soy;
import decorator.decorator.Whip;

public class DecoratorPattern {
public static void main(String[] args) {
decorating();
decoratingWithSize();
}

private static void decorating() {
final Beverage espresso = new Espresso();
printBeverage(espresso);

Beverage darkRoast = new DarkRoast();
printBeverage(darkRoast); // 일반 다크 로스트 커피

darkRoast = new Mocha(darkRoast); // mocha로 감싸기
darkRoast = new Mocha(darkRoast); // mocha로 한 번 더 감싸기
darkRoast = new Whip(darkRoast); // 휘핑크림 추가
printBeverage(darkRoast); // 모카샷2 + 휘핑크림 추가한 다크 로스트 커피

Beverage houseBlend = new HouseBlend();
printBeverage(houseBlend); // 일반 하우스 블랜드 커피

houseBlend = new Soy(houseBlend);
houseBlend = new Mocha(houseBlend);
houseBlend = new Whip(houseBlend);
printBeverage(houseBlend); // 두유 + 모카샷 + 휘핑크림 추가한 블랜드 커피
}
Comment on lines +18 to +37
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

객체를 여러 번 감싸므로써 행동을 위임하여 새로운 기능/데이터를 더한다.

  • 지금은 new 키워드로 객체를 생성하지만, 추후에 팩토리 패턴/빌더 패턴과 같이 더 나은 방법으로 객체를 생성할 수 있다.


private static void decoratingWithSize() {
Beverage darkRoast = new DarkRoast();
printBeverage(darkRoast); // 톨 사이즈의 다크 로스트 커피

darkRoast.setSize(Size.VENTI);
printBeverage(darkRoast); // 벤티 사이즈의 다크 로스트 커피

darkRoast = new Mocha(darkRoast); // mocha로 감싸기
darkRoast = new Mocha(darkRoast); // mocha로 한 번 더 감싸기
darkRoast = new Whip(darkRoast); // 휘핑크림 추가
printBeverage(darkRoast); // 모카샷2 + 휘핑크림 추가한 벤티 사이즈의 다크 로스트 커피
}

private static void printBeverage(Beverage beverage) {
System.out.println(beverage.getSize() + ": " + beverage.getDescription() + " $" + beverage.cost());
}
}
15 changes: 15 additions & 0 deletions src/decorator/beverage/DarkRoast.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package decorator.beverage;

import decorator.beverage.base.Beverage;

public class DarkRoast extends Beverage {

public DarkRoast() {
description = "다크 로스트 커피"; // description 변수는 Beverage로부터 상속받음
}

@Override
public double cost() {
return 0.99 * sizeCostMap.get(super.getSize());
}
}
15 changes: 15 additions & 0 deletions src/decorator/beverage/Decaf.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package decorator.beverage;

import decorator.beverage.base.Beverage;

public class Decaf extends Beverage {

public Decaf() {
description = "디카페인 커피"; // description 변수는 Beverage로부터 상속받음
}

@Override
public double cost() {
return 0.89 * sizeCostMap.get(super.getSize());
}
}
15 changes: 15 additions & 0 deletions src/decorator/beverage/Espresso.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package decorator.beverage;

import decorator.beverage.base.Beverage;

public class Espresso extends Beverage {

public Espresso() {
description = "에스프레소"; // description 변수는 Beverage로부터 상속받음
}

@Override
public double cost() {
return 1.99 * sizeCostMap.get(super.getSize());
}
}
15 changes: 15 additions & 0 deletions src/decorator/beverage/HouseBlend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package decorator.beverage;

import decorator.beverage.base.Beverage;

public class HouseBlend extends Beverage {

public HouseBlend() {
description = "하우스 블렌드 커피"; // description 변수는 Beverage로부터 상속받음
}

@Override
public double cost() {
return 0.89 * sizeCostMap.get(super.getSize());
}
}
36 changes: 36 additions & 0 deletions src/decorator/beverage/base/Beverage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package decorator.beverage.base;

import decorator.beverage.enums.Size;

import java.util.EnumMap;

public abstract class Beverage {

protected static final EnumMap<Size, Double> sizeCostMap;

static {
sizeCostMap = new EnumMap<>(Size.class);
sizeCostMap.put(Size.TALL, 1.0);
sizeCostMap.put(Size.GRANDE, 1.2);
sizeCostMap.put(Size.VENTI, 1.5);
}

protected Size size = Size.TALL;
protected String description = "제목 없음";

// 추상 클래스에서 getDescription() 을 미리 구현됨.
public String getDescription() {
return description;
}

public Size getSize() {
return size;
}

public void setSize(Size size) {
this.size = size;
}

// cost() 는 서브 클래스에서 구현해야 함.
public abstract double cost();
}
Comment on lines +7 to +36
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beverage를 추상 클래스 또는 인터페이스로 정의하여 데코레이터 패턴을 활용할 수 있음

5 changes: 5 additions & 0 deletions src/decorator/beverage/enums/Size.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package decorator.beverage.enums;

public enum Size {
TALL, GRANDE, VENTI
}
24 changes: 24 additions & 0 deletions src/decorator/decorator/Mocha.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package decorator.decorator;

import decorator.beverage.base.Beverage;
import decorator.decorator.base.CondimentDecorator;

public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
// 장식하고 있는 객체(beverage)에 cost() 작업을 위임하여 리턴값을 구한뒤
// 그 결과에 모카 가격을 사이즈게 맞게 추가로 계산
return beverage.cost() + 0.35;
}

@Override
public String getDescription() {
// 장식하고 있는 객체(beverage)에 대한 설명에
// 그 결과에 모카에 대한 설명을 추가로 붙힘
return beverage.getDescription() + ", 모카";
}
}
Comment on lines +6 to +24
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구상 클래스는 구상 요소에 맞게 새로운 기능/데이터를 더하여 행동을 확장한다.

  • cost() : return beverage.cost() + 0.35; (0.35 <- 새로운 데이터)
  • getDescription() : return beverage.getDescription() + ", 모카"; (모카 <- 새로운 데이터)

25 changes: 25 additions & 0 deletions src/decorator/decorator/Soy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package decorator.decorator;

import decorator.beverage.base.Beverage;
import decorator.decorator.base.CondimentDecorator;

public class Soy extends CondimentDecorator {

public Soy(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
// 장식하고 있는 객체(beverage)에 cost() 작업을 위임하여 리턴값을 구한뒤
// 그 결과에 두유 가격을 사이즈게 맞게 추가로 계산
return beverage.cost() + 0.11;
}

@Override
public String getDescription() {
// 장식하고 있는 객체(beverage)에 대한 설명에
// 그 결과에 두유에 대한 설명을 추가로 붙힘
return beverage.getDescription() + ", 두유";
}
}
25 changes: 25 additions & 0 deletions src/decorator/decorator/Whip.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package decorator.decorator;

import decorator.beverage.base.Beverage;
import decorator.decorator.base.CondimentDecorator;

public class Whip extends CondimentDecorator {

public Whip(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
// 장식하고 있는 객체(beverage)에 cost() 작업을 위임하여 리턴값을 구한뒤
// 그 결과에 휘핑크림 가격을 사이즈게 맞게 추가로 계산
return beverage.cost() + 0.15;
}

@Override
public String getDescription() {
// 장식하고 있는 객체(beverage)에 대한 설명에
// 그 결과에 휘핑크림에 대한 설명을 추가로 붙힘
return beverage.getDescription() + ", 휘핑크림";
}
}
25 changes: 25 additions & 0 deletions src/decorator/decorator/base/CondimentDecorator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package decorator.decorator.base;

import decorator.beverage.base.Beverage;
import decorator.beverage.enums.Size;

/**
* 첨가물
*/
public abstract class CondimentDecorator extends Beverage {
/**
* 각 데코레이터가 감쌀 음료를 나타내는 Beverage 객체를 저장할 인스턴스 변수.
* 모든 Beverage의 구상 클래스를 담기 위해 "Beverage"라는 슈퍼클래스 유형을 사용한다.
* <p>
* Wrapping 하여 데코레이팅한다.
*/
protected Beverage beverage;

@Override
public abstract String getDescription();

@Override
public Size getSize() {
return beverage.getSize();
}
}
Comment on lines +6 to +25
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데코레이터 클래스의 형식은 해당 클래스가 감싸는 클래스의 형식과 동일하다.
형식을 동일하게 하기 위해 "상속"이나 "인터페이스 구현"을 활용할 수 있다.

헤드퍼스트 디자인패턴 예시에는 "상속"으로 형식을 동일하게 하였다.

45 changes: 45 additions & 0 deletions src/decorator/데코레이터패턴.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 상속의 문제점

> concrete class가 너무 많아짐
>

스타벅스 커피 전문점의 음료를 나타내는 추상 클래스 : Beverage를 두고
메뉴가 추가될때마다 서브 클래스를 추가하게 되면 클래스가 겉잡을 수 없이 많아진다.

> 컴파일 시점에 행동이 정적으로 정해짐
>

# 디자안 원칙 :OCP

**Open-Closed Principle**

> 클래스의 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

기존 코드를 건드리지 않고 확장이 가능할까? -> observer 패턴

# 데코레이터 패턴

![img.png](../../img/decorator/img.png)
![img.png](../../img/decorator/img2.png)

> 객체에 추가 요소를 동적으로 더할 수 있는 패턴으로, 서브클래스를 만들 때보다 유연하게 기능읗 확장할 수 있는 특징이 있다.

- 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 동일하다
- 그렇기 때문에, wrapping된 객체가 들어갈 자리에 데코레이터 객체를 넣을 수 있다
- 하나의 객체를 여러 데이코레이터로 감쌀(Wrapping) 수 있다
- **데코레이터는 객체에 행동을 위임하는 일과 함께 추가 작업을 수행할 수 있다** (키 포인트)

## 데코레이터 패턴 예제에서 "상속"이 사용되는 이유

데코레이터의 특징은 "데코레이터로 감싸는 객체"의 형식과 "데코레이터"의 형식이 같다는 것이다.
이 형식을 "상속"으로서 맞추는 것이다. (도구로서 상속이 활용 됨)

- 상속은 행동이 컴파일 시점에 정해져버리지만, 데코레이터 패턴은 구성을 활용하여 실행중에 조합해서 사용할 수 있다.

또한 기존 코드에서 Beverage 라는 추상 클래스를 사용하고 있기 때문에 기존 코드를 고치지 않고 "상속"으로 형식을 맞추었다.

- 상속 대신 "인터페이스"로 형식을 맞출 수 있다.

## 데코레이터 패턴의 예시 : 자바 I/O

![img.png](../../img/decorator/img3.png)
1 change: 1 addition & 0 deletions src/observer/ObserverPattern.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ public static void main(String[] args) {
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 22.4f);

}
}
3 changes: 2 additions & 1 deletion src/observer/subject/impl/WeatherData.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void setMeasurements(float temperatures, float humidity, float pressure)
notifyObservers();
}


@Override
public void registerObserver(Observer observer) {
observers.add(observer);
Expand All @@ -42,7 +43,7 @@ public void removeObserver(Observer observer) {
public void notifyObservers() {
for (final Observer observer : observers) {
// (push: subject -> observer) subject 클래스에서 observer들에게 데이터를 push 해준다.
// observer.updateByPush(temperatures, humidity, pressure);'
// observer.updateByPush(temperatures, humidity, pressure);

// (pull : observer <- subject) observer들이 각자 subject의 데이터를 pull한다.
// push보단 pull이 좋다.
Expand Down