예제 상황
다양한 음료를 모두 포괄하는 주문 시스템 구축
각각의 음료를 별도의 클래스로 하나 하나 만들게 되면 클래스가 너무 많아져서 폭발해버린다.
그렇다고 서브클래스를 만드는 방식으로 행동을 상속받으면,
- 그 행동은 컴파일 시 완전히 결정되고
- 모든 서브클래스에서 똑같은 행동을 상속받아야 한다.
그러나 구성을 통해서 객체의 행동을 확장하면 실행 중 동적으로 행동을 설정할 수 있다.
그러면 기존의 코드는 건드리지 않으므로 코드 수정에 따른 버그나 의도하지 않은 부작용을 막을 수 있다.
디자인 원칙
클래스는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다. (OCP: Open-Closed-Principle)
→ 기존 코드를 건드리지 않고 확장으로 새운 행동을 추가하면, 새로운 기능을 추가할 때 급변하는 주변 환경에 잘 적응하는 유연하고 튼튼한 디자인 만들 수 있음
그러나 모든 부분에서 OCP 를 준수하는 것은 불가능하다.
- OCP 를 준수하는 객체지향 디자인을 만들려면 적지 않은 시간과 노력이 필요
- 디자인의 모든 부분을 깔끔하게 정돈할 만큼 여유가 있는 상황도 흔치 않음
- OCP 를 지키다 보면 새로운 단계의 추상화가 필요한 경우가 종종 있는데, 추상화를 하다 보면 코드가 복잡해짐
따라서 가장 바뀔 가능성이 높은 부분을 중점적으로 살펴보고 OCP 를 적용하는 것이 가장 좋다.
바뀌는 부분 가운데 중요한 부분을 골라내는 안목을 높이려면 여러 디자인을 살펴보고 관련 지식을 많이 알아둬야함
데코레이터 패턴
객체에 추가 요소를 동적으로 더할 수 있다. 따라서 서브클래스를 만들 때보다 훨씬 유연하게 기능 확장 가능
- 데코레이터의 슈퍼 클래스는 자신이 장식하고 있는 객체의 슈퍼 클래스와 같다
- 여러 데코레이터를 사용할 수 있다
- 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있어서, 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 상관 없음
- 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다.
- 실행 중에 필요한 데코레이터를 마음대로 적용 가능
이렇게 보면 래퍼 클래스와 같지 않나? 라는 생각이 들 수 있는데, 일반적인 래퍼 클래스와의 차이점은
- 자신이 장식할 구성 요소와 같은 인터페이스 또는 추상 클래스를 구현한다는 것이다.
즉, 자기 자신이 장식하고 있는 객체의 자리에 들어갈 수 있어야 한다.
데코레이터 패턴에서는 상속을 사용해서 형식을 맞추어 줌. 행동을 상속을 통해 물려 받는 것이 아니다. (상속을 통해 물려받으면 행동이 컴파일 시 정적으로 결정되어 버림 → 슈퍼클래스에서 받은 것과 오버라이드한 것만 사용 가능)
행동은 어떤 구성 요소를 가지고 데코레이터를 만들 때 새롭게 추가해줌 → 새로운 행동은 슈퍼클래스로부터 상속받는 것이 아니라 객체를 구성해서 얻기 때문 (실행 중 마음대로 조합해서 사용할 수 있다는 장점)
행동은 기본 구성 요소와는 다른 데코레이터 등을 인스턴스 변수에 저장하는 식으로 연결
이러한 특징 때문에, 데코레이터 패턴을 구현할 때는 인터페이스와 추상 클래스 모두를 사용해서 구현 가능. (특정한 추상 구성 요소를 지정할 필요가 없어서 사실 인터페이스를 쓰면 됨)
데코레이터 패턴에 구체 클래스를 구성 요소로 적용하면 코드가 제대로 동작하지 않게 된다.
따라서 추상 클래스를 구성 요소로 적용해야만 제대로 된 결과를 얻을 수 있다.
관리해야 할 객체가 늘어나니까 코드 구현 시 실수할 가능성이 높아지는데, 그래서 실제로 팩토리나 빌더 같은 다른 패턴으로 데코레이터를 만들고 사용함
→ 구상 구성 요소가 캡슐화가 잘 되어 있어서 걱정하지 않아도 됨
감싸고 있는 객체에 행동을 추가하는 용도로 만들어진다.
만약 여러 단계의 데코레이터를 파고 들어가서 어떤 작업을 해야 한다면 원래 만들어진 의도에 어긋나게 됨
그렇게 하려면 추가적인 데코레이터를 씌워주면 해결할 수 있음
데코레이터가 적용된 예
: java.io 패키지
InputStream ← FileInputStream ← BufferedInputStream ← ZipInputStream
정리
- 상속 대신 데코레이터 패턴으로 행동을 확장할 수 있다 (+ OCP 만족)
- 데코레이터를 끼워 넣어도 클라이언트는 데코레이터를 사용하고 있다는 사실을 알 수 없다.
- 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영(같은 형식 가짐)
- 자잘한 클래스가 엄청 추가되는 경우가 종종 있다.
- 구성 요소를 초기화하는 데 필요한 코드가 훨씬 복잡해진다. → 팩토리, 빌더 패턴으로 해결 가능
- 특정 형식에 의존하는 코드에 데코레이터를 그냥 적용하면 엉망이 되어버림
'Read Book > 헤드퍼스트 디자인 패턴' 카테고리의 다른 글
6장. 커맨드 패턴 (0) | 2023.04.18 |
---|---|
5장. 싱글턴 패턴 (0) | 2023.04.18 |
4장. 팩토리 패턴 (0) | 2023.04.05 |
2장. 옵저버 패턴 (1) | 2023.03.21 |
1장. 디자인 패턴 소개와 전략 패턴 (0) | 2023.03.21 |