Web/JS

[JS] 디자인 패턴(Design Pattern)_데코레이터 패턴(Decorator Pattern)

메바동 2023. 2. 26. 23:56
728x90

"JavaScript Design Pattern" 포스팅은 ChatGPT에게 "Essential design patterns for JavaScript developers to learn"로 질문한 뒤 나온 내용에 대해 정리하는 포스팅입니다.

 


 

1. 데코레이터 패턴(Decorator Pattern)

데코레이터 패턴은 개발자가 객체를 데코레이터 클래스의 객체로 감싸서 런타임에 새로운 기능을 동적으로 추가할 수 있도록 하는 구조 패턴이다.

데코레이터 패턴을 사용하면 개발자는 객체의 원래 구현을 변경하지 않고도 새로운 동작을 추가하여 객체의 기능을 확장할 수 있다.

 

데코레이터 패턴은 Component interface, Concrete component, Decorator abstract class, Concrete Decorator의 네 가지 조요 컴포넌트로 구성된다.

 

  • Component: 이 인터페이스는 Concrete Component 및 Concrete Decorator에 의해 구현될 메서드를 정의한다. 이 인터페이스는 추상 클래스 또는 인터페일 수 있다.
  • Concrete Component: Component Interface의 기본 구현으로, 데코레이트될 객체이다.
  • Decorator: Component Interface를 구현하는 추상 클래스이며 Component Interface의 인스턴스에 대한 참조를 포함한다. Decorator 클래스는 실행 시 Concrete Component에 추가할 수 있는 메서드를 정의한다.
  • Concrete Decorator: Decorator 클래스를 확장하고 Concrete Component에 새 기능을 추가하는 클래스다. 또한, Decorator 클래스의 메서드를 호출하여 새로운 기능을 추가할 수 있다.

 

JavaScript에서 데코레이터 패턴은 다양한 방식으로 구현할 수 있는데, 가장 일반적인 접근 방식은 객체를 인수로 받아 원래 객체를 확장하거나 수정하는 새 객체를 반환하는 고차 함수를 사용하는 것이다.

 

다음은 JavaScript에서 데코레이터 패턴을 구현한 예이다:

 

// 간단한 객체 정의
const myObject = {
  name: 'John',
  age: 30,
  sayHello() {
    console.log(
      `Hello, my name is ${this.name} and I'm ${this.age} years old.`
    );
  },
};

// 데코레이터 함수 정의
function addJobTitle(obj, title) {
  return {
    ...obj,
    jobTitle: title,
    sayHello() {
      console.log(
        `Hello, my name is ${this.name}, I'm ${this.age} years old, and I work as a ${this.jobTitle}.`
      );
    },
  };
}

// 데코레이터를 사용하여 객체에 job title 추가
const decoratedObject = addJobTitle(myObject, 'Developer');

// 장식된 객체에서 sayHello() 메서드를 호출합니다.
decoratedObject.sayHello();

 

이 예제에는 name, age, 그리고 sayHello() 메서드가 있는 간단한 객체가 있다.

그리고 obj와 title을 가져와 객체에 jobTitle 속성과, sayHello() 메서드를 인사말에 job title을 추가하는 수정된 sayHello() 메서드로 수정하여 원래 객체를 확장한 새 객체를 반환하는 addJobTitle Decorator 함수를 정의한다.

그런 다음, 데코레이터를 사용하여 myObject에 job title을 추가하고 decoratedObject 변수에 저장한다.

 

데코레이터 패턴은 기존 코드를 변경하지 않고 기존 객체에 새로운 기능이나 동작을 추가해야 할 때 유용하다.

객체와 데코레이터의 관심사를 분리할 수 있으므로 코드를 더 쉽게 유지보수하고 확장할 수 있다.

복잡하고 유연한 시스템을 만들기 위해 팩토리 패턴이나 어댑터 패턴과 같은 다른 디자인 패턴과 함께 사용하는 경우가 많다.

 

 

 

2. Decorator Pattern 연습 코드

ChatGPT에게 "Give me a challenge to practice the Decorator Pattern in JavaScript."라고 질문한 뒤 나온 문제와 그에 대한 답이다.

 

 

스켈레톤 코드는 다음과 같다.

 

class Coffee {
  constructor() {
    this.description = 'Unknown Coffee';
  }

  cost() {
    return 0;
  }

  getDescription() {
    return this.description;
  }
}

class Decorator extends Coffee {
  constructor(coffee) {
    super();
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  getDescription() {
    return this.coffee.getDescription();
  }
}

class MilkDecorator extends Decorator {
  // TODO: Implement the MilkDecorator class
}

class SugarDecorator extends Decorator {
  // TODO: Implement the SugarDecorator class
}

class VanillaDecorator extends Decorator {
  // TODO: Implement the VanillaDecorator class
}

// Usage
const coffee = new Coffee();
console.log(coffee.getDescription() + ' costs $' + coffee.cost());

const coffeeWithMilkAndSugar = new SugarDecorator(new MilkDecorator(coffee));
console.log(
  coffeeWithMilkAndSugar.getDescription() +
    ' costs $' +
    coffeeWithMilkAndSugar.cost()
);

const coffeeWithVanilla = new VanillaDecorator(coffee);
console.log(
  coffeeWithVanilla.getDescription() + ' costs $' + coffeeWithVanilla.cost()
);

 

이를 구현한 코드는 다음과 같다.

 

class Coffee {
  constructor() {
    this.description = 'Unknown Coffee';
  }

  cost() {
    return 0;
  }

  getDescription() {
    return this.description;
  }
}

class Decorator extends Coffee {
  constructor(coffee) {
    super();
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  getDescription() {
    return this.coffee.getDescription();
  }
}

class MilkDecorator extends Decorator {
  // TODO: Implement the MilkDecorator class
  constructor(coffee) {
    super(coffee);
    this.description = 'Milk';
  }

  cost() {
    return this.coffee.cost() + 0.5;
  }

  getDescription() {
    return this.coffee.getDescription() + ` with ${this.description}`;
  }
}

class SugarDecorator extends Decorator {
  // TODO: Implement the SugarDecorator class
  constructor(coffee) {
    super(coffee);
    this.description = 'Sugar';
  }

  cost() {
    return this.coffee.cost() + 0.2;
  }

  getDescription() {
    return this.coffee.getDescription() + ` with ${this.description}`;
  }
}

class VanillaDecorator extends Decorator {
  // TODO: Implement the VanillaDecorator class
  constructor(coffee) {
    super(coffee);
    this.description = 'Vanilla';
  }

  cost() {
    return this.coffee.cost() + 0.7;
  }
  getDescription() {
    return this.coffee.getDescription() + ` with ${this.description}`;
  }
}

// Usage
const coffee = new Coffee();
console.log(coffee.getDescription() + ' costs $' + coffee.cost());

const coffeeWithMilkAndSugar = new SugarDecorator(new MilkDecorator(coffee));
console.log(
  coffeeWithMilkAndSugar.getDescription() +
    ' costs $' +
    coffeeWithMilkAndSugar.cost()
);

const coffeeWithVanilla = new VanillaDecorator(coffee);
console.log(
  coffeeWithVanilla.getDescription() + ' costs $' + coffeeWithVanilla.cost()
);

 

728x90