1. μ μ
π‘ λ°μ½λ μ΄ν° ν¨ν΄ (decorator pattern)
κ°μ²΄μ μΆκ°μ μΈ μ건μ λμ μΌλ‘ 첨κ°νλ€.
λ°μ½λ μ΄ν°λ μλΈν΄λμ€λ₯Ό λ§λλ κ²μ ν΅ν΄μ κΈ°λ₯μ μ μ°νκ² νμ₯ν μ μλ λ°©λ²μ μ 곡νλ€.
2. μ) μ»€νΌ μ£Όλ¬Έ μμ€ν
νμ¬μ μ»€νΌ μμ€ν μ λ€μκ³Ό κ°μ΄ λμ΄ μλ€.
Beverage λΌλ μνΌν΄λμ€κ° μ‘΄μ¬νκ³ , μ΄ ν΄λμ€λ₯Ό μμνμ¬ Espresso, Decaf λ± κ° μλ£ ν΄λμ€κ° μλ€.
// Beverage μνΌ ν΄λμ€
public abstract class Beverage {
String description = "λ¬΄μ¨ μλ£";
// getter
public String getDescription() {
return description;
}
/** κ°κ²©μ λ€λ₯΄λ―λ‘ κ° ν΄λμ€μμ κ²°μ */
public abstract Integer cost();
}
// Beverage λ₯Ό μμλ°μ λ§λ Espresso ν΄λμ€
public class Espresso extends Beverage {
public Espresso() {
description = "μμ€νλ μ";
}
@Override
public Integer cost() {
return 3000;
}
}
// κ·ΈμΈ μλ£ ν΄λμ€..β
κ·Έλ°λ°, μλ‘μ΄ μ£Όλ¬Έλ°©λ²μ λμ νλ€κ³ νλ€. κ³ κ°μ΄ μνλ κ°μ’ 컀μ€ν μ μ§μνλ€.
컀νΌλ₯Ό μ£Όλ¬Έν λ μνλ μλ½μ μΆκ°νκ±°λ λΊμλ, μ°μ λ₯Ό λμ λ‘ λ°κΎΈλ λ±μ 컀μ€ν μ μ§μνλ©° κ°κ²©μ λͺ¨λ λ€λ₯΄λ€.
- μμ€νλ μμ λμ μΆκ°ν΄μ£Όμκ³ ννν¬λ¦Ό μΉμ΄μ£ΌμΈμ. (EspressoWithSoyAndWhip ν΄λμ€ μμ±)
- λμΉ΄νμΈμ μ°μ μΆκ°ν΄μ£ΌμΈμ. (DecafWithWholeMilk ν΄λμ€ μμ±)
μ΄λ κ² λͺ¨λ μꡬμ¬νμ μ‘°ν©νμ¬ ν΄λμ€λ₯Ό λ§λ€λ©΄ κ·Έ μκ° λ무 λ§μμ§λ€. λ€λ₯Έ λ°©λ²μ κ³ μν΄μΌ νλ€.
μνΌ ν΄λμ€μ milk, soy, whip λ±μ boolean λ³μλ₯Ό μΆκ°νν νμ ν΄λμ€μμ cost() μμ λν΄μ£Όλ λ°©μμ μ΄λ¨κΉ?
// μνΌν΄λμ€μ extra λ₯Ό boolean λ³μλ‘ μ μνκ³ getter, setter μΆκ°
public abstract class BeverageNo {
String description = "λ¬΄μ¨ μλ£";
boolean milk; // μ°μ μΆκ°μ¬λΆ λ³μ
boolean soy; // λμ μΆκ°μ¬λΆ λ³μ
boolean whip; // νν μΆκ°μ¬λΆ λ³μ
boolean mocha; // λͺ¨μΉ΄ μΆκ°μ¬λΆ λ³μ
// getter
public String getDescription() {
return description;
}
/** μΆμ λ©μλ : κ°κ²©μ κ° μλ£ ν΄λμ€μμ κ²°μ */
public abstract Integer cost();
// boolean λ³μλ€μ getter, setter
public boolean isMilk() {
return milk;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
// soy, whip, mocha μ getter, setter λ μλ΅..
}
// Beverage λ₯Ό μμλ°μ κ° μλ£ ν΄λμ€μμ boolean λ³μλ‘ cost() κ³μ°νλ€.
public class Espresso extends BeverageNo {
public Espresso() { description = "μμ€νλ μ"; }
@Override
public Integer cost() {
Integer result = 3000;
// κ° extra λ³μλ₯Ό νμΈνκ³ true μ΄λ©΄ κ°κ²©μ λν΄μ€λ€
if (milk) { result += 2000; }
if (soy) { result += 3000; }
if (mocha) { result += 1500; }
if (whip) { result += 1500; }
return result;
}
}
// λ€λ₯Έ μλ£λ€ μλ΅..
μ΄λ κ² μμμ ν΅ν΄ κ° κ΅¬ν ν΄λμ€μμ μ€λ²λΌμ΄λλ‘ κ΅¬ννλ λ°©μμ΄ μ’μ λ°©μμΌκΉ?
κ° ν΄λμ€λ§λ€ λ³κ²½μ΄ μμΌλ©° μ½λ μ€λ³΅λ λ°μνλ μ’μ λ°©λ²μ΄λΌ λ³Ό μ μλ€.
μμλ§ νλ€κ³ ν΄μ μ μ°νκ³ κ΄λ¦¬κ° μ¬μ΄ κ²μ΄ μλλ€. ꡬμ±κ³Ό μμμ ν΅ν΄ μ€ν μ€μ νλμ μμ μν€λ λ°©λ²μ΄ μλ€.
μμκ°μ΄ μλΈν΄λμ€λ₯Ό λ§λλ λ°©μμΌλ‘ νλμ μμλ°μΌλ©΄ κ·Έ νλμ μ»΄νμΌμ ν λ μμ ν κ²°μ λλ€. κ²λ€κ° λͺ¨λ μλΈν΄λμ€μμ κ°μ νλμ μμλ°μμΌνλ€. κ·Έλ¬λ ꡬμ±μ ν΅ν΄ κ°μ²΄μ νλμ νμ₯νλ©΄ μ€νμ€μ λμ μΌλ‘ κ²°μ ν μ μλ€.
κ°μ²΄λ₯Ό λμ μΌλ‘ ꡬμ±νλ©΄, κΈ°μ‘΄ μ½λλ₯Ό κ³ μΉλ λμ μ μ½λλ₯Ό λ§λ€μ΄ μ κΈ°λ₯μ μΆκ°ν μ μλ€. κΈ°μ‘΄ μ½λλ₯Ό λ°κΎΈμ§ μμΌλ―λ‘ μλμΉ μμ λ²κ·Έλ₯Ό λ°©μ§ν μ μλ€.
β λμμΈ μμΉ OCP (Open - Closed Principle)
π λμμΈ μμΉ OCP (Open - Closed Principle)
ν΄λμ€λ νμ₯μ λν΄μλ μ΄λ € μμ΄μΌνμ§λ§ μ½λ λ³κ²½μ λν΄μλ λ«ν μμ΄μΌ νλ€.
μμμ μ¨μ μλ£κ°κ²©κ³Ό Extra κ°κ²©μ ν©ν΄ κ³μ°νλ λ°©λ²μ ν΄λμ€κ° μ΄λ§μ΄λ§νκ² λ§μμ§κ±°λ, μλΈν΄λμ€μ μ ν©νμ§μμ κΈ°λ₯μ μΆκ°νκ² λ μλ μλ€. μ¦, μλ£μμ μμν΄μ μμ€νΈλΌλ‘ μ₯μ(decorate) νμ!
- Espresso κ°μ²΄λ₯Ό κ°μ Έμ¨λ€.
- Soy κ°μ²΄λ‘ μ₯μνλ€.
- Whip κ°μ²΄λ‘ μ₯μνλ€.
- cost() λ©μλλ₯Ό νΈμΆνλ€. μ΄λ 첨κ°λ¬Όμ κ°κ²©μ κ³μ°νλ μΌμ ν΄λΉ κ°μ²΄λ€μκ² μμλλ€.
1. Beverage μνΌν΄λμ€
public abstract class Beverage {
String description = "λ¬΄μ¨ μλ£";
// getter
public String getDescription() {
return description;
}
/** μΆμ λ©μλ : μλΈν΄λμ€μμ κ°κ²©κ²°μ */
public abstract Integer cost();
}
2. ExtraDecorator ν΄λμ€ (Beverage λ₯Ό κΎΈλ©°μ£Όλ ν΄λμ€)
public abstract class ExtraDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
3. Espresso ν΄λμ€
public class Espresso extends Beverage {
public Espresso() {
description = "μμ€νλ μ";
}
@Override
public Integer cost() {
return 3000; // μμ€νλ μ κΈ°λ³Έ κ°κ²©
}
}
4. ExtraDecorator λ₯Ό μμλ°μ Soy ν΄λμ€, Mocha ν΄λμ€
// Soy
public class Soy extends ExtraDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public Integer cost() {
return beverage.cost() + 3000; // κΈ°λ³Έμ΄ λλ μλ£μ Soy μ κ°κ²©μ λν΄μ€
}
@Override
public String getDescription() {
return beverage.getDescription() + ", λμ "; // μ€λͺ
μλ μΆκ°
}
}
// Mocha
public class Mocha extends ExtraDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public Integer cost() {
return beverage.cost() + 1500; // μλ£μ Mocha μ κ°κ²©μ λν΄μ€
}
@Override
public String getDescription() {
return beverage.getDescription() + ", λͺ¨μΉ΄"; // μ€λͺ
μλ μΆκ°
}
}
5. κΈ°ν μλ£ λ° Extra ν΄λμ€ μλ΅..
6. μ€ν
@Slf4j
@Test
void espressoWithSoyAndMocha() {
Beverage esp = new Espresso();
Beverage withSoy = new Soy(esp);
Beverage withSoyMocha = new Mocha(withSoy);
log.info("espressoWithSoyAndMocha.getDescription() = {}", withSoyMocha.getDescription());
log.info("espressoWithSoyAndMocha.cost() = {}", withSoyMocha.cost());
Assertions.assertThat(withSoyMocha.cost()).isEqualTo(7500); // OK
}
@Test
void espressoWithSoy() {
Beverage esp = new Espresso();
esp = new Soy(esp);
log.info("espressoWithSoy.getDescription() = {}", esp.getDescription());
log.info("espressoWithSoy.cost() = {}", esp.cost());
Assertions.assertThat(esp.cost()).isEqualTo(6000); // OK
}
μΆλ ₯κ²°κ³Ό
1. espressoWithSoyAndMocha() κ²°κ³Ό
espressoWithSoyAndMocha.getDescription() = μμ€νλ μ, λμ , λͺ¨μΉ΄
espressoWithSoyAndMocha.cost() = 7500
2. espressoWithSoy() κ²°κ³Ό
espressoWithSoy.getDescription() = μμ€νλ μ, λμ
espressoWithSoy.cost() = 6000
β UML Class Diagram
- Component : ConcreteComponent μ Decorator κ° κ΅¬νν μΈν°νμ΄μ€
- ConcreteComponent : Componentλ₯Ό ꡬνν λ°μ½λ μ΄ν°λ‘ κΎΈλ©°μ€ λμ κ°μ²΄
- Decorator : Component λ₯Ό κ°μ§κ³ μλ μΆμ ν΄λμ€
- Decorator λ κΎΈλ©°μ€ λμμ΄ μμ΄μΌνλ€. κ·Έλ¬λ―λ‘ Component λ₯Ό κ°μ§κ³ μμ΄μΌνλλ° κ° λ°μ½λ μ΄ν°κ° μ»΄ν¬λνΈλ₯Ό κ°μ§λ©΄ μ½λμ€λ³΅μ΄ λ°μνλ€. κ·Έλμ Decoratorλ‘ μΆμ ν΄λμ€λ₯Ό λ§λ€μ΄ ν΄κ²°ν μ μλ€.
- ConcreteDecorator : Decorator λ₯Ό ꡬνν λ°μ½λ μ΄ν° κ°μ²΄
μ°Έκ³
ν€λ νΌμ€νΈ λμμΈ ν¨ν΄ (Head First Design Patterns) - μλ¦ ν리먼,μ리μλ² μ€ λ‘μ¨,μΌμ΄μ μμλΌ,λ²νΈ λ² μ΄μΈ