데코레이터 패턴
Last updated
Last updated
컴포지션을 통해 객체의 행동을 확장한다면, 런타임에 동적으로 행동을 설정할 수도 있고 객체에 여러 행동을 추가해도 기존 코드에 영향이 최소한으로 가게 된다.
클래스는 확장에는 열려있어야 하지만 변경에는 닫혀있어야 한다는 객체 지향의 OCP원칙에 따라 설계해야 한다. 즉 요구 사항에 의해 새로운 기능이 추가되거나 기존 기능이 변경되더라도, 기존 코드에 변경 없이 새로운 코드를 추가할 수 있어야 한다는 의미이다.
객체에 추가 요소를 동적으로 더할 수 있도록 데코레이터를 사용하는 디자인 패턴이다.
데코레이터를 사용하면 서브 클래스를 만드는 것 보다 훨씬 유연하게 기능을 확장할 수 있다.
예를 들어 여러 기능들을 조합해야 하는 경우 서브 클래스를 만들다보면 클래스 폭발 현상이 발생할 수 있다. 데코레이터 패턴은 이러한 기능 조합을 원활하게 만들어준다.
상속과 컴포지션을 통해 Decorator 추상 클래스는 Component 인터페이스를 구현하는 동시에 필드로 가지며, 필수 메서드와 부가 기능을 구현한다.
상속하는 이유는 Component와 타입을 동일하게 맞추기 위함이고, 필드로 갖는 것은 행동을 상속받기 위함이다.
장점
객체에 부가 요소를 동적으로 추가할 수 있어 여러 행동을 합성할 수 있다.
여러 행동을 작은 클래스로 나누어 표현하므로 단일 책임 원칙을 지킨다.
단점
특정 타입에 의존하는 클라이언트 코드에 데코레이터를 적용하면 제대로 활용할 수 없으므로 아무데나 적용하면 안된다.
많은 클래스가 추가되어야 하므로 구조가 복잡해진다.
클라이언트가 구성 요소를 초기화할 때 코드가 복잡해질 수 있.
인터페이스 혹은 추상 클래스를 정의하여 데코레이터의 기반이 되는 구성 요소를 만든다.
이를 구현하거나 상속받은 클래스들은 이제 데코레이터 클래스의 대상이 될 수 있다.
원하는 부가 기능들을 분리하여 각각의 데코레이터 클래스로 만든다. 이 때 앞서 정의한 인터페이스 혹은 추상 클래스를 구현/상속하는 동시에 필드로 가지도록 한다.
I/O에는 다양한 방식이 존재하며, 이를 지원하기 위해 자바에서는 데코레이터 패턴을 사용한다.
InputStream은 추상 데코레이터 클래스 역할을 하며, 데코레이터에 감싸이는 컴포넌트 역할을 하는 클래스들이 존재한다. 그리고 데코레이터 클래스들이 존재한다. 이 때 데코레이터에서는 입력받은 데이터를 원하는 형태로 가공하도록 한다.
Lettuce는 Netty를 기반으로 하는 라이브러리이다. 따라서 Netty에서 제공해주는 Channel에 write하는 역할을 담당하는 클래스가 필요하다.
가장 기본적으로 Channel에 데이터를 write하는 클래스는 DefaultEndpoint이다.
Lettuce는 Redis 클러스터에 요청을 보내는 기능도 지원한다. 이 때 어떤 클러스터의 노드에 요청을 보낼 지 정하기 위한 ClusterDistributionChannelWriter 클래스를 제공하는데, 이는 데코레이터 클래스와 유사하다.
기본적인 요청을 write하기 위한 RedisChannelWriter 필드를 가지고 있으며, RedisChannelWriter라는 타입에 할당될 수 있도록 인터페이스를 구현하고 있다.
Redis의 해시 슬롯에 따라 알맞는 노드에 연산을 보내는 부분은 데코레이터 패턴의 쓰임과 달라 설명하지 않았다.
RedisChannelHandler는 클러스터이든 Standalone이든 상관 없이 사용되는 클래스이다. 만약 클러스터 환경인 경우에는 channelWriter 필드에 ClusterDistributionChannelWriter
를 저장해둔다. Standalone이라면 channelWriter 필드에 DefaultEndpoint
를 저장해둔다.