OCP는 가장 중요한 원칙가운데 하나이다.
다른글에서도 설명했지만 확장에는 open이지만, 변경에는 closed라는 뜻이다.
즉, 클래스를 확장하는것은 가능하지만, 코드를 고치는건 안된다는 뜻이다.
뭔가 모순되는 말 같지만, 옵저버 패턴에서도 본 것 처럼, 기존의 인터페이스 코드, 혹은 상위클래스 코드는 하나도 건드리지 않고 확장하는것이 가능하다. (물론 이를 적용시키려면 클라이언트 코드를 살짝 수정하긴 해야한다.)
그리고 OCP는 지키면 좋지만, 모든 코드에서 지킬 수 도 없고 안지키고 짜는게 더 나을때도 있다.
데코레이터 패턴이란 객체에 추가적인 요건을 동적으로 첨가하여 서브클래스를 만드는것을 통해 동적으로 유연한 확장을 제공하는것이다.
근데 이거 글로만 보니깐 좀 이해가 안간다. 백문이 불여일타라고 일단 코딩을 해보자
package starbuzz;
public abstract class Beverage {
String description = "No Name";
public String getDescription() {
return description;
}
public abstract double cost();
}
package starbuzz;
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
Beverage는 Espresso같은 음료 클래스의 상위클래스이다.
CondimentDecorator는 첨가물의 상위클래스이다. Descriptor를 Beverage로부터 받아온다.
package starbuzz;
public class Espresso extends Beverage{
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
에스프레소라는 음료를 구현한 클래스이다. Beverage의 descriptor를 받아서 자신의 음료명을 전달하고 cost를 전달한다.
package starbuzz;
public class Whip extends CondimentDecorator{
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return .10 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
}
Whip은 첨가물이다. 생성자로 beverage를 받아서 beverage.cost에 자신의 값을 더해서 반환하고, 설명 역시 beverage description에 자신의 첨가물명을 첨부해서 반송한다.
package starbuzz;
public class StarBuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage1 = new DarkRoast();
//System.out.println(beverage1.getDescription() + " $" + beverage1.cost());
beverage1 = new Mocha(beverage1);
beverage1 = new Mocha(beverage1);
beverage1 = new Whip(beverage1);
System.out.println(beverage1.getDescription() + " $" + beverage1.cost());
Beverage beverage2 = new HouseBlend();
//System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
beverage2 = new Soy(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}
Espresso $1.99
Dark Roast, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34
첫번째는 단순히 음료클래스만 구현을 한 경우이다.
두번째는 음료에 모카2개, 휩 1개 추가한 모습이다.
DarkRoast라는 음료를 가진 객체 beverage1에 Mocha라는 새로운 객체를 추가한다. 그러면 이는 데코레이터 형식으로 작동 DarkRoast를 감싸게되고,
내부 메소드들에 의하여 beverage의 코스트를 '자신의 코스트 + 신규 코스트'로 바꾸게 된다. 설명 역시 마찬가지로 작동한다.
이를 반복하면 결국 출력결과가 완성이 된다.
getSize, setSize를 Beverage 클래스에 추가하고 이를 통해 사이즈를 설정하고, 가격변동을 주는 기능을 추가할 수 있다.
package starbuzz;
public abstract class Beverage {
String description = "No Name";
String size = "No Size";
public String getDescription() {
return description;
}
public abstract double cost();
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
setSize는 외부에서 들어온 size값을 Beverage의 size로 바꿔주는 역할을 한다.
getSize는 size값을 반환해준다.
package starbuzz;
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public String getSize() {
return beverage.getSize();
}
@Override
public double cost() {
double cost = beverage.cost();
if (getSize().equals("Tall") ) {
cost += .10;
} else if (getSize().equals("Grande")) {
cost += .15;
} else if (getSize().equals("Venti")) {
cost += .20;
}
return cost;
}
}
이를 활용하여 모카의 가격정책을 다르게 설정할 수 있다.
Beverage beverage = new Espresso();
beverage.setSize("Venti"); // 실제로는 입력값을 넣어주면 될것같다.
System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());
beverage = new Mocha(beverage);
System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());
//beverage.setSize("Grande");
//System.out.println(beverage.getDescription() + beverage.getSize() + " $" + beverage.cost());
이렇게 하면
Espresso Venti $1.99
Espresso , MochaVenti $2.19
같은 결과가 나온다.
다만 문제점이 있는데
만약 사이즈의 변경이 생긴다면, 그 값이 덮어써지지 않는다는점이다.
데코레이터패턴에 대해서도 공부를 했다. 뭔가 지금까지 배운 패턴들이 다 그게 그거같은데 또 미묘하게 다른 무언가가 있다. 머리로는 이해가 가는데 막상 코드를 짜보려하면 잘 짜지지 않는게 좀 어려운 부분이다.