Steady Dev - TIL

데코레이터 패턴

  • 객체를 동적으로 구성(composition)하는 것

구성(Composition) vs 상속(Inheritance)?

상속은 되도록 사용하지 말기

  • Java의 창시자인 제임스 고슬링이 한 인터뷰에서 "내가 자바를 만들면서 가장 후회하는 일은 상속을 만든 점이다" 라고 말할 정도 이다.
구성(Composition)상속(Inheritance)
정의구성이란 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻하위 클래스는 상위 클래스의 모든 메소드를 재사용할 수 있고, 재정의를 하여 하위 클래스만의 메소드로 변경하는 것
포함관계has-ais-a
객체 생성 방식객체를 동적으로 생성 (클래스 안에서 인스턴스 생성) → 기존 코드를 고치는 대신 새로운 코드를 만드는데 유연하다. 즉 확장에 유연하다컴파일 단계에서 객체 생성 → 변화와 대응이 힘들다
단점더 복잡한 코드, 더 많은 보일러 플레이트가 필요상속은 캡슐화를 위반한다. 상위 클래스에 많이 의존하게 된다.
  • 구성(composition)을 사용하면 OCP원칙을 따르게 되나요? 례.

구성과 상속 코드 예시

  1. 상속은 강한 결합(tightly coupled)되어있으나 구성은 느슨한 결합(loosely coupled)이다.

    public class ClassA {
    public void foo(){}
    }
    class ClassB extends ClassA{
    public void bar(){}
    }

    ClassA는 많은 클래스들이 참조할것이다. (Exception class처럼). ClassA에 bar()라는 메소드를 추가한다고 생각해보자.

    public class ClassA {
    public void foo(){}
    public int bar(){
    return 0;
    }
    }

    이제 ClassA를 참조하자마자 ClassA.bar()의 리턴타입이 다르기 때문에 ClassB에서는 컴파일 에러가 날 것이다. 이런 경우에는 슈퍼클래스나 서브클래스 bar()를 수정해주어야 한다. 구성을 이용하면 이런 경우가 전혀 발생하지 않는다. 아래를 보자.

    public class ClassA {
    public void foo(){}
    }
    class ClassB{
    ClassA classA = new ClassA();
    public void bar(){
    classA.foo();
    classA.bar();
    }
    }

OCP(Open-Closed Principle)

  • OCP(Open Closed Principle) : 클래스는 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다.
  • 모든 부분에서 OCP를 준수하기는 불가능하다. 디자인의 모든 부분을 깔끔하게 정돈할 필요도 없고 그런 여유도 없는게 현실이다. 따라서 바뀔 가능성이 높은 부분, 중요한 부분을 중점적으로 살펴보고 OCP를 적용하는 것이 좋다.

데코레이터 패턴 흐름

  • 데코레이터 패턴을 사용하면 객체를 데코레이터 클래스의 객체로 래핑하여 런타임 시 객체의 동작을 동적으로 변경할 수 있습니다.
  • 데코레이터 객체는 wrapper 객체이다.
  1. DarkRoast 객체를 가져온다.
  2. Mocha 객체로 장식한다.
  3. Whip 객체로 장식한다.
  4. cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해달 객체에게 위임한다.

데코레이터 패턴 특징

  • 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같다.
  • 한 객체를 여러 개의 데코레이터로 감쌀 수 있다.
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼클래스를 가지고 있기 때문에 원래 객체(싸여있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 상관없다.
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있다.
  • 객체는 언제든지 감쌀 수 있으므로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있다.

데코레이터 패턴 정의

  • 객체에 추가 요소를 동적으로 더할 수 있는 패턴. 데코레이터를 사용하면 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있다.
  • 상황에 따라 어떤 객체에 책임을 덧붙이는 패턴

코드 예시 (JS)

데코레이터 적용 전

function Espresso() {
this.cost = 2500;
}
function Americano() {
Espresso.call(this);
this.cost = new Espresso().cost + 500;
this.water = 250;
}
function CafeLatte() {
Americano.call(this);
this.cost = new Americano().cost + 500;
this.milk = 100;
}
console.log(new Espresso());
console.log(new Americano());
console.log(new CafeLatte());

위와 같은 상황에서 카푸치노를 만들고 싶으면 카푸치노 function을 추가해야한다. 또, 바닐라 라떼를 추가하고싶다면 function을 추가해야한다. 즉, 확장을 하게 되면 function을 새로 만들어 추가해 줘야 하는 단점이있다.

데코레이터 적용 후

function Espresso() {
this.cost = 2500;
}
function Water(espresso) {
espresso.cost = espresso.cost + 500;
espresso.water = 350;
return espresso;
}
function Milk(espresso) {
espresso.cost = espresso.cost + 500;
espresso.milk = 100;
return espresso;
}
var espresso = new Espresso();
var americano = Water(new Espresso());
var cafeLatte = Milk(Water(new Espresso()));
console.log(cafeLatte);

객체를 동적으로 할당 가능해서 카푸치노를 만들때 function을 생성하지 않고도 만들 수 있다. 즉 확장성이 좋다. 또한 기존 클래스를 수정하지 않고 기능을 확장할 수 있다.


실제 데코레이터 패턴을 적용한 예 - http

// 파라매터로 받은 apiMethod 함수를 받는다.
http = function http(apiMethod) {
return function apiHandler(req, res, next) {
// request 개체를 통해 넘어온 요청데이터를 정규화 한다.
// body, params, query, files 를 object와 options 객체에 모아 담는다.
var object = req.body,
options = _.extend({}, req.files, req.query, req.params, {
context: {
user: req.user && req.user.id ? req.user.id : null,
},
});
if (_.isEmpty(object)) {
object = options;
options = {};
}
// 정규화된 요청데이터를 파라메터로 넘어온 apiMethod 함수에 넘겨준다.
return apiMethod(object, options)
.tap(function onSuccess(response) {
// 응답 헤더를 설정한다.
return addHeaders(apiMethod, req, res, response || {});
})
.then(function then(response) {
// 응답 바디를 설정한다.
res.json(response || {});
})
.catch(function onAPIError(error) {
// 에러 처리
next(error);
});
};
};

http() 데코레이터는 apiMethod라는 함수를 대상으로 어떤 행동을 추가한다.


단점

  • 특정 형식에 의존하는 클라이언트 코드에 데코레이터를 함부로 적용하면 안된다.
  • 구성 요소를 초기화하는데 필요한 코드가 훨씬 복잡해진다.
  • 자잘한 객체가 매우 많이 추가될 수 있기에 팩토리 패턴과 빌더 패턴과 함께 쓰여야 괜찮다.

Reference

https://jeonghwan-kim.github.io/javascript-decorator-pattern/

https://www.digitalocean.com/community/tutorials/composition-vs-inheritance