본문 바로가기
Programming/OOP

[Design Pattern] Decorator Pattern, 데코레이터 패턴

by TinKerBellBass 2022. 4. 21.
728x90
반응형

OCP(Open-Closed Principle), 개방-폐쇄 원칙

확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

기존 코드의 변경 없이 기능을 확장해야 한다는 의미인데,

이 원칙을 지키기 위해서 기존 클래스를 상속받아 사용하거나, 의존 관계를 통한 구성을 사용해야 한다.

OCP 의 단골인 DB 커넥션 부분을 살펴보자.

어느 날 갑자기 MySQL 에 심각한 버그가 발견되어 다른 DB 로 교체해야 한다고 하면,

MySqlCon 을 사용해서 커넥션을 얻어오는 Repository 클래스의 생성자를 수정해야 한다.

Repository 클래스가 MysqlCon 구상 클래스에 강하게 결합되어 있기 때문이다.

이런 코드 변경 없이 확장을 하기 위해서는

슈퍼 타입을 상속 또는 구현한 서브 타입의 인스턴스는 슈퍼 타입에 할당할 수 있고,

슈퍼 타입으로서 할당된 서브 타입 인스턴스의 메서드를 호출하면 서브 타입의 메서드가 호출된다는 다형성을 이용하면 된다.

슈퍼 타입은 상황에 따라 추상 클래스 또는 인터페이스를 사용하면 된다.

MysqlCon 에서 PostgresCon 으로 구상 클래스를 교체할 때는, Repository 의 생성자에 PostgresCon 인스턴스를 주입해 주면 된다.

의존성 관리를 담당하는 곳의 코드 수정만 하면 되는 것이다. 

그리고 Maria DB 를 사용하고 싶으면 DBConnection 에 맞춰서 MariaCon 클래스를 구현하면 된다.

 

Decorator Pattern

데코레이터 패턴의 핵심은 장식할(감쌀, 랩핑할) 대상과 같은 타입의 장식(Decorator)을 만들어서

장식 대상을 장식하는 것인데, 이때 장식 대상과 같은 타입의 장식을 만들기 위해 다형성이 사용된다.

장식은 장식 대상을 구성을 통해 가지고 있으며,

장식 대상이 처리해야 할 사항을 장식 대상에게 위임하고, 그 결과를 받아와 장식해서 반환한다.

기존 코드의 수정 없이 장식을 확장해 나갈 수 있는 데코레이터 패턴은 OCP 잘 이용한 패턴이다.

 

이때 장식할 대상과 장식의 슈퍼 타입이 추상 구성 요소, 장식할 대상이 구성 요소,

데코레이터를 하나로 묶는 슈퍼 타입의 하위 타입이 추상 데코레이터, 장식이 데코레이터이다. 

슈퍼 타입인 추상 구성 요소를 추상 클래스가 아닌 인터페이스로 만드는 경우,

추상 데코레이터는 구현하지 않고 데코레이터가 직접 슈퍼 타입을 구현해도 괜찮다.

추상 데코레이터를 사용하는 이유가 슈퍼 타입을 구성으로 가지고 있는 코드를 중복하지 않고

추상 클래스에 가지고 있기 위해서이기 때문이다.

대신 인터페이스를 사용하는 경우 구성을 가지는 코드가 중복되는데

그렇다고 인터페이스가 가질 수 있는 필드인 static final 로 구성을 가지는 것은 썩 기분이 좋지 않다.

 

데코레이터 패턴의 단점은 사용되는 타입이 장식할 대상과 장식의 슈퍼 클래스이기 때문에

감싸 버리고 나면 이전의 객체가 무엇인지 알 수 없다는 것이다.

 

데코레이터 패턴의 단골 손님은 자바의 InputStream 이다.

구상 구성 요소 하나, 데코레이터 하나로만 그려보면 아래와 같이 되어 있다.

추상 데코레이터인 FilterInputStream 은 구성 요소인 InputStream 을 필드로 가지고 있다.

데코레이터는 in.read() 해서 구성 요소에게 처리를 위임하고 그 결과를 가지고 무엇인가 처리할 것이다.

(실제로는 이것보다 메서드 관계가 복잡하지만 단순하게 전체적으로 보면)

FileInputStream 에 버퍼를 사용하고 싶으면 BufferdInputStream 으로 감싸면 된다는 것이 핵심이다.

Decorator Pattern Class Diagram

책에서는 슈퍼 타입을 추상 클래스로 구현하고 있어서 인터페이스로 구현하였고,

인스턴스 생성은 각 언어마다 약간씩 변형한 빌더 패턴으로 구현해 보았다.

빌더 패턴에서 한 가지 마음에 안 드는 것은 새로운 데코레이터를 추가할 때마다 빌더에 추가해 줘야 한다는 것인데

뭔가 딱 떠오르는 방법이 없어서 일단 패스.

 

추상 구성 요소 Discount 를 구상 구성 요소인 StudentDiscount 와 VipDiscount 가 구현하고 있고,

인터페이스를 사용하였기 때문에 추상 데코레이터는 만들지 않았고,

데코레이터인 TenOrderDiscount 와 WelcomeDiscount 가 직접 추상 구성 요소인 Discount 를 구현하고 있다.

그리고 데코레이터는 추상 구성 요소인 Discount 를 필드로 가지고 있다.

 

추상 구성 요소인 Discount 는 할인 후 금액을 구하는 amountAfterDiscount 메서드와

이력을 구하는 history 메서드를 강제하고 있기 때문에,

Discount 를 구현한 구상 구성 요소와 데코레이터도 이 메서드를 구현해야 한다.

그리고 이 두 메서드를 통해 감싸나 갈 수 있다.

전체 소스는 https://github.com/Jongwon-Hyun/design_pattern

 

728x90
반응형

댓글