javascript design patterns - Lee-hyuna/33-js-concepts-kr GitHub Wiki

μžλ°”μŠ€ν¬λ¦½νŠΈ λ””μžμΈ νŒ¨ν„΄

원문: JavaScript Design Patterns

이 κΈ°μ‚¬μ—μ„œλŠ” μœ μ§€λ³΄μˆ˜ν•˜κΈ° 더 쒋은 JavaScript μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 데 μ‚¬μš©ν•  μˆ˜μžˆλŠ” λ””μžμΈ νŒ¨ν„΄μ— λŒ€ν•΄ μ„€λͺ…ν•©λ‹ˆλ‹€. JavaScript의 ν΄λž˜μŠ€μ™€ 객체, ν”„λ‘œν† νƒ€μž… 상속, ν΄λ‘œμ € λ“±κ³Ό 같은 κ°œλ…μ„ 기본적으둜 μ΄ν•΄ν•˜κ³  μžˆλ‹€κ³  κ°€μ •ν•œλ‹€.

이 κΈ°μ‚¬λŠ” 주제의 νŠΉμ„±μœΌλ‘œ 인해 μ „μ²΄μ μœΌλ‘œ μ˜€λž«λ™μ•ˆ μ½ν˜€μ‘ŒμœΌλ―€λ‘œ μ„Ήμ…˜μ„ λ…λ¦½μ μœΌλ‘œ μœ μ§€ν•˜λ €κ³  λ…Έλ ₯ν–ˆλ‹€. λ”°λΌμ„œ νŠΉμ • λΆ€λΆ„ (λ˜λŠ” 이 경우 νŠΉμ • νŒ¨ν„΄)을 골라 μ„ νƒν•˜κ³  관심이 μ—†κ±°λ‚˜ 잘 λͺ¨λ₯΄λŠ” 뢀뢄을 λ¬΄μ‹œν•˜λ©΄ λœλ‹€.

μ°Έκ³  : 여기에 μ„€λͺ… 된 λͺ¨λ“  λ””μžμΈ νŒ¨ν„΄μ„ κ΅¬ν˜„ν•˜κΈ°μœ„ν•œ μ†ŒμŠ€ μ½”λ“œλŠ” GitHub 에 μžˆλ‹€.

μ†Œκ°œ

μš°λ¦¬λŠ” 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€. μ΄λŸ¬ν•œ λ¬Έμ œλŠ” 일반적으둜 λ§Žμ€ μœ μ‚¬μ μ„ 가지고 있으며, 이λ₯Ό ν•΄κ²°ν•˜λ €κ³  ν•  λ•Œ λͺ‡ 가지 곡톡적인 νŒ¨ν„΄μ΄ μžˆλ‹€. λ””μžμΈ νŒ¨ν„΄μ΄ λ“±μž₯ν•˜λŠ” μ΄μœ μ΄λ‹€.

λ””μžμΈ νŒ¨ν„΄μ€ μ†Œν”„νŠΈμ›¨μ–΄ μ„€κ³„μ—μ„œ 일반적으둜 λ°œμƒν•˜λŠ” λ¬Έμ œμ— λŒ€ν•œ 일반적인 μž¬μ‚¬μš© μ†”λ£¨μ…˜ μ†Œν”„νŠΈμ›¨μ–΄ κ³΅ν•™μ—μ„œ μ‚¬μš©λ˜λŠ” μš©μ–΄μ΄λ‹€.

λ””μžμΈ νŒ¨ν„΄μ˜ κΈ°λ³Έ κ°œλ…μ€ μ²˜μŒλΆ€ν„° μ†Œν”„νŠΈμ›¨μ–΄ μ—”μ§€λ‹ˆμ–΄λ§ μ‚°μ—…μ—μ„œ μ‚¬μš©λ˜μ–΄ μ™”μ§€λ§Œ μ‹€μ œλ‘œ κ³΅μ‹ν™”λ˜μ§€λŠ” μ•Šμ•˜λ‹€. Erich Gamma, Richard Helm, Ralph Johnson, 그리고 John Vlissidesμ˜ν•΄ μž‘μ„± 된 λ””μžμΈ νŒ¨ν„΄ : μž¬μ‚¬μš© κ°€λŠ₯ν•œ 객체 지ν–₯ μ†Œν”„νŠΈμ›¨μ–΄μ˜ μš”μ†Œ μ†Œν”„νŠΈμ›¨μ–΄ μ—”μ§€λ‹ˆμ–΄λ§μ—μ„œ 곡식화 된 λ””μžμΈ νŒ¨ν„΄ κ°œλ…μ„ μΆ”μ§„ν•˜λŠ” 데 μ€‘μš”ν•œ 역할을 ν–ˆλ‹€. 이제 λ””μžμΈ νŒ¨ν„΄μ€ μ†Œν”„νŠΈμ›¨μ–΄ 개발의 ν•„μˆ˜ μš”μ†Œμ΄λ©° μ˜€λž«λ™μ•ˆ μ‚¬μš©λ˜μ–΄ μ™”λ‹€.

원본 μ±…μ—λŠ” 23 개의 λ””μžμΈ νŒ¨ν„΄μ΄ λ„μž…λ˜μ—ˆλ‹€.

λ””μžμΈ νŒ¨ν„΄μ€ μ—¬λŸ¬ 가지 이유둜 μœ μš©ν•˜λ‹€. λ””μžμΈ νŒ¨ν„΄μ€ μ—…κ³„μ—μ„œ μž…μ¦ 된 ν•΄κ²° 방법을 μ œμ‹œν•œλ‹€. 널리 μ•Œλ €μ§„ λ°©μ‹μœΌλ‘œ 문제λ₯Ό ν•΄κ²°ν•˜κ³  이λ₯Ό μ •μ˜ν•˜λŠ” 데 도움이 된 업계 졜고의 개발자의 κ²½ν—˜κ³Ό 톡찰λ ₯을 λ°˜μ˜ν•˜λŠ”, κ²¬κ³ ν•œ μ ‘κ·Ό 방식이닀. νŒ¨ν„΄μ€ λ˜ν•œ 개발 ν”„λ‘œμ„ΈμŠ€μ˜ 속도λ₯Ό 크게 λ†’μ΄λŠ” λ™μ‹œμ— μ½”λ“œλ₯Όλ³΄λ‹€ μž¬μ‚¬μš© κ°€λŠ₯ν•˜κ³  읽기 μ‰½κ²Œ λ§Œλ“ λ‹€.

κ·ΈλŸ¬λ‚˜ λ””μžμΈ νŒ¨ν„΄μ€ κ²°μ½” μ™„μ„± 된 μ†”λ£¨μ…˜μ΄ μ•„λ‹ˆλ‹€. 이것듀은 단지 문제λ₯Ό ν•΄κ²°ν•˜λŠ” λ°©λ²•μ΄λ‚˜ 기법을 μš°λ¦¬μ—κ²Œ μ œκ³΅ν•  뿐이닀.

μ°Έκ³  : 이 κΈ°μ‚¬μ—μ„œλŠ” 주둜 객체 지ν–₯ 관점과 ν˜„λŒ€ JavaScriptμ—μ„œμ˜ μœ μš©μ„±μ— κ΄€ν•œ λ””μžμΈ νŒ¨ν„΄μ— λŒ€ν•΄ μ„€λͺ…ν•œλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— GoF의 λ§Žμ€ ν΄λž˜μ‹ νŒ¨ν„΄μ΄ μƒλž΅ 될 수 있으며 Addy Osmani의 Learn JavaScript Design Patterns 와 같은 μ†ŒμŠ€μ˜ 일뢀 μ΅œμ‹  νŒ¨ν„΄ 이 ν¬ν•¨λ©λ‹ˆλ‹€. μ˜ˆμ œλŠ” μ΄ν•΄ν•˜κΈ° 쉽도둝 λ‹¨μˆœν•˜κ²Œ μœ μ§€λ˜λ―€λ‘œ 각 λ””μžμΈ νŒ¨ν„΄μ˜ μ΅œμ ν™” 된 κ΅¬ν˜„μ΄ μ•„λ‹ˆλ‹€.

λ””μžμΈ νŒ¨ν„΄μ˜ λ²”μ£Ό

λ””μžμΈ νŒ¨ν„΄μ€ 일반적으둜 μ„Έ 가지 μ£Όμš” 그룹으둜 λΆ„λ₯˜λ©λ‹ˆλ‹€.

μ°½μ‘° λ””μžμΈ νŒ¨ν„΄

μ΄λ¦„μ—μ„œ μ•Œ 수 μžˆλ“―μ΄ μ΄λŸ¬ν•œ νŒ¨ν„΄μ€ 객체 생성 λ©”μ»€λ‹ˆμ¦˜μ„ μ²˜λ¦¬ν•˜κΈ°μœ„ν•œ 것이닀. μ°½μ‘° λ””μžμΈ νŒ¨ν„΄μ€ 기본적으둜 객체의 생성 과정을 μ œμ–΄ν•¨μœΌλ‘œμ¨ 문제λ₯Ό ν•΄κ²°ν•œλ‹€.

μƒμ„±μž νŒ¨ν„΄, νŒ©ν† λ¦¬ νŒ¨ν„΄, ν”„λ‘œν† νƒ€μž… νŒ¨ν„΄ 및 싱글톀 νŒ¨ν„΄ κ³Ό 같은 νŒ¨ν„΄μ— λŒ€ν•΄ μžμ„Ένžˆ μ„€λͺ…ν•œλ‹€.

ꡬ쑰 섀계 νŒ¨ν„΄

μ΄λŸ¬ν•œ νŒ¨ν„΄μ€ 클래슀 및 객체 ꡬ성과 관련이 μžˆλ‹€. 전체 μ‹œμŠ€ν…œμ— 영ν–₯을주지 μ•Šκ³  ν•˜λ‚˜ μ΄μƒμ˜ λΆ€ν’ˆμ„ κ΅¬μ‘°ν™”ν•˜κ±°λ‚˜ μž¬κ΅¬μ„± ν•  수 μžˆλ‹€. 즉, κΈ°μ‘΄ κΈ°λŠ₯을 λ³€κ²½ν•˜μ§€ μ•Šκ³ λ„ μƒˆλ‘œμš΄ κΈ°λŠ₯을 얻을 수 μžˆλ‹€.

μ–΄λŒ‘ν„° νŒ¨ν„΄, 볡합 νŒ¨ν„΄, λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄, νΌμ‚¬λ“œ νŒ¨ν„΄, ν”ŒλΌμ΄μ›¨μ΄νŠΈ νŒ¨ν„΄ 및 ν”„λ‘μ‹œ νŒ¨ν„΄ κ³Ό 같은 νŒ¨ν„΄μ— λŒ€ν•΄ μžμ„Ένžˆ μ„€λͺ…ν•œλ‹€.

행동 λ””μžμΈ νŒ¨ν„΄

μ΄λŸ¬ν•œ νŒ¨ν„΄μ€ 이 κΈ°μ’… 객체 κ°„μ˜ 톡신을 κ°œμ„ ν•˜λŠ” 데 κ΄€λ ¨λœλ‹€.

μ±…μž„ μ‚¬μŠ¬ νŒ¨ν„΄, μ»€λ§¨λ“œ νŒ¨ν„΄, μ΄ν„°λ ˆμ΄ν„° νŒ¨ν„΄, μ€‘μž¬μž νŒ¨ν„΄, κ΄€μ°°μž β€‹β€‹νŒ¨ν„΄, μƒνƒœ νŒ¨ν„΄, μ „λž΅ νŒ¨ν„΄ 및 ν…œν”Œλ¦Ώ νŒ¨ν„΄ κ³Ό 같은 νŒ¨ν„΄μ— λŒ€ν•΄ μžμ„Ένžˆ μ„€λͺ…ν•œλ‹€.

μƒμ„±μž νŒ¨ν„΄ (Factory Pattern)

클래슀 기반의 λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. μƒμ„±μžλŠ” ν•΄λ‹Ή ν•¨μˆ˜λ‘œ μ •μ˜λœ λ©”μ„œλ“œ 및 μ†μ„±μœΌλ‘œ μƒˆ 객체λ₯Ό μΈμŠ€ν„΄μŠ€ν™”ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆλŠ” 특수 ν•¨μˆ˜μ΄λ‹€.

고전적인 λ””μžμΈ νŒ¨ν„΄μ€ μ•„λ‹ˆλ‹€. μ‹€μ œλ‘œ, λŒ€λΆ€λΆ„μ˜ 객체 지ν–₯ μ–Έμ–΄μ˜ νŒ¨ν„΄μ΄λΌκΈ° 보단 기본적인 μ–Έμ–΄ ꡬ성에 가깝닀. κ·ΈλŸ¬λ‚˜ JavaScriptμ—μ„œλŠ” μƒμ„±μž ν•¨μˆ˜λ‚˜ "class" μ •μ˜ 없이도 객체λ₯Ό μ¦‰μ„μ—μ„œ λ§Œλ“€ 수 μžˆλ‹€. λ”°λΌμ„œ 이 λ‹¨μˆœν•œ ν•˜λ‚˜μ˜ νŒ¨ν„΄μ΄ λ‹€λ₯Έ νŒ¨ν„΄λ“€μ˜ 기반이 λœλ‹€κ³  μƒκ°ν•œλ‹€.

μƒμ„±μž νŒ¨ν„΄μ€ 주어진 μ’…λ₯˜μ˜ μƒˆ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ JavaScriptμ—μ„œ κ°€μž₯ 일반적으둜 μ‚¬μš©λ˜λŠ” νŒ¨ν„΄ 쀑 ν•˜λ‚˜μ΄λ‹€.

이 μ˜ˆμ œμ—μ„œ, name와 specialAbility와 같은 속성과 getDetails와 같은 λ©”μ„œλ“œλ₯Ό 가진 Hero 클래슀λ₯Ό μ •μ˜ν•œλ‹€. 그런 λ‹€μŒ 각 μ†μ„±μ˜ 값을 인수둜 μ „λ‹¬ν•˜λŠ” new ν‚€μ›Œλ“œλ‘œ μƒμ„±μž λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ 객체 IronMan을 μΈμŠ€ν„΄μŠ€ν™”ν•œλ‹€.

// traditional Function-based syntax
function Hero(name, specialAbility) {
  // setting property values
  this.name = name;
  this.specialAbility = specialAbility;

  // declaring a method on the object
  this.getDetails = function() {
    return this.name + ' can ' + this.specialAbility;
  };
}

// ES6 Class syntax
class Hero {
  constructor(name, specialAbility) {
    // setting property values
    this._name = name;
    this._specialAbility = specialAbility;

    // declaring a method on the object
    this.getDetails = function() {
      return `${this._name} can ${this._specialAbility}`;
    };
  }
}

// creating new instances of Hero
const IronMan = new Hero('Iron Man', 'fly');

console.log(IronMan.getDetails()); // Iron Man can fly

νŒ©ν† λ¦¬ νŒ¨ν„΄ (Factory Pattern)

νŒ©ν† λ¦¬ νŒ¨ν„΄μ€ 또 λ‹€λ₯Έ 클래슀 기반 생성 νŒ¨ν„΄μ΄λ‹€. 객체 μΈμŠ€ν„΄μŠ€ν™”μ˜ μ±…μž„μ„ ν•˜μœ„ ν΄λž˜μŠ€μ— μœ„μž„ν•˜λŠ” λ²”μš© μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•œλ‹€.

이 νŒ¨ν„΄μ€ μ„œλ‘œ λ‹€λ₯΄μ§€λ§Œ λΉ„μŠ·ν•œ νŠΉμ„±μ΄ λ§Žμ€ 개체 μ»¬λ ‰μ…˜μ„ κ΄€λ¦¬ν•˜κ±°λ‚˜ μ‘°μž‘ν•΄μ•Ό ν•  λ•Œ 자주 μ‚¬μš©λœλ‹€.

이 μ˜ˆμ—μ„œλŠ” λ§€κ°œλ³€μˆ˜λ₯Ό μ·¨ν•˜λŠ” λ©”μ„œλ“œλ₯Ό 가진 BallFactoryλΌλŠ” νŒ©ν† λ¦¬ 클래슀λ₯Ό λ§Œλ“€κ³ , λ§€κ°œλ³€μˆ˜μ— 따라 객체 μΈμŠ€ν„΄μŠ€ν™” μ±…μž„μ„ 각 ν΄λž˜μŠ€μ— μœ„μž„ν•œλ‹€. νƒ€μž… λ§€κ°œλ³€μˆ˜κ°€ "football" λ˜λŠ” "soccer"인 경우, 객체 μΈμŠ€ν„΄μŠ€ν™”λŠ” Football ν΄λž˜μŠ€μ—μ„œ μ²˜λ¦¬ν•˜μ§€λ§Œ, "basketball" 객체 μΈμŠ€ν„΄μŠ€ν™”λŠ” Basketball ν΄λž˜μŠ€μ—μ„œ μ²˜λ¦¬ν•œλ‹€.

class BallFactory {
  constructor() {
    this.createBall = function(type) {
      let ball;
      if (type === 'football' || type === 'soccer') ball = new Football();
      else if (type === 'basketball') ball = new Basketball();
      ball.roll = function() {
        return `The ${this._type} is rolling.`;
      };

      return ball;
    };
  }
}

class Football {
  constructor() {
    this._type = 'football';
    this.kick = function() {
      return 'You kicked the football.';
    };
  }
}

class Basketball {
  constructor() {
    this._type = 'basketball';
    this.bounce = function() {
      return 'You bounced the basketball.';
    };
  }
}

// creating objects
const factory = new BallFactory();

const myFootball = factory.createBall('football');
const myBasketball = factory.createBall('basketball');

console.log(myFootball.roll()); // The football is rolling.
console.log(myBasketball.roll()); // The basketball is rolling.
console.log(myFootball.kick()); // You kicked the football.
console.log(myBasketball.bounce()); // You bounced the basketball.

ν”„λ‘œν† νƒ€μž… νŒ¨ν„΄ (Prototype Pattern)

이 νŒ¨ν„΄μ€ 고전적인 객체지ν–₯ 상속 λŒ€μ‹  ν”„λ‘œν† νƒ€μž… 상속을 μ‚¬μš©ν•˜λ―€λ‘œ JavaScript에 특히 μ€‘μš”ν•˜λ‹€. λ”°λΌμ„œ JavaScript의 강점을 λ”°λ₯΄λ©° κΈ°λ³Έ 지원을 μ œκ³΅ν•œλ‹€.

이 μ˜ˆμ œμ—λŠ” JavaScript의 Object.create κΈ°λŠ₯을 μ‚¬μš©ν•˜μ—¬ λ‹€λ₯Έ 객체 myCarλ₯Ό μƒμ„±ν•˜κ³  μƒˆ 객체에 μΆ”κ°€ 속성 ownerλ₯Ό μ •μ˜ν•˜κΈ° μœ„ν•΄ ν”„λ‘œν† νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•˜λŠ” car 객체가 μžˆλ‹€.

// using Object.create as was recommended by ES5 standard
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};

// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true

싱글톀 νŒ¨ν„΄ (Singleton Pattern)

싱글톀은 ν•˜λ‚˜μ˜ ν΄λž˜μŠ€μ— μΈμŠ€ν„΄μŠ€λ§Œ μ‘΄μž¬ν•  수 μžˆλŠ” νŠΉλ³„ν•œ λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. 싱글톀 클래슀의 μΈμŠ€ν„΄μŠ€κ°€ μ—†μœΌλ©΄ μƒˆ μΈμŠ€ν„΄μŠ€κ°€ λ§Œλ“€μ–΄μ§€κ³  λ°˜ν™˜λ˜μ§€λ§Œ μΈμŠ€ν„΄μŠ€κ°€ 이미 μ‘΄μž¬ν•˜λ©΄ κΈ°μ‘΄ μΈμŠ€ν„΄μŠ€μ— λŒ€ν•œ μ°Έμ‘°κ°€ λ°˜ν™˜λœλ‹€.

μ™„λ²½ν•œ μ‹€μ œ μ‚¬λ‘€λŠ” MongoDB의 유λͺ…ν•œ Node.js ODM 라이브러리인 mongoose이닀. mongooseλŠ” μ‹±κΈ€ 톀 νŒ¨ν„΄μ„ μ‚¬μš©ν•œλ‹€.

이 μ˜ˆμ œμ—λŠ” 싱글톀 클래슀인 Databaseκ°€ μžˆλ‹€. λ¨Όμ € new μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•˜μ—¬ Database 클래슀 μƒμ„±μžλ₯Ό ν˜ΈμΆœν•΄ 객체 mongoλ₯Ό λ§Œλ“ λ‹€. μ΄λ²ˆμ—λŠ” 객체가 μ—†κΈ° λ•Œλ¬Έμ— 객체가 μΈμŠ€ν„΄μŠ€ν™” λœλ‹€. 두 번째둜 mysql 객체λ₯Ό λ§Œλ“€ 땐, μƒˆ 객체가 μΈμŠ€ν„΄μŠ€ν™”λ˜μ§€ μ•Šκ³  이전에 μΈμŠ€ν„΄μŠ€ν™” 된 객체, 즉 mongo 객체에 λŒ€ν•œ μ°Έμ‘°κ°€ λ°˜ν™˜λœλ‹€.

class Database {
  constructor(data) {
    if (Database.exists) {
      return Database.instance;
    }
    this._data = data;
    Database.instance = this;
    Database.exists = true;
    return this;
  }

  getData() {
    return this._data;
  }

  setData(data) {
    this._data = data;
  }
}

// usage
const mongo = new Database('mongo');
console.log(mongo.getData()); // mongo

const mysql = new Database('mysql');
console.log(mysql.getData()); // mongo

μ–΄λŒ‘ν„° νŒ¨ν„΄ (Adapter Pattern)

μ–΄λŒ‘ν„° νŒ¨ν„΄μ€ ν•œ 클래슀의 μΈν„°νŽ˜μ΄μŠ€κ°€ λ‹€λ₯Έ 클래슀의 μΈν„°νŽ˜μ΄μŠ€λ‘œ λ³€ν™˜λ˜λŠ” ꡬ쑰적 νŒ¨ν„΄μ΄λ‹€. 이 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ ν˜Έν™˜λ˜μ§€ μ•ŠλŠ” μΈν„°νŽ˜μ΄μŠ€ λ•Œλ¬Έμ— λ‹€λ₯Έ λ°©λ²•μœΌλ‘œλŠ” λΆˆκ°€λŠ₯ν–ˆλ˜ ν΄λž˜μŠ€κ°€ ν•¨κ»˜ μž‘λ™ν•˜λ„λ‘ ν•œλ‹€.

이 νŒ¨ν„΄μ€ μ’…μ’… 기쑴의 λ‹€λ₯Έ 였래된 APIκ°€ μ—¬μ „νžˆ μž‘λ™ ν•  수 μžˆλ„λ‘, μƒˆλ‘œμš΄ λ¦¬νŒ©ν† λ§ 된 API에 λŒ€ν•œ wrapperλ₯Ό μž‘μ„±ν•˜λŠ” 데 μ‚¬μš©λœλ‹€. μ΄λŠ” 일반적으둜 μƒˆλ‘œμš΄ κ΅¬ν˜„ λ˜λŠ” μ½”λ“œ λ¦¬νŒ©ν† λ§ (μ„±λŠ₯ ν–₯상과 같은 이유둜 μˆ˜ν–‰)으둜 인해 λ‹€λ₯Έ 곡용 APIκ°€ μƒμ„±λ˜λŠ” 반면, μ‹œμŠ€ν…œμ˜ λ‹€λ₯Έ 뢀뢄은 μ—¬μ „νžˆ ​​기쑴 APIλ₯Ό μ‚¬μš©ν•˜κ³  ν•¨κ»˜ μž‘λ™ν•˜λ„λ‘ μ‘°μ •λ˜μ–΄μ•Όν•˜λŠ” κ²½μš°μ— μˆ˜ν–‰λœλ‹€.

이 μ˜ˆμ œμ—λŠ” 이전 API (OldCalculator 클래슀)와 μƒˆ API (NewCalculator 클래슀)κ°€ μžˆλ‹€. OldCalculator ν΄λž˜μŠ€λŠ” λ§μ…ˆκ³Ό λΊ„μ…ˆμ„μœ„ν•œ operation λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ” 반면 NewCalculatorλŠ” λ§μ…ˆκ³Ό λΊ„μ…ˆμ„ μœ„ν•œ λ³„λ„μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€. Adapter 클래슀 CalcAdapterλŠ” NewCalculatorλ₯Ό κ°μ‹Έμ„œ(wrap) operation λ©”μ†Œλ“œλ₯Ό 곡용 API에 μΆ”κ°€ν•˜λŠ” λ™μ‹œμ— 자체 λ§μ…ˆ 및 λΊ„μ…ˆ κ΅¬ν˜„μ„ μ‚¬μš©ν•œλ‹€.

// old interface
class OldCalculator {
  constructor() {
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return term1 + term2;
        case 'sub':
          return term1 - term2;
        default:
          return NaN;
      }
    };
  }
}

// new interface
class NewCalculator {
  constructor() {
    this.add = function(term1, term2) {
      return term1 + term2;
    };
    this.sub = function(term1, term2) {
      return term1 - term2;
    };
  }
}

// Adapter Class
class CalcAdapter {
  constructor() {
    const newCalc = new NewCalculator();

    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          // using the new implementation under the hood
          return newCalc.add(term1, term2);
        case 'sub':
          return newCalc.sub(term1, term2);
        default:
          return NaN;
      }
    };
  }
}

// usage
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15

const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15

const adaptedCalc = new CalcAdapter();
console.log(adaptedCalc.operations(10, 5, 'add')); // 15;

볡합 νŒ¨ν„΄ (Composite pattern)

νŠΈλ¦¬μ™€ 같은 ꡬ쑰둜 객체λ₯Ό κ΅¬μ„±ν•˜μ—¬, 전체 λΆ€λΆ„μ˜ 계측 ꡬ쑰λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ꡬ쑰 섀계 νŒ¨ν„΄μ΄λ‹€. 이 νŒ¨ν„΄μ—μ„œ νŠΈλ¦¬μ™€ 같은 ꡬ쑰의 각 λ…Έλ“œλŠ” κ°œλ³„ κ°μ²΄μ΄κ±°λ‚˜ κ΅¬μ„±λœ 객체 λͺ¨μŒ 일 수 μžˆλ‹€. μ–΄μ¨Œλ“  각 λ…Έλ“œλŠ” κ· μΌν•˜κ²Œ μ²˜λ¦¬λœλ‹€.

이 νŒ¨ν„΄μ„ μ‹œκ°ν™”ν•˜λŠ” 것은 μ•½κ°„ λ³΅μž‘ν•˜λ‹€. 이것을 μ΄ν•΄ν•˜λŠ” κ°€μž₯ μ‰¬μš΄ 방법은 닀단계 λ©”λ‰΄μ˜ 예λ₯Ό λ³΄λŠ” 것이닀. 각 λ…Έλ“œλŠ” μžμ‹μœΌλ‘œ μ—¬λŸ¬ μ˜΅μ…˜μ΄ μžˆλŠ” λ³„κ°œμ˜ μ˜΅μ…˜ 일 μˆ˜λ„ 있고 메뉴 자체일 수 μžˆλ‹€. μžμ‹μ΄ μžˆλŠ” λ…Έλ“œ μ»΄ν¬λ„ŒνŠΈλŠ” 볡합 μ»΄ν¬λ„ŒνŠΈ(composite component)이고 μžμ‹μ΄ μ—†λŠ” λ…Έλ“œ μ»΄ν¬λ„ŒνŠΈλŠ” 리프 μ»΄ν¬λ„ŒνŠΈ(leaft component)이닀.

이 μ˜ˆμ œμ—μ„œλŠ” ν•„μš”ν•œ 곡톡 κΈ°λŠ₯λ§Œμ„ κ΅¬ν˜„ν•˜κ³  λ‹€λ₯Έ λ©”μ„œλ“œλ₯Ό μΆ”μƒν™”ν•˜λŠ” Component의 κΈ°λ³Έ 클래슀λ₯Ό μž‘μ„±ν•œλ‹€. κΈ°λ³Έ ν΄λž˜μŠ€μ—λŠ” μž¬κ·€λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•˜μœ„ 클래슀둜 λ§Œλ“  볡합 트리 ꡬ쑰λ₯Ό ν†΅κ³Όν•˜λŠ” static λ©”μ„œλ“œλ„ μžˆλ‹€. 그런 λ‹€μŒ κΈ°λ³Έ 클래슀λ₯Ό ν™•μž₯ν•˜λŠ” 두 개의 μ„œλΈŒ 클래슀인 μžμ‹μ΄ μ—†λŠ” Leaf와 μžμ‹μ„ κ°€μ§ˆ 수 μžˆλŠ” Compositeλ₯Ό λ§Œλ“ λ‹€. 두 개의 μ„œλΈŒ ν΄λž˜μŠ€λŠ” 볡합 ꡬ쑰 (이 경우 트리)λ₯Ό μž‘μ„±ν•˜λŠ” 데 μ‚¬μš©λœλ‹€.

class Component {
  constructor(name) {
    this._name = name;
  }

  getNodeName() {
    return this._name;
  }

  // abstract methods that need to be overridden
  getType() {}

  addChild(component) {}

  removeChildByName(componentName) {}

  removeChildByIndex(index) {}

  getChildByName(componentName) {}

  getChildByIndex(index) {}

  noOfChildren() {}

  static logTreeStructure(root) {
    let treeStructure = '';
    function traverse(node, indent = 0) {
      treeStructure += `${'--'.repeat(indent)}${node.getNodeName()}\n`;
      indent++;
      for (let i = 0, length = node.noOfChildren(); i < length; i++) {
        traverse(node.getChildByIndex(i), indent);
      }
    }

    traverse(root);
    return treeStructure;
  }
}

class Leaf extends Component {
  constructor(name) {
    super(name);
    this._type = 'Leaf Node';
  }

  getType() {
    return this._type;
  }

  noOfChildren() {
    return 0;
  }
}

class Composite extends Component {
  constructor(name) {
    super(name);
    this._type = 'Composite Node';
    this._children = [];
  }

  getType() {
    return this._type;
  }

  addChild(component) {
    this._children = [...this._children, component];
  }

  removeChildByName(componentName) {
    this._children = [...this._children].filter(component => component.getNodeName() !== componentName);
  }

  removeChildByIndex(index) {
    this._children = [...this._children.slice(0, index), ...this._children.slice(index + 1)];
  }

  getChildByName(componentName) {
    return this._children.find(component => component.name === componentName);
  }

  getChildByIndex(index) {
    return this._children[index];
  }

  noOfChildren() {
    return this._children.length;
  }
}

// usage
const tree = new Composite('root');
tree.addChild(new Leaf('left'));
const right = new Composite('right');
tree.addChild(right);
right.addChild(new Leaf('right-left'));
const rightMid = new Composite('right-middle');
right.addChild(rightMid);
right.addChild(new Leaf('right-right'));
rightMid.addChild(new Leaf('left-end'));
rightMid.addChild(new Leaf('right-end'));

// log
console.log(Component.logTreeStructure(tree));
/*
root
--left
--right
----right-left
----right-middle
------left-end
------right-end
----right-right
*/

λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄ (Decorator Pattern)

이 νŒ¨ν„΄μ€ κΈ°μ‘΄ ν΄λž˜μŠ€μ— λ™μž‘ λ˜λŠ” κΈ°λŠ₯을 λ™μ μœΌλ‘œ μΆ”κ°€ν•˜λŠ” 것에 μ΄ˆμ μ„ λ‘” ꡬ쑰 섀계 νŒ¨ν„΄μ΄λ‹€. 클래슀 μƒμ†μ˜ λ˜λ‹€λ₯Έ λŒ€μ•ˆμ΄λ‹€.

JavaScriptλ₯Ό μ‚¬μš©ν•˜λ©΄ 객체에 λ©”μ„œλ“œμ™€ 속성을 λ™μ μœΌλ‘œ μΆ”κ°€ν•  수 있기 λ•Œλ¬Έμ—, JavaScriptμ—μ„œ λ°μ½”λ ˆμ΄ν„° μœ ν˜• λ™μž‘μ€ 맀우 μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€. κ°€μž₯ κ°„λ‹¨ν•œ 접근방식은 객체의 속성을 μΆ”κ°€ν•˜λŠ” κ²ƒμ΄μ§€λ§Œ, μž¬μ‚¬μš© ν•  μˆ˜λŠ” 없을 것이닀.

μ‹€μ œλ‘œ, JavaScript 언어에 λ°μ½”λ ˆμ΄ν„°λ₯Ό μΆ”κ°€ν•˜λΌλŠ” μ œμ•ˆμ΄ μžˆλ‹€. JavaScript의 λ°μ½”λ ˆμ΄ν„°μ— λŒ€ν•œ Addy Osmani의 κ²Œμ‹œλ¬Ό 을 μ‚΄νŽ΄λ³΄μž .

μ œμ•ˆμ„œ 자체 에 λŒ€ν•΄ 읽으렀면 μ—¬κΈ°.

이 μ˜ˆμ—μ„œλŠ” Book 클래슀λ₯Ό λ§Œλ“ λ‹€. λ˜ν•œ book 객체λ₯Ό 인자둜 λ°›κ³  "꾸며진(decorated)" book 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” 두 개의 λ°μ½”λ ˆμ΄ν„° ν•¨μˆ˜λ₯Ό μΆ”κ°€λ‘œ λ§Œλ“ λ‹€. ν•˜λ‚˜μ˜ μƒˆλ‘œμš΄ 속성과 ν•˜λ‚˜μ˜ μƒˆλ‘œμš΄ ν•¨μˆ˜λ₯Ό μΆ”κ°€ν•˜λŠ” giftWrapκ³Ό ν•˜λ‚˜μ˜ μƒˆλ‘œμš΄ 속성을 μΆ”κ°€ν•˜κ³  ν•˜λ‚˜μ˜ κΈ°μ‘΄ μ†μ„±μ˜ 값을 νŽΈμ§‘ν•˜λŠ” hardbindBook이닀.

class Book {
  constructor(title, author, price) {
    this._title = title;
    this._author = author;
    this.price = price;
  }

  getDetails() {
    return `${this._title} by ${this._author}`;
  }
}

// decorator 1
function giftWrap(book) {
  book.isGiftWrapped = true;
  book.unwrap = function() {
    return `Unwrapped ${book.getDetails()}`;
  };

  return book;
}

// decorator 2
function hardbindBook(book) {
  book.isHardbound = true;
  book.price += 5;
  return book;
}

// usage
const alchemist = giftWrap(new Book('The Alchemist', 'Paulo Coelho', 10));

console.log(alchemist.isGiftWrapped); // true
console.log(alchemist.unwrap()); // 'Unwrapped The Alchemist by Paulo Coelho'

const inferno = hardbindBook(new Book('Inferno', 'Dan Brown', 15));

console.log(inferno.isHardbound); // true
console.log(inferno.price); // 20

νΌμ‚¬λ“œ νŒ¨ν„΄ (Facade Pattern)

νΌμ‚¬λ“œ νŒ¨ν„΄μ€ JavaScript λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ 널리 μ‚¬μš©λ˜λŠ” ꡬ쑰 섀계 νŒ¨ν„΄μ΄λ‹€. νΌμ‚¬λ“œ νŒ¨ν„΄μ€ ν•˜μœ„ μ‹œμŠ€ν…œμ΄λ‚˜ ν•˜μœ„ 클래슀의 λ³΅μž‘μ„±μœΌλ‘œλΆ€ν„° λ²—μ–΄λ‚˜ μ‚¬μš© νŽΈμ˜μ„±μ„ μœ„ν•΄ ν†΅ν•©λ˜κ³  λ‹¨μˆœν•œ 곡곡 지ν–₯ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.

이 νŒ¨ν„΄μ˜ μ‚¬μš©μ€ jQuery와 같은 λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ 맀우 μΌλ°˜μ μ΄λ‹€.

이 μ˜ˆμ—μ„œλŠ” ComplaintRegistry 클래슀λ₯Ό μ‚¬μš©ν•˜μ—¬ 곡개 APIλ₯Ό λ§Œλ“ λ‹€. ν΄λΌμ΄μ–ΈνŠΈκ°€ μ‚¬μš©ν•  ν•˜λ‚˜μ˜ λ©”μ†Œλ“œ, 즉 registerComplaint 만 λ…ΈμΆœν•œλ‹€. registerComplaintλŠ” λ‚΄λΆ€μ μœΌλ‘œ type μΈμˆ˜μ— 따라 ProductComplaint λ˜λŠ” ServiceComplaint 쀑 ν•˜λ‚˜μ˜ ν•„μš”ν•œ 객체λ₯Ό μΈμŠ€ν„΄μŠ€ν™” μ²˜λ¦¬ν•œλ‹€. λ˜ν•œ 고유 ID 생성, 뢈만 사항을 λ©”λͺ¨λ¦¬μ— μ €μž₯ν•˜λŠ” λ“±μ˜ λ‹€λ₯Έ λͺ¨λ“  λ³΅μž‘ν•œ κΈ°λŠ₯도 μ²˜λ¦¬ν•œλ‹€. κ·ΈλŸ¬λ‚˜ μ΄λŸ¬ν•œ λͺ¨λ“  λ³΅μž‘μ„±μ€ νΌμ‚¬λ“œ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ—¬ 숨겨져 μžˆλ‹€.

let currentId = 0;

class ComplaintRegistry {
  registerComplaint(customer, type, details) {
    const id = ComplaintRegistry._uniqueIdGenerator();
    let registry;
    if (type === 'service') {
      registry = new ServiceComplaints();
    } else {
      registry = new ProductComplaints();
    }
    return registry.addComplaint({ id, customer, details });
  }

  static _uniqueIdGenerator() {
    return ++currentId;
  }
}

class Complaints {
  constructor() {
    this.complaints = [];
  }

  addComplaint(complaint) {
    this.complaints.push(complaint);
    return this.replyMessage(complaint);
  }

  getComplaint(id) {
    return this.complaints.find(complaint => complaint.id === id);
  }

  replyMessage(complaint) {}
}

class ProductComplaints extends Complaints {
  constructor() {
    super();
    if (ProductComplaints.exists) {
      return ProductComplaints.instance;
    }
    ProductComplaints.instance = this;
    ProductComplaints.exists = true;
    return this;
  }

  replyMessage({ id, customer, details }) {
    return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.`;
  }
}

class ServiceComplaints extends Complaints {
  constructor() {
    super();
    if (ServiceComplaints.exists) {
      return ServiceComplaints.instance;
    }
    ServiceComplaints.instance = this;
    ServiceComplaints.exists = true;
    return this;
  }

  replyMessage({ id, customer, details }) {
    return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.`;
  }
}

// usage
const registry = new ComplaintRegistry();

const reportService = registry.registerComplaint('Martha', 'service', 'availability');
// 'Complaint No. 1 reported by Martha regarding availability have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.'

const reportProduct = registry.registerComplaint('Jane', 'product', 'faded color');
// 'Complaint No. 2 reported by Jane regarding faded color have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.'

ν”ŒλΌμ΄μ›¨μ΄νŠΈ νŒ¨ν„΄ (Flyweight Pattern)

ν”ŒλΌμ΄μ›¨μ΄νŠΈ νŒ¨ν„΄μ€ μ„Έλ°€ν•œ 객체(fine-grained objects)λ₯Ό ν†΅ν•΄νš¨μœ¨μ μΈ 데이터 κ³΅μœ μ— 쀑점을 λ‘” ꡬ쑰 섀계 νŒ¨ν„΄μž…λ‹ˆλ‹€. νš¨μœ¨μ„± 및 λ©”λͺ¨λ¦¬ μ ˆμ•½ λͺ©μ μœΌλ‘œ μ‚¬μš©λœλ‹€.

이 νŒ¨ν„΄μ€ λͺ¨λ“  μ’…λ₯˜μ˜ 캐싱 λͺ©μ μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλ‹€. μ‹€μ œλ‘œ μ΅œμ‹  λΈŒλΌμš°μ €λŠ” ν”ŒλΌμ΄μ›¨μ΄νŠΈ νŒ¨ν„΄μ˜ λ³€ν˜•μ„ μ‚¬μš©ν•˜μ—¬ λ™μΌν•œ 이미지λ₯Ό 두 λ²ˆλ‘œλ“œν•˜μ§€ λͺ»ν•˜κ²Œ ν•œλ‹€.

이 μ˜ˆμ—μ„œλŠ” μ•„μ΄μŠ€ν¬λ¦Ό 맛에 κ΄€ν•œ 데이터λ₯Ό κ³΅μœ ν•˜κΈ°μœ„ν•œ μ„Έλ°€ν•œ ν”ŒλΌμ΄κΈ‰ 클래슀 와 IcecreamFactoryν•΄λ‹Ή ν”ŒλΌμ΄κΈ‰ 객체λ₯Ό λ§Œλ“œλŠ” νŒ©ν† λ¦¬ 클래슀 λ₯Ό λ§Œλ“­λ‹ˆλ‹€. λ©”λͺ¨λ¦¬ μ ˆμ•½μ„ μœ„ν•΄ λ™μΌν•œ 객체가 두 번 μΈμŠ€ν„΄μŠ€ν™”λ˜λ©΄ 객체가 μž¬ν™œμš©λ©λ‹ˆλ‹€. ν”ŒλΌμ΄κΈ‰ κ΅¬ν˜„μ˜ κ°„λ‹¨ν•œ μ˜ˆμž…λ‹ˆλ‹€.

이 μ˜ˆμ—μ„œλŠ” μ•„μ΄μŠ€ν¬λ¦Ό 맛에 κ΄€ν•œ 데이터λ₯Ό κ³΅μœ ν•˜κΈ°μœ„ν•œ μ„Έλ°€ν•œ ν”ŒλΌμ΄μ›¨μ΄νŠΈ 클래슀 Icecreamκ³Ό ν•΄λ‹Ή ν”ŒλΌμ΄μ›¨μ΄νŠΈ 객체λ₯Ό λ§Œλ“€κΈ°μœ„ν•œ νŒ©ν† λ¦¬ 클래슀 IcecreamFactoryλ₯Ό λ§Œλ“ λ‹€. λ©”λͺ¨λ¦¬ μ ˆμ•½μ„ μœ„ν•΄ λ™μΌν•œ 객체가 두 번 μΈμŠ€ν„΄μŠ€ν™”λ˜λ©΄ 객체가 μž¬ν™œμš©λœλ‹€. ν”ŒλΌμ΄μ›¨μ΄νŠΈ νŒ¨ν„΄ κ΅¬ν˜„μ˜ κ°„λ‹¨ν•œ μ˜ˆμ΄λ‹€.

// flyweight class
class Icecream {
  constructor(flavour, price) {
    this.flavour = flavour;
    this.price = price;
  }
}

// factory for flyweight objects
class IcecreamFactory {
  constructor() {
    this._icecreams = [];
  }

  createIcecream(flavour, price) {
    let icecream = this.getIcecream(flavour);
    if (icecream) {
      return icecream;
    } else {
      const newIcecream = new Icecream(flavour, price);
      this._icecreams.push(newIcecream);
      return newIcecream;
    }
  }

  getIcecream(flavour) {
    return this._icecreams.find(icecream => icecream.flavour === flavour);
  }
}

// usage
const factory = new IcecreamFactory();

const chocoVanilla = factory.createIcecream('chocolate and vanilla', 15);
const vanillaChoco = factory.createIcecream('chocolate and vanilla', 15);

// reference to the same object
console.log(chocoVanilla === vanillaChoco); // true

ν”„λ‘μ‹œ νŒ¨ν„΄ (Proxy Pattern)

ν”„λ‘μ‹œ νŒ¨ν„΄λŠ” μ΄λ¦„μ²˜λŸΌ λ™μž‘ν•˜λŠ” ꡬ쑰적 λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€. λŒ€μƒ 객체λ₯Ό λ˜λ‹€λ₯Έ 객체가 μ ‘κ·Όν•  λ•Œ, 접근을 ν†΅μ œν•˜λŠ” λŒ€λ¦¬μž 역할을 ν•œλ‹€.

일반적으둜 λŒ€μƒ κ°œμ²΄κ°€ μ œμ•½μ„ λ°›κ³  λͺ¨λ“  μ±…μž„μ„ 효율적으둜 μ²˜λ¦¬ν•˜μ§€ λͺ»ν•  μˆ˜μžˆλŠ” μƒν™©μ—μ„œ μ‚¬μš©λœλ‹€. 이 경우 ν”„λ‘μ‹œλŠ” 일반적으둜 ν΄λΌμ΄μ–ΈνŠΈμ— λ™μΌν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•˜κ³  κ³Όλ„ν•œ μ••λ ₯을 ν”Όν•˜κΈ° μœ„ν•΄ λŒ€μƒ 객체에 λŒ€ν•œ μ œμ–΄λœ 접근을 μ§€μ›ν•˜λŠ” 간접적인 μˆ˜μ€€μ„ μΆ”κ°€ν•œλ‹€.

ν”„λ‘μ‹œ νŒ¨ν„΄μ€ λ„€νŠΈμ›Œν¬ μš”μ²­μ΄ λ§Žμ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μœΌλ‘œ μž‘μ—… ν•  λ•Œ λΆˆν•„μš”ν•˜κ±°λ‚˜ 쀑볡 된 λ„€νŠΈμ›Œν¬ μš”μ²­μ„ ν”Όν•˜λŠ” 데 맀우 μœ μš©ν•˜λ‹€.

이 μ˜ˆμ—μ„œλŠ” Proxy 및 Reflect의 두 가지 μƒˆλ‘œμš΄ ES6 κΈ°λŠ₯을 μ‚¬μš© ν•©λ‹ˆλ‹€. Proxy κ°μ²΄λŠ” JavaScript 객체의 κΈ°λ³Έ μž‘μ—…μ— λŒ€ν•œ μ‚¬μš©μž 지정 λ™μž‘μ„ μ •μ˜ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€ (ν•¨μˆ˜ 및 배열도 JavaScript의 객체 μž„). Proxy κ°μ²΄λŠ” Proxy 객체 λ₯Ό λ§Œλ“œλŠ” 데 μ‚¬μš©ν•  μˆ˜μžˆλŠ” μƒμ„±μž λ©”μ„œλ“œμ΄κ³ , ν”„λ‘μ‹œλ₯Ό ν•  target 객체와 ν•„μš”ν•œ μ‚¬μš©μž 지정을 μ •μ˜ν•  handler 객체λ₯Ό 인수둜 λ°›λŠ”λ‹€. handler κ°μ²΄λŠ” μ‚¬μš©μž μ •μ˜ λ™μž‘μ„ μΆ”κ°€ν•˜λŠ” 데 μ‚¬μš©λ˜λŠ” get, set, has, apply 같은 일뢀 트랩 κΈ°λŠ₯을 μ •μ˜ ν•  수 μžˆλ‹€. λ°˜λ©΄μ—, ReflectλŠ” Proxy의 handler 객체가 μ§€μ›ν•˜λŠ” μœ μ‚¬ν•œ λ©”μ„œλ“œλ₯Ό 슀슀둜 static λ©”μ„œλ“œλ‘œ μ œκ³΅ν•˜λŠ” λ‚΄μž₯ 객체이닀. μƒμ„±μžκ°€ μ•„λ‹ˆλ‹€. static λ©”μ„œλ“œλŠ” κ°€λ‘œμ±„κΈ° κ°€λŠ₯ν•œ μžλ°”μŠ€ν¬λ¦½νŠΈ μž‘μ—…μ— μ‚¬μš©λœλ‹€.

이제 λ„€νŠΈμ›Œν¬ μš”μ²­ 같은 ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€. μš°λ¦¬λŠ” 그것을 networkFetch둜 λͺ…λͺ…ν–ˆλ‹€. url을 인수둜 λ°›μ•„ 그에 따라 μ‘λ‹΅ν•œλ‹€. μΊμ‹œλ₯Ό μ‚¬μš©ν•  μˆ˜μ—†λŠ” κ²½μš°μ—λ§Œ λ„€νŠΈμ›Œν¬μ—μ„œ 응닡을 λ°›λŠ” ν”„λ‘μ‹œλ₯Ό κ΅¬ν˜„ν•˜λ €κ³ ν•œλ‹€. 그렇지 μ•ŠμœΌλ©΄ μΊμ‹œμ—μ„œ 응닡을 λ°˜ν™˜ν•œλ‹€.

μΊμ‹œ μ „μ—­ λ³€μˆ˜λŠ” μΊμ‹œ 된 응닡을 μ €μž₯ν•©λ‹ˆλ‹€. target으둜 μ›λž˜ networkFetchλ₯Ό μ‚¬μš©ν•˜μ—¬ proxiedNetworkFetchλΌλŠ” ν”„λ‘μ‹œλ₯Ό μž‘μ„±ν•˜κ³  handler κ°μ²΄μ—μ„œ apply λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ ν•¨μˆ˜ ν˜ΈμΆœμ„ ν”„λ‘μ‹œ ν•œλ‹€. apply λ©”μ„œλ“œλŠ” target 객체 μžμ²΄μ— μ „λ‹¬λœλ‹€. thisArgκ°’κ³Ό argsλŠ” λ°°μ—΄κ³Ό 같은 ꡬ쑰둜 μ „λ‹¬λœλ‹€.

전달 된 url μΈμˆ˜κ°€ μΊμ‹œμ— μžˆλŠ”μ§€ ν™•μΈν•œλ‹€. μΊμ‹œμ— μ‘΄μž¬ν•˜λ©΄ μ›λž˜ λŒ€μƒ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ§€ μ•Šκ³  응닡을 λ°˜ν™˜ν•œλ‹€. 그렇지 μ•ŠμœΌλ©΄, μš°λ¦¬λŠ” Reflect.apply λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ thisArg와 argsλ₯Ό μ „λ‹¬ν•˜λ©° target ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œλ‹€.

// Target
function networkFetch(url) {
  return `${url} - Response from network`;
}

// Proxy
// ES6 Proxy API = new Proxy(target, handler);
const cache = [];
const proxiedNetworkFetch = new Proxy(networkFetch, {
  apply(target, thisArg, args) {
    const urlParam = args[0];
    if (cache.includes(urlParam)) {
      return `${urlParam} - Response from cache`;
    } else {
      cache.push(urlParam);
      return Reflect.apply(target, thisArg, args);
    }
  },
});

// usage
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from network'
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from cache'

μ±…μž„ μ‚¬μŠ¬ νŒ¨ν„΄ (Chain of Responsibility Pattern)

이것은 λŠμŠ¨ν•˜κ²Œ μ—°κ²°λœ 객체 체인을 μ œκ³΅ν•˜λŠ” 행동 λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. μ΄λŸ¬ν•œ 각 κ°μ²΄λŠ” ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­μ— 따라 μž‘λ™ν•˜κ±°λ‚˜ μ²˜λ¦¬ν•˜λ„λ‘ 선택할 수 μžˆλ‹€.

μ±…μž„ μ‚¬μŠ¬ νŒ¨ν„΄μ˜ 쒋은 μ˜ˆλŠ” μ΄λ²€νŠΈκ°€ 일련의 쀑첩 된 DOM μš”μ†Œλ₯Ό 톡해 μ „νŒŒλ˜λŠ” DOMμ—μ„œμ˜ 이벀트 버블링(bubbling)인데, κ·Έ 쀑 ν•˜λ‚˜λŠ” 이벀트λ₯Ό μˆ˜μ‹ ν•˜κ³  μ΄λ²€νŠΈμ— λŒ€ν•΄ μˆ˜ν–‰ν•˜κΈ° μœ„ν•΄ "이벀트 λ¦¬μŠ€λ„ˆ"κ°€ μ²¨λΆ€λ˜μ–΄μžˆμ„ 수 μžˆλ‹€.

이 μ˜ˆμ œμ—μ„œ, μš°λ¦¬λŠ” CumulativeSum 클래슀λ₯Ό μƒμ„±ν•˜λŠ”λ°, 이 ν΄λž˜μŠ€λŠ” μ˜΅μ…˜μœΌλ‘œ initialValueλ₯Ό λ°›μ•„ μΈμŠ€ν„΄μŠ€ν™” ν•  수 μžˆλ‹€. 전달 된 값을 객체의 sum 속성에 μΆ”κ°€ν•˜κ³  add λ©”μ†Œλ“œ 호좜의 체인을 ν—ˆμš©ν•˜κΈ° μœ„ν•΄ object 자체λ₯Ό λ°˜ν™˜ν•˜λŠ” add λ©”μ„œλ“œκ°€ μžˆλ‹€.

μ±…μž„ μ‚¬μŠ¬ νŒ¨ν„΄μ€ jQueryμ—μ„œλ„ λ³Ό μˆ˜μžˆλŠ” 일반적인 νŒ¨ν„΄μœΌλ‘œ, jQuery 객체에 λŒ€ν•œ 거의 λͺ¨λ“  λ©”μ†Œλ“œ 호좜이 jQuery 객체λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ λ©”μ†Œλ“œ ν˜ΈμΆœμ„ ν•¨κ»˜ μ—°κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class CumulativeSum {
  constructor(intialValue = 0) {
    this.sum = intialValue;
  }

  add(value) {
    this.sum += value;
    return this;
  }
}

// usage
const sum1 = new CumulativeSum();
console.log(sum1.add(10).add(2).add(50).sum); // 62


const sum2 = new CumulativeSum(10);
console.log(sum2.add(10).add(20).add(5).sum); // 45

μ»€λ§¨λ“œ νŒ¨ν„΄ (Command Pattern)

μ»€λ§¨λ“œ νŒ¨ν„΄μ€ λ™μž‘ λ˜λŠ” λ™μž‘μ„ 객체둜 μΊ‘μŠν™”ν•˜λŠ” 것을 λͺ©ν‘œλ‘œν•˜λŠ” 행동 λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. 이 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ μž‘μ—…μ„ μš”μ²­ν•˜κ±°λ‚˜ μ‹€μ œ κ΅¬ν˜„μ„ μ²˜λ¦¬ν•˜κ±°λ‚˜ μ²˜λ¦¬ν•˜λŠ” κ°μ²΄μ—μ„œ λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” 객체λ₯Ό λΆ„λ¦¬ν•˜μ—¬ μ‹œμŠ€ν…œκ³Ό 클래슀λ₯Ό λŠμŠ¨ν•˜κ²Œ μ—°κ²°ν•  수 μžˆλ‹€.

클립 λ³΄λ“œ APIκ°€ μ»€λ§¨λ“œ νŒ¨ν„΄κ³Ό μœ μ‚¬ν•©λ‹ˆλ‹€. Redux μ‚¬μš©μžμΈ 경우, 이미 μ»€λ§¨λ“œ νŒ¨ν„΄μ„ μ‚¬μš©ν•΄λ³Έ 것이닀. 멋진 μ‹œκ°„ μ—¬ν–‰ 디버깅 κΈ°λŠ₯을 ν—ˆμš©ν•˜λŠ” μž‘μ—…μ€, μž‘μ—…μ„ λ‹€μ‹œ μ‹€ν–‰ν•˜κ±°λ‚˜ μ‹€ν–‰ μ·¨μ†Œν•˜λ„λ‘ 좔적 ν•  μˆ˜μžˆλŠ” μΊ‘μŠν™” 된 μž‘μ—…μΌ 뿐이닀. λ”°λΌμ„œ μ‹œκ°„ 여행이 κ°€λŠ₯ν•΄μ‘Œλ‹€.

이 μ˜ˆμ œμ—λŠ” μ—¬λŸ¬ λ©”μ„œλ“œκ°€μžˆλŠ” SpecialMath ν΄λž˜μŠ€μ™€ ν•΄λ‹Ή μ£Όμ œμ—μ„œ 싀행될 λͺ…λ Ή, 즉 SpecialMath 클래슀의 객체λ₯Ό μΊ‘μŠν™”ν•˜λŠ” Command ν΄λž˜μŠ€κ°€ μžˆλ‹€. Command ν΄λž˜μŠ€λŠ” μ‹€ν–‰ 된 λͺ¨λ“  λͺ…령을 μΆ”μ ν•˜μ—¬ μ‹€ν–‰ μ·¨μ†Œ 및 μž¬μ‹€ν–‰ν–‰ μœ ν˜• μž‘μ—…μ„ ν¬ν•¨ν•˜λ„λ‘ κΈ°λŠ₯을 ν™•μž₯ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆλ‹€.

class SpecialMath {
  constructor(num) {
    this._num = num;
  }

  square() {
    return this._num ** 2;
  }

  cube() {
    return this._num ** 3;
  }

  squareRoot() {
    return Math.sqrt(this._num);
  }
}

class Command {
  constructor(subject) {
    this._subject = subject;
    this.commandsExecuted = [];
  }
  execute(command) {
    this.commandsExecuted.push(command);
    return this._subject[command]();
  }
}

// usage
const x = new Command(new SpecialMath(5));
x.execute('square');
x.execute('cube');

console.log(x.commandsExecuted); // ['square', 'cube']

μ΄ν„°λ ˆμ΄ν„° νŒ¨ν„΄ (Iterator Pattern)

μ΄ν„°λ ˆμ΄ν„° νŒ¨ν„΄μ€ κΈ°λ³Έ ν‘œν˜„μ„ λ…ΈμΆœμ‹œν‚€μ§€ μ•Šκ³  집계 객체 μš”μ†Œμ— 순차적으둜 μ•‘μ„ΈμŠ€ν•˜λŠ” 방법을 μ œκ³΅ν•˜λŠ” 행동 섀계 νŒ¨ν„΄μ΄λ‹€.

μ΄ν„°λ ˆμ΄ν„°λ“€μ€ μš°λ¦¬κ°€ 끝에 도달할 λ•ŒκΉŒμ§€ next()λ₯Ό ν˜ΈμΆœν•˜μ—¬ ν•œ λ²ˆμ— ν•˜λ‚˜μ”© μˆœμ„œκ°€ 정해진 일련의 κ°€μΉ˜λ“€μ„ λ°Ÿμ•„ λ‚˜κ°€λŠ” νŠΉλ³„ν•œ μ’…λ₯˜μ˜ 행동을 ν•œλ‹€. ES6μ—μ„œ Iterator와 Generator의 λ„μž…μœΌλ‘œ 인해 μ΄ν„°λ ˆμ΄ν„° νŒ¨ν„΄μ˜ κ΅¬ν˜„μ΄ 맀우 κ°„λ‹¨ν•΄μ‘Œλ‹€.

μ•„λž˜μ— 두 가지 μ˜ˆκ°€ μžˆμŠ΅λ‹ˆλ‹€. 첫째, ν•˜λ‚˜ IteratorClassλŠ” Iterator μŠ€νŽ™μ„ μ‚¬μš©ν•˜κ³ , λ‹€λ₯Έ ν•˜λ‚˜ iteratorUsingGeneratorλŠ” Generator ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œλ‹€.

Symbol.iterator( Symbol은 κΈ°λ³Έ 데이터 νƒ€μž…μ˜ μƒˆλ‘œμš΄ μ’…λ₯˜μ˜) 객체에 λŒ€ν•œ κΈ°λ³Έ 반볡자λ₯Ό μ§€μ •ν•˜λŠ” 데 μ‚¬μš©λœλ‹€. μ»¬λ ‰μ…˜μ΄ for...of 루핑 ꡬ성을 μ‚¬μš©ν•  수 μžˆλ„λ‘ Symbol.iteratorλŠ” μ •μ˜ν•΄μ•Όν•œλ‹€. 첫 번째 μ˜ˆμ—μ„œλŠ” μƒμ„±μžλ₯Ό μ •μ˜ν•˜μ—¬ 일뢀 데이터 λͺ¨μŒμ„ μ €μž₯ ν•œ λ‹€μŒ λ°˜λ³΅μ„ μœ„ν•œ next λ©”μ†Œλ“œκ°€ 포함 된 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” Symbol.iteratorλ₯Ό μ •μ˜ν–ˆλ‹€.

두 번째 경우, 두 번째 κ²½μš°μ—λŠ” Generator ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜μ—¬ 데이터 배열을 μ „λ‹¬ν•˜κ³  next 및 yieldλ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή μš”μ†Œλ₯Ό 반볡적으둜 λ°˜ν™˜ν•œλ‹€. μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λŠ” μ΄ν„°λ ˆμ΄ν„°μ˜ νŒ©ν† λ¦¬λ‘œ μž‘λ™ν•˜λŠ” νŠΉμˆ˜ν•œ μœ ν˜•μ˜ ν•¨μˆ˜μ΄λ©°, 자체 λ‚΄λΆ€ μƒνƒœλ₯Ό λͺ…μ‹œ 적으둜 μœ μ§€ν•˜κ³  반볡적으둜 값을 μ‚°μΆœ ν•  수 μžˆλ‹€. 자체 μ‹€ν–‰μ£ΌκΈ°λ₯Ό μΌμ‹œμ€‘μ§€ν–ˆλ‹€κ°€ λ‹€μ‹œ μ‹œμž‘ν•  수 μžˆλ‹€.

// using Iterator
class IteratorClass {
  constructor(data) {
    this.index = 0;
    this.data = data;
  }

  [Symbol.iterator]() {
    return {
      next: () => {
        if (this.index < this.data.length) {
          return { value: this.data[this.index++], done: false };
        } else {
          this.index = 0; // to reset iteration status
          return { done: true };
        }
      },
    };
  }
}

// using Generator
function* iteratorUsingGenerator(collection) {
  var nextIndex = 0;

  while (nextIndex < collection.length) {
    yield collection[nextIndex++];
  }
}

// usage
const gen = iteratorUsingGenerator(['Hi', 'Hello', 'Bye']);

console.log(gen.next().value); // 'Hi'
console.log(gen.next().value); // 'Hello'
console.log(gen.next().value); // 'Bye'

μ€‘μž¬μž νŒ¨ν„΄ (Mediator Pattern)

μ€‘μž¬μž νŒ¨ν„΄μ€ 일련의 객체가 μ„œλ‘œ μƒν˜Έ μž‘μš©ν•˜λŠ” 방식을 μΊ‘μŠν™”ν•˜λŠ” 행동 λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€. λŠμŠ¨ν•œ 결합을 μ΄‰μ§„ν•˜μ—¬ 객체가 μ„œλ‘œ λͺ…μ‹œμ μœΌλ‘œ μ°Έμ‘°λ˜μ§€ μ•Šλ„λ‘ν•˜μ—¬ 객체 그룹에 λŒ€ν•œ 쀑앙 κΆŒν•œμ„ μ œκ³΅ν•©λ‹ˆλ‹€.

이 μ˜ˆμ—μ„œλŠ” Airplane 객체가 μ„œλ‘œ μƒν˜Έ μž‘μš©ν•˜λŠ” 방식을 μ œμ–΄ν•˜λŠ” β€‹β€‹μ€‘μž¬μžλ‘œ TrafficTowerκ°€ μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  Airplane κ°μ²΄λŠ” TrafficTower 객체에 λ“±λ‘λ˜λ©°, Airplane 객체가 λ‹€λ₯Έ λͺ¨λ“  Airplane 객체의 μ’Œν‘œ 데이터λ₯Όλ°›λŠ” 방법을 μ²˜λ¦¬ν•˜λŠ” μ€‘μž¬μž 클래슀 객체이닀.

class TrafficTower {
  constructor() {
    this._airplanes = [];
  }

  register(airplane) {
    this._airplanes.push(airplane);
    airplane.register(this);
  }

  requestCoordinates(airplane) {
    return this._airplanes.filter(plane => airplane !== plane).map(plane => plane.coordinates);
  }
}

class Airplane {
  constructor(coordinates) {
    this.coordinates = coordinates;
    this.trafficTower = null;
  }

  register(trafficTower) {
    this.trafficTower = trafficTower;
  }

  requestCoordinates() {
    if (this.trafficTower) return this.trafficTower.requestCoordinates(this);
    return null;
  }
}

// usage
const tower = new TrafficTower();

const airplanes = [new Airplane(10), new Airplane(20), new Airplane(30)];
airplanes.forEach(airplane => {
  tower.register(airplane);
});

console.log(airplanes.map(airplane => airplane.requestCoordinates())) 
// [20, 30], [10, 30], [10, 20](/Lee-hyuna/33-js-concepts-kr/wiki/20,-30],-[10,-30],-[10,-20)

κ΄€μ°°μž νŒ¨ν„΄ (Observer Pattern)

ν•˜λ‚˜μ˜ 객체(publisher)κ°€ μƒνƒœλ₯Ό λ³€κ²½ν•  λ•Œ λ‹€λ₯Έ λͺ¨λ“  쒅속 개체(subscribers)μ—κ²Œ μžλ™μœΌλ‘œ μ•Œλ¦¬κ³  μ—…λ°μ΄νŠΈ λ˜λ„λ‘ 객체 κ°„μ˜ μΌλŒ€λ‹€ 쒅속성을 μ •μ˜ν•˜λŠ” μ€‘μš”ν•œ λ™μž‘ λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. 이것을 PubSub (publisher / subscribers) λ˜λŠ” 이벀트 λ””μŠ€νŒ¨μ²˜ / λ¦¬μŠ€λ„ˆ νŒ¨ν„΄μ΄λΌκ³ λ„ ν•œλ‹€. publishersλŠ” λ•Œλ•Œλ‘œ μ£Όμ œλΌκ³ ν•˜λ©° subscribersλŠ” λ•Œλ•Œλ‘œ κ΄€μ°°μžλΌκ³ ν•©λ‹ˆλ‹€.

addEventListenerλ˜λŠ” jQueryλ₯Ό μ‚¬μš©ν–ˆλ‹€λ©΄ 이미이 νŒ¨ν„΄μ— μ–΄λŠ 정도 μ΅μˆ™ ν•  것이닀.

이 μ˜ˆμ—μ„œλŠ” subscriber μ»¬λ ‰μ…˜μ—μ„œ Observer 클래슀의 객체λ₯Ό μΆ”κ°€ν•˜κ³  μ œκ±°ν•˜λŠ” λ©”μ„œλ“œκ°€ μžˆλŠ” κ°„λ‹¨ν•œ Subject 클래슀λ₯Ό λ§Œλ“ λ‹€. λ˜ν•œ Subject 클래슀 객체의 λ³€κ²½ 사항을 ꡬ독 ν•œObserver에 μ „νŒŒν•˜λŠ” fire λ©”μ„œλ“œκ°€ μžˆλ‹€. 반면, Observer ν΄λž˜μŠ€λŠ” λ‚΄λΆ€ μƒνƒœμ™€ κ΅¬λ…ν•œ Subjectμ—μ„œ μ „νŒŒλœ 변경에 따라 λ‚΄λΆ€ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” λ©”μ„œλ“œλ₯Ό κ°–λŠ”λ‹€.

class Subject {
  constructor() {
    this._observers = [];
  }

  subscribe(observer) {
    this._observers.push(observer);
  }

  unsubscribe(observer) {
    this._observers = this._observers.filter(obs => observer !== obs);
  }

  fire(change) {
    this._observers.forEach(observer => {
      observer.update(change);
    });
  }
}

class Observer {
  constructor(state) {
    this.state = state;
    this.initialState = state;
  }

  update(change) {
    let state = this.state;
    switch (change) {
      case 'INC':
        this.state = ++state;
        break;
      case 'DEC':
        this.state = --state;
        break;
      default:
        this.state = this.initialState;
    }
  }
}

// usage
const sub = new Subject();

const obs1 = new Observer(1);
const obs2 = new Observer(19);

sub.subscribe(obs1);
sub.subscribe(obs2);

sub.fire('INC');

console.log(obs1.state); // 2
console.log(obs2.state); // 20

μƒνƒœ νŒ¨ν„΄ (State Pattern)

λ‚΄λΆ€ μƒνƒœ 변경에 따라 객체의 λ™μž‘μ„ λ³€κ²½ν•  수 μžˆλŠ” 행동 λ””μžμΈ νŒ¨ν„΄μ•„λ‹€. μƒνƒœ νŒ¨ν„΄ ν΄λž˜μŠ€κ°€ λ°˜ν™˜ ν•œ κ°μ²΄λŠ” 클래슀λ₯Ό λ³€κ²½ν•˜λŠ” κ²ƒμœΌλ‘œ 인닀. 각 객체 μœ ν˜•μ΄ νŠΉμ • μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ œν•œλœ 객체 μ„ΈνŠΈμ— μƒνƒœ 별 λ‘œμ§μ„ μ œκ³΅ν•œλ‹€.

이 νŒ¨ν„΄μ„ μ΄ν•΄ν•˜κΈ° μœ„ν•΄ μ‹ ν˜Έλ“±μ˜ κ°„λ‹¨ν•œ 예λ₯Ό λ“€μ–΄λ³΄μž. TrafficLight ν΄λž˜μŠ€λŠ” λ‚΄λΆ€ μƒνƒœ (Red, Yellow λ˜λŠ” Green 클래슀의 객체)λ₯Ό κΈ°μ€€μœΌλ‘œ λ°˜ν™˜ν•˜λŠ” 객체λ₯Ό λ³€κ²½ν•œλ‹€.

class TrafficLight {
  constructor() {
    this.states = [new GreenLight(), new RedLight(), new YellowLight()];
    this.current = this.states[0];
  }

  change() {
    const totalStates = this.states.length;
    let currentIndex = this.states.findIndex(light => light === this.current);
    if (currentIndex + 1 < totalStates) this.current = this.states[currentIndex + 1];
    else this.current = this.states[0];
  }

  sign() {
    return this.current.sign();
  }
}

class Light {
  constructor(light) {
    this.light = light;
  }
}

class RedLight extends Light {
  constructor() {
    super('red');
  }

  sign() {
    return 'STOP';
  }
}

class YellowLight extends Light {
  constructor() {
    super('yellow');
  }

  sign() {
    return 'STEADY';
  }
}

class GreenLight extends Light {
	constructor() {
		super('green');
	}

	sign() {
		return 'GO';
	}
}

// usage
const trafficLight = new TrafficLight();

console.log(trafficLight.sign()); // 'GO'
trafficLight.change();

console.log(trafficLight.sign()); // 'STOP'
trafficLight.change();

console.log(trafficLight.sign()); // 'STEADY'
trafficLight.change();

console.log(trafficLight.sign()); // 'GO'
trafficLight.change();

console.log(trafficLight.sign()); // 'STOP'

μ „λž΅ νŒ¨ν„΄ (Strategy Pattern)

νŠΉμ • μž‘μ—…μ— λŒ€ν•œ λŒ€μ²΄ μ•Œκ³ λ¦¬μ¦˜μ„ μΊ‘μŠν™” ν•  수 μžˆλŠ” 행동 섀계 νŒ¨ν„΄μ΄λ‹€. λ˜ν•œ μ•Œκ³ λ¦¬μ¦˜ μ œν’ˆκ΅°μ„ μ •μ˜ν•˜κ³  ν΄λΌμ΄μ–ΈνŠΈ κ°„μ„­μ΄λ‚˜ 지식없이 λŸ°νƒ€μž„μ— μƒν˜Έ κ΅ν™˜ ν•  μˆ˜μžˆλŠ” λ°©μ‹μœΌλ‘œ μ•Œκ³ λ¦¬μ¦˜μ„ μΊ‘μŠν™”ν•œλ‹€.

μ•„λž˜ μ˜ˆμ—μ„œλŠ” μΆœκ·Όμ„ μœ„ν•œ λͺ¨λ“  μ „λž΅μ„ μΊ‘μŠν™”ν•˜κΈ° μœ„ν•΄ Commute 클래슀λ₯Ό λ§Œλ“ λ‹€. 그런 λ‹€μŒ Bus , PersonalCar 및 Taxi의 μ„Έ 가지 μ „λž΅μ„ μ •μ˜ν•œλ‹€. 이 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ—¬ λŸ°νƒ€μž„μ— Commute 객체의 trave λ©”μ„œλ“œμ— μ‚¬μš©ν•˜λ„λ‘ κ΅¬ν˜„μ„ λ°”κΏ€ 수 μžˆλ‹€.

// encapsulation
class Commute {
  travel(transport) {
    return transport.travelTime();
  }
}

class Vehicle {
  travelTime() {
    return this._timeTaken;
  }
}

// strategy 1
class Bus extends Vehicle {
  constructor() {
    super();
    this._timeTaken = 10;
  }
}

// strategy 2
class Taxi extends Vehicle {
  constructor() {
    super();
    this._timeTaken = 5;
  }
}

// strategy 3
class PersonalCar extends Vehicle {
  constructor() {
    super();
    this._timeTaken = 3;
  }
}

// usage
const commute = new Commute();

console.log(commute.travel(new Taxi())); // 5
console.log(commute.travel(new Bus())); // 10

ν…œν”Œλ¦Ώ νŒ¨ν„΄ (Template Pattern)

μ΄λŠ” μ•Œκ³ λ¦¬μ¦˜μ˜ 골격 λ˜λŠ” μž‘μ—… κ΅¬ν˜„μ„ μ •μ˜ν•˜μ§€λ§Œ 일뢀 단계λ₯Ό μ„œλΈŒ 클래슀둜 μ—°κΈ°ν•˜λŠ” λ™μž‘ 기반 λ””μžμΈ νŒ¨ν„΄μ΄λ‹€. μ„œλΈŒ ν΄λž˜μŠ€λŠ” μ•Œκ³ λ¦¬μ¦˜μ˜ μ™ΈλΆ€ ꡬ쑰λ₯Ό λ³€κ²½ν•˜μ§€ μ•Šκ³  μ•Œκ³ λ¦¬μ¦˜μ˜ νŠΉμ • 단계λ₯Ό μž¬μ •μ˜ ν•  수 μžˆλ‹€.

이 μ˜ˆμ œμ—λŠ” work λ©”μ„œλ“œλ₯Ό λΆ€λΆ„μ μœΌλ‘œ κ΅¬ν˜„ν•˜λŠ” Template 클래슀 Employeeκ°€ μžˆλ‹€. μ„œλΈŒ ν΄λž˜μŠ€κ°€ μ „μ²΄μ μœΌλ‘œ μž‘λ™ν•˜λ„λ‘ λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Όν•œλ‹€. 그런 λ‹€μŒ ν…œν”Œλ¦Ώ 클래슀λ₯Ό ν™•μž₯ν•˜κ³  κ΅¬ν˜„ 격차λ₯Ό λ©”μš°κΈ° μœ„ν•΄ ν•„μš”ν•œ λ©”μ†Œλ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” 두 개의 μ„œλΈŒ 클래슀 인 Developer및 Testerλ₯Ό λ§Œλ“ λ‹€.

class Employee {
  constructor(name, salary) {
    this._name = name;
    this._salary = salary;
  }

  work() {
    return `${this._name} handles ${this.responsibilities() /* gap to be filled by subclass */}`;
  }

  getPaid() {
    return `${this._name} got paid ${this._salary}`;
  }
}

class Developer extends Employee {
  constructor(name, salary) {
    super(name, salary);
  }

  // details handled by subclass
  responsibilities() {
    return 'application development';
  }
}

class Tester extends Employee {
  constructor(name, salary) {
    super(name, salary);
  }

  // details handled by subclass
  responsibilities() {
    return 'testing';
  }
}

// usage
const dev = new Developer('Nathan', 100000);
console.log(dev.getPaid()); // 'Nathan got paid 100000'
console.log(dev.work()); // 'Nathan handles application development'

const tester = new Tester('Brian', 90000);
console.log(tester.getPaid()); // 'Brian got paid 90000'
console.log(tester.work()); // 'Brian handles testing'

κ²°λ‘ 

λ””μžμΈ νŒ¨ν„΄μ€ μ†Œν”„νŠΈμ›¨μ–΄ μ—”μ§€λ‹ˆμ–΄λ§μ— μ€‘μš”ν•˜λ©° 일반적인 문제λ₯Ό ν•΄κ²°ν•˜λŠ” 데 맀우 도움이 될 수 μžˆλ‹€. κ·ΈλŸ¬λ‚˜ 이것은 맀우 κ΄‘λ²”μœ„ν•œ 주제이며, λ””μžμΈ νŒ¨ν„΄μ— κ΄€ν•œ λͺ¨λ“  것을 이 글에 ν¬ν•¨μ‹œν‚€λŠ” 것은 λΆˆκ°€λŠ₯ν–ˆλ‹€. λ”°λΌμ„œ λͺ¨λ˜ JavaScriptλ₯Ό μž‘μ„±ν•˜λŠ” 데 μ‹€μ œλ‘œ 도움이 될 μˆ˜μžˆλŠ” ν•­λͺ©μ— λŒ€ν•΄ 짧고 κ°„κ²°ν•˜κ²Œ μ΄μ•ΌκΈ°ν•˜λ„λ‘ κ³¨λžλ‹€. 더 μžμ„Ένžˆ μ•Œμ•„ 보렀면 μ•„λž˜μ˜ 책듀을 μ‚΄νŽ΄λ³΄κΈΈ λ°”λž€λ‹€.

  1. λ””μžμΈ νŒ¨ν„΄ : μž¬μ‚¬μš© κ°€λŠ₯ν•œ 객체 지ν–₯ μ†Œν”„νŠΈμ›¨μ–΄μ˜ μš”μ†Œ by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides (Gang of Four)
  2. μžλ°” 슀크립트 λ””μžμΈ νŒ¨ν„΄ μ•Œμ•„λ³΄κΈ° by Addy Osmani
  3. μžλ°” 슀크립트 νŒ¨ν„΄ by Stoyan Stefanov