Move Embellishment to Decorator (206)
29 Jun 2021 | DesignPattern Refactoring개요
어떤 클래스에 핵심 기능을 위한 코드와 꾸밈 코드가 뒤섞여 있으면,
꾸밈 코드를 데코레이터로 옮긴다
동기
시스템에 새 기능을 추가해야 할 때면 보통 기존 클래스에 코드를 덧붙인다.
이렇게 새로 추가된 코드는 기존 코드에 대한 꾸밈 코드로서 동작하는 경우가 많다.
문제는 이런 꾸밈 코드로 인해 새로운 필드나 메서드, 로직이 복잡해지고 호스트 클래스가 복잡해지는 것이다. 게다가 새로 추가된 부분은 특정 조건에서만 쓰이는 것이다.
위의 문제점의 해결책은 Decorator 패턴이다. 꾸밈 코드를 각자의 클래스로 옮기고 그 객체가 호스트를 감싸도록 만드는 것이다. 그렇게 한다면 특별한 처리가 필요한 때에 클라이언트가 호스트 객체를 꾸밈 객체로 감싸 사용함으로써 특수 기능을 수행할 수 있다.
장점
- 꾸밈 코드를 제거해 호스트 클래스를 단순하게 만든다.
- 어떤 클래스의 핵심 기능과 부가 기능을 손쉽게 구별할 수 있다.
- 서로 관련된 클래스에서 중복된 꾸밈 코드를 줄일 수 있다.
단점
- 클라이언트의 입장에서는 대상 객체의 타입이 바뀐다.
- 코드를 이해하고 디버깅하기 더 어려워질 수도 있다.
- 데코레이터 객체의 조합 방식이 서로에게 영향을 끼친다면, 설계를 더 복잡하게 만들어야 한다.
클래스가 여러 파일로 나뉘어 더 복잡해질 수도 있다.
절차
먼저 대상이 될 클래스를 찾는 것으로 시작한다. 즉, 핵심 기능에 다른 기능을 추가하기 위한 꾸밈 코드를 가지고 있는 클래스를 찾는다. 어떤 클래스가 꾸밈 코드를 갖는다 해서 무조건 Decorator로 리팩터링을 해야 할 건 아니다. 결정하기 전 public
메서드가 너무 많지 않은지 살펴보는 것이 좋다. 데코레이터는 그 대상이 되는 호스트 객체에 대한 투명한 외투로 동작해 클라이언트 코드에서는 데코레이터 객체와 호스트 객체를 동일한 인터페이스로 조작할 수 있어야 하기 떄문이다. public
메서드가 지나치게 많다면 Replace Conditional Logic with Strategy같은 대안을 고려해야 한다.
-
enclosure type을 찾아내거나 새로 만든다. 인클로저 타입이란 호스트 클래스의 클라이언트가 사용하는 모든 public 메서드를 제공하는 클래스 혹은 인터페이스로, 데코레이터 클래스와 호스트 클래스 둘 모두에 대한 수퍼타입이 될 존재다. 즉 Decorator의 Component에 해당한다.
어떤 경우 인클로저 타입을 새로 만들 필요 없이 기존에 있던 클래스나 인터페이스를 그대로 이용할 수도 있다. 그러나 그 클래스가 데이터를 유지하고 있다면 인클로저 타입으로 적절하지 않다. 데코레이터는 그런 데이터가 필요 없음에도 상속을 받게 되기 떄문이다. -
호스트 클래스에서 꾸밈 코드에 해당하는 조건 로직을 찾아 Replace Conditional with Polymorphism을 통해 제거한다. 해당 리팩토링에서는 타입 코드를 자체 캡슐화할 것을 제시하고 있는데, 그 지침에 따라 타입 코드에 대한 get 메서드를 만들때엔 리턴 타입이 앞 단계에서 정의한 인클로저 타입이도록 하는 것이 중요하다.
- 단계 2에서 호스트 클래스의 서브 클래스를 하나 이상 만들었다. 이 서브 클래스들을 위임 클래스(어떤 동작의 수행을 다른 객체에 위임하는 클래스)를 만든다. 이 떄 다음 사항들을 지켜야 한다.
- 위임 클래스는 모두 인클로저 타입을 구현하도록 한다.
- 위임 클래스의 대리 객체 필드도 인클로저 필드로 한다.
- 꾸밈 코드를 위임 클래스가 위임 메서드를 호출하기 전에 실행할 것인지 아니면 그 후에 실행할 것인지를 결정한다.
- 이제 각 위임 클래스의 대리 객체 필드에 호스트 클래스의 새 인스턴스를 만들어 대입한다. 이 대입문은 위임 클래스의 생성자에 위치해야 한다. 그 다음, 호스트 클래스의 인스턴스를 생성하는 코드에 Extract Parameter 리팩터링을 적용해 파라미터로 뽑아낸다. 마지막으로 생성자의 파라미터 중 불필요한 것이 있다면 Remove Parameter 리팩터링을 이용해 제거한다.