티스토리 뷰
* 요구 상황 가정
- 자동차의 종류 별로(기본, 세단, SUV, 트럭 등) 특정한 타이어, 브레이크, 엔진 오일 등의 부품들을 추가해서 완제품으로 구성하려고 한다.
- 자동차에는 기본 성능 점수가 배정되어 있고, 각 부품을 추가할 때마다 각각의 부품에 해당하는 성능 점수를 자동차의 기본 성능 점수에 더해 산출한다.
* 생각해볼 수 있는 방식
1)

- 클래스 개수가 넘 많아짐. 너무 추상화가 안되어 있음.
2)

package nodecorator;
public class Car {
boolean fastBrake;
boolean thickTire;
boolean goodEngineOil;
public boolean hasFastBrake() {
return fastBrake;
}
public boolean hasThickTire() {
return thickTire;
}
public boolean hasGoodEngineOil() {
return goodEngineOil;
}
public int addPerformanceRating() {
int rating = 0;
if (hasThickTire()) {
rating += 10;
}
if (hasFastBrake()) {
rating += 20;
}
if (hasGoodEngineOil()) {
rating += 30;
}
return rating;
}
}
package nodecorator;
public class BasicCar extends Car{
int performanceRating = 10;
public BasicCar() {
fastBrake = true;
goodEngineOil = true;
thickTire = false;
}
@Override
public int addPerformanceRating() {
return this.performanceRating + super.addPerformanceRating();
}
}
package nodecorator;
public class Main {
public static void main(String[] args) {
Car basicCar = new BasicCar();
System.out.println(basicCar.addPerformanceRating());
}
}
-문제점:
1. 단일책임원칙 위반: 하나의 클래스는 하나의 책임만 져야한다. -> Car 클래스에서 브레이크, 엔진오일, 타이어 등의 책임을 지고 있음.
2. 개방폐쇄원칙 위반: 부품의 종류가 더 추가됨에 따라 Car 클래스의 메소드를 전부 수정해줘야 한다. 또한 상속 받은 BasicCar 클래스의 생성자 부분도 수정해야된다.
3. 불필요한 메소드의 상속: 새로운 차종이 추가될 경우, 해당 차종이 좋은 엔진오일이 필요 없을 때, 즉 hasGoodEngineOil()이 필요없을 때에도 불구하고 상속을 받을 수 있음.
3) 데코레이터 패턴 적용
* 데코레이터란? (Decorator) - 장식자
* 무엇을 장식할까? - 위의 예에서는 Suv, Sedan, BasicCar
* 무엇으로? - FastBrake, GoodEngineOil 등
* 클래스 다이어 그램 (Suv 클래스를 FastBrake와 ThickTire로 장식하는 상황 가정)

* 코드 예시
- Car 클래스
package org.example.decorator;
public abstract class Car {
private int performanceRating;
public abstract int addPerformanceRating();
}
- Suv 클래스
package org.example.decorator;
public class Suv extends Car {
private final int performanceRating = 10;
@Override
public int addPerformanceRating() {
return performanceRating;
}
}
- CarDecorator 클래스
package org.example.decorator;
public abstract class CarDecorator extends Car{
protected Car car;
public CarDecorator(Car car) {
this.car = car;
}
public abstract int addPerformanceRating();
}
- FastBrake 클래스
package org.example.decorator;
public class FastBrake extends CarDecorator{
public FastBrake(Car car) {
super(car);
}
@Override
public int addPerformanceRating() {
return car.addPerformanceRating() + 10;
}
}
-ThickTire 클래스
package org.example.decorator;
public class ThickTire extends CarDecorator{
public ThickTire(Car car) {
super(car);
}
@Override
public int addPerformanceRating() {
return car.addPerformanceRating() + 20;
}
}
- Main 클래스
package org.example.decorator;
public class DecoratorMain {
public static void main(String[] args) {
Car suv = new FastBrake(new ThickTire(new Suv()));
System.out.println(suv.addPerformanceRating());
}
}
- 메모리 상 객체 생성 과정
new Suv() -> 1번
new ThickTire(new Suv()) -> 2번
new FastBrake(new ThickTire(new Suv())) -> 3번
Car suv = -> 4번


4) 앞서 말한 문제점이 해결 됐나?
1. 단일책임원칙 -> FastBrake, ThickTire 등 클래스를 분리하여 책임을 분산하였다.
2. 개방폐쇄원칙 -> 클래스 다이어그램을 보면 알 수 있듯이, Suv 클래스 입장에선 장식자가 기능을 추가해줌으로써 Suv 클래스 내부를 전혀 건드리지 않고 기능을 추가할 수 있다.
3. 불필요한 메소드의 상속 -> 기능 별로 클래스를 분리하면서 자연스럽게 해결.
5) 데코레이터 패턴의 특징 정리
- 장식 대상은 데코레이터의 존재를 알 수 없다.
- 데코레이터의 부모 클래스는 자신이 장식하려고 하는 객체의 부모 클래스와 동일하게 설정.
- 데코레이터 안에는 부모 클래스의 레퍼런스가 있다. (Car car)
- 한 객체를 여러 개의 데코레이터로 감쌀 수 있음.
- FastBrake, ThickTire 클래스의 addPerformanceRating() 메소드 내부에 추가적인 기능을 구현할 수 있음.
- 프로그램 실행 중 동적으로 데코레이터를 사용하여 객체에 추가 요소를 더할 수 있다.
단점
- Suv 객체를 '필요로 하는' 로직을 처리해야할 경우에는, 데코레이터 패턴이 적절하지 않다. (외부에서 감싸고 있기 때문)
- 실행 단계가 중요하지 않음 -> 다시 말하면, 데코레이터 생성 순서가 중요한 경우에는 적합하지 않을 수 있음.
- 기능을 추가할 때 마다 객체를 추가해줘야함.
- 데코레이터를 많이 사용하면 코드가 복잡해짐.
6) 느낀점
- Java I/O에 대한 궁금증 어느 정도 해결
- 객체지향설계원칙을 지키기 위한 과정은 험난하다..