Programing/Design Patterns

Javascript Decorator Pattern 장식자 패턴 객체의 동적 기능 확장 패턴

리커니 2022. 3. 22.
반응형

Javascript Decorator Pattern 장식자 패턴 객체의 동적 기능 확장 패턴

 

동적으로 객체의 기능을 추가할 수 있는 장식자 패턴(Decorator Pattern) 에 대해 알아보겠습니다.

 

Decorator Pattern 은 객체에 어떤 기능을 동적으로 확장하는데 유용한 패턴입니다.

기능의 순서가 중요하다면 어떤 순서로 기능을 추가할지도 지정할 수 있습니다.

 

키보드에 기능에 따른 모델명을 출력하는 예제를 만들어보겠습니다.

우선 키보드 클래스를 생성합니다.

 

class Keyboard{
    constructor(){
    }
}

 

기본 모델명 클래스 변수와 그 변수를 리턴해주는 getModel 메소드를 생성합니다.

 

class Keyboard{
    constructor(){
        this.model = "PD-";
    }
    getModel(){
        return this.model;
    }
}

 

이제 동적으로 기능에 따른 모델명을 추가하기 위한 decorator Object를 생성합니다.

 

class Keyboard{
    constructor(){
        this.model = "PD-";
        this.decorator = {};
        this.decorator.hybrid = {
            getModel: function(){
                return this.uber.getModel() + "KB800";
            }
        }
        this.decorator.white = {
            getModel: function(){
                return this.uber.getModel() + "W";
            }
        }
        this.decorator.black = {
            getModel: function(){
                return this.uber.getModel() + "B";
            }
        }
        this.decorator.blank = {
            getModel: function(){
                return this.uber.getModel() + "N";
            }
        }
        this.decorator.silentness = {
            getModel: function(){
                return this.uber.getModel() + "S";
            }
        }        
    }
    getModel(){
        return this.model;
    }
}

 

class 변수로 decorator Object를 생성하고 객체를 장식(decorate)할 5개의 decorator property를 추가합니다.

decorator에는 Keyboard가 갖는 동일한 명칭의 getModel 메소드가 있고

그 내에 this.uber는 자신에게 상속된 부모를 가리키게 됩니다.

그럼 이제 실제 장식(decorate) 기능을 하는 decorate 메소드를 Keyboard class에 추가하겠습니다.

 

decorate(decorator){
    const F = function(){}, overrides = this.decorators[decorator];
    let newObj = null;
    F.prototype = this;
    newObj = new F();
    newObj.uber = F.prototype;
    for(let key in overrides){
        newObj[key] = overrides[key];
    }
    return newObj;
}

 

여기서는 임시 생성자 패턴을 사용해 F에 this를 상속합니다.

부모에게 접근할 수 있도록 uber property도 추가합니다.

그리고 임시 생성자 F를 활용해 newObj 객체를 생성하고 여기에 overrides 객체의 property들을 추가한 후 리턴합니다. 여기에서 property는 getModel이 되겠죠.

 

이제 위의 decorate 메소드를 활용해 키보드 클래스에 모델명을 추가해보겠습니다.

 

const hhkb = new Keyboard();
hhkb = hhkb.decorate("hybrid");
hhkb = hhkb.decorate("whie");
hhkb = hhkb.decorate("blank");
hhkb = hhkb.decorate("silentness");
console.log(hhkb.getModel());

 

hybrid 모델에 흰색, 무각, 저소음 기능이 있는 키보드의 모델명을 출력하는 코드입니다.

결과는 'PD-KB800WNS' 가 출력됩니다.

어떻게 이런 결과가 나오는지 확인해봅시다.

 

hhkb.getModel();는 마지막으로 decorate된 silentness의 getModel을 호출하게됩니다.

silentness의 getModel은 부모(this.uber)인 blank의 getModel을, blank는 또 부모인 white의 getModel을.......

이런식으로 체이닝되어 가장 상위 부모인 Keyboard.prototype의 getModel을 우선적으로 실행하게됩니다.

리턴된 'PD-'를 hybrid의 getModel에서 'KB800'을 붙여 리턴하고 white에서 'W'를 붙여 리턴...

결론적으로 'PD-KB800WNS' 가 리턴되게 되는 것이죠.

 

위에서 말씀드린데로 기능의 순서를 변경할 경우 모델명이 변경되게 됩니다.

hybrid -> silentness -> blank -> white = 'PD-KB800SNW'

 

상속을 활용하지 않고 위의 기능을 구현해보겠습니다.

 

class Keyborad{
    constructor(){
        this.model = "PD-";
        this.decoratorsArray = [];
        this.decorators = {};
        this.decorators.hybrid = {
            getModel: function(model){
                return model + "KB800";
            }
        }
        .
        .
        .
        .
        .

 

추가할 기능을 관리할 배열을 클래스변수로 추가합니다. (decoratorsArray)

그리고 각 decorator getModel에는 model 을 파라미터로 받아 기능명을 더해서 리턴 해 주도록 수정했습니다.

 

decorate(decorator){
    this.decoratorsArray.push(decorator);
}

decorate 메소드는 decoratorArray에 decorator를 추가하는 기능으로 수정했습니다.

getModel 메소드는 기본 모델명에 decoratorArray에 있는 decorator의 getModel을 호출하도록 변경했습니다.

 

getModel(){
    let model = this.model;
    for(let deco of this.decoratorsArray){
        model = this.decorators[deco].getModel(model);
    }
}

 

호출하는 방식도 변경됩니다.

 

const hhkb = new Keyboard();
hhkb.decorate("hybrid");
hhkb.decorate("whie");
hhkb.decorate("blank");
hhkb.decorate("silentness");
console.log(hhkb.getModel());

 

각 decorator 가 가지고 있던 기능을 getModel에서 처리하게됩니다.

 

 

반응형

댓글

💲 추천 글