dev.toTim Winfred (They/Them) 님이 작성한 Design Patterns In JavaScript 번역 자료입니다. 번역에 문제가 있다면 댓글 달아주시구요. 원문을 보시기를 추천드립니다

최근 제 직업에서 어떤 디자인 패턴을 사용하는지 개발자 친구로부터 질문을 받았습니다. 사용한 패턴이 분명히 있는 것을 알고 있음에도 정답을 몰랐기 때문에 질문이 조금 당황스러웠습니다. 그 패턴에 이름이 있나요? 그리고 왜 내가 사용해 온 패턴이 다른 것보다 더 좋은 것일까요?

오늘의 포스팅에서는 다양한 디자인 패턴과 이를 자바스크립트로 구현하는 방법을 배우면서 느낀 점과 생각을 적어보도록 하겠습니다. 이 게시물의 여행에 자유롭게 참여하거나 이미 배웠어야 할 것 같은 것을 발견했을 때 제 멍청한 짓(noob-ness)을 놀리세요. (늦은 것이 안 하는 것보다 낫지?)

참고: 이 게시물의 주요 리소스 중 하나는 Lambda Test의 블로그 게시물이었습니다.

디자인 패턴이란 무엇인가요?

Wikipedia에 따르면 소프트웨어 디자인 패턴은…:

소프트웨어 설계의 주어진 컨텍스트 내에서 일반적으로 발생하는 문제에 대한 일반적이고 재사용 가능한 솔루션입니다. 소스나 기계어로 직접 변환할 수 있는 완성된 디자인이 아닙니다. 오히려 다양한 상황에서 사용할 수 있는 문제를 해결하는 방법에 대한 설명 또는 템플릿입니다. 디자인 패턴은 프로그래머가 응용 프로그램이나 시스템을 디자인할 때 일반적인 문제를 해결하는 데 사용할 수 있는 공식화된 모범 사례입니다.

Lambda Test 포스트 또한 “시간이 지나면서 객체 지향 소프트웨어 개발자가 채택한 검증된(time-tested) 솔루션과 모범 사례를 나타냅니다”라고 말합니다.

2021 JavaScript 디자인 패턴

JavaScript Design Patterns Graphic - a visual of the above information

Source

Lambda Test에 따르면 JavaScript에서 모든 패턴을 구현하는데 필요한 기본 기능이 있기 때문에 모든 패턴이 필요한 것은 아닙니다. 따라서 이 게시물에서는 선택한 디자인 패턴 그룹에 대해서만 논의할 것입니다. 내가 건너뛴 것을 추가하고 싶다면 언제든지 댓글을 남겨주세요.

참고: 아래 인용된 모든 정의는 Wikipedia의 Software Design Patterns 항목에서 가져온 것입니다.

Constructor / Builder Pattern (생성자 / 빌더 패턴) - 생성 디자인 (Creational Design)

복잡한 오브젝트의 구성(construction)을 해당 표현과 분리하여 동일한 구성 프로세스를 통해 다양한 표현을 생성할 수 있습니다.

OK. 이전에 생성자 디자인 패턴을 사용하여 JavaScript 코드를 작성한 적이 있습니다! 나는 그것이 JavaScript의 클래스 기능이라고 생각했던 것 같습니다. 나는 그것이 “디자인 패턴”으로 간주된다는 것을 결코 알지 못했습니다.

class Person {
  constructor(name, age, mother) {
    this.name = name;
    this.age = age;
    this.mother = mother;
  }
}

const tim = new Person('Tim', 31, null);
const tina = new Person('Tina', 57, null);

tim.mother = tina;

console.log(tim);

const grandma = new Person('Sherry', 80, null);

tina.mother = grandma;

console.log(tim);

Factory Pattern - 생성 디자인 (Creational Design)

단일 오브젝트를 만들기 위한 인터페이스를 정의하지만 서브 클래스가 인스턴스화(instantiate)할 클래스를 결정하도록 합니다. 팩토리 메서드를 사용하면 클래스가 서브 클래스에 대한 인스턴스화(instantiation)를 연기할 수 있습니다.

Lambda Test 포스트에서 패턴을 구현한 방식을 보면 이름이 딱 맞는 것 같습니다. 문자 그대로 적절한 오브젝트를 반환하기 위해 다양한 인수를 사용할 수 있는 단일 함수를 설정하는 패턴입니다. 의류 공장에 셔츠를 달라고 하면 셔츠를 주는 것과 같습니다. 바지를 달라고 하면 바지를 줍니다. 신발을 달라고 하면 신발을 줍니다. 그리고 각각의 오브젝트들은 고유의 기능을 가지고 있습니다.

function animalFactory() {
  this.createAnimal = function(animalType) {
    let animal;

    switch(animalType) {
      case 'dog':
        animal = new Dog();
        break;
      case 'cat':
        animal = new Cat();
        break;
      case 'horse':
        animal = new Horse();
        break;
      default:
        animal = new Monkey();
        break;
    }

    return animal;
  }
}

const Dog = function() {
  this.makeSound = () => {
    console.log('woof woof!');
  }
}

const Cat = function() {
  this.makeSound = () => {
    console.log('prrr prrr meow!');
  }
}

const Horse = function() {
  this.makeSound = () => {
    console.log('neeeighhh!')
  }
}

const Monkey = function() {
  this.makeSound = () => {
    console.log('ooooh ahh oooh oooh!');
  }
}

const factory = new animalFactory();

const jojo = factory.createAnimal('dog');
jojo.makeSound();

const smokey = factory.createAnimal('cat');
smokey.makeSound();

const princess = factory.createAnimal('horse');
princess.makeSound();

const kong = factory.createAnimal('monkey');
kong.makeSound();

Prototype Pattern - 생성 디자인 (Creational Design)

프로토타입 인스턴스를 사용하여 생성할 오브젝트의 종류를 지정하고 기존 오브젝트의 ‘뼈대(skeleton)’에서 새 오브젝트를 생성하여 성능을 높이고 메모리 공간(footprints)을 최소화합니다.

프로토타이핑을 많이 다뤄본적이 없었고 JavaScript의 멋진 기능이라는 것을 알고 있기 때문에 더 자세히 살펴볼 수 있어서 흥분됩니다. 솔직히 말해서, 이것은 Lambda Test 포스트의 섹션을 다시 읽고 이것이 모두 복제에 관한 것이라는 것을 깨닫기 전까지 저를 많이 혼란스럽게 했습니다. 일단 아래 구현을 시도해보니 그것을 알아낼 수 있었고 정말로 이해할 수 있었습니다.

const macBook = {
  color: 'silver',
  turnOn() {
    console.log('turning on...');
  },
  turnOff() {
    console.log('turning off...');
  }
}

// Proper prototype cloning
const myComputer = Object.create(macBook, { owner: { value: 'Tim'} });
console.log(myComputer.__proto__ === macBook);

// Not a prototype copy
const newComputer = {...macBook, owner: 'John'};
console.log(newComputer.__proto__ === macBook);

macBook.power = 'USB-C';

// The protoype gets the new value for 'power'
console.log(myComputer.power);
// But the non-prototype doesn't
console.log(newComputer.power);

Singleton Pattern / Strict Pattern - 생성 디자인 (Creational Design)

클래스에 인스턴스가 하나만 있는지 확인하고 이에 대한 전역 액세스 포인트를 제공합니다.

이것은 하나의 인스턴스만 존재하는지 확인합니다. 다른 인스턴스를 만드려고 시도하면 이미 존재하는 참조(reference)를 반환합니다. 쉽게 이해되시죠.

const Database = (function () {
  let instance;

  function createDatabaseInstance() {
    return new Object('Database Instance');
  }

  function getDatabaseInstance() {
    if (!instance) {
      instance = createDatabaseInstance();
    }

    return instance;
  }

  return { getDatabaseInstance }
})();

const databaseInstance1 = Database.getDatabaseInstance();
const databaseInstance2 = Database.getDatabaseInstance();

console.log(databaseInstance1 === databaseInstance2);

Adapter Pattern / Wrapper Pattern - 구조 디자인 (Structural Design)

클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환합니다. 어댑터를 사용하면 호환되지 않는 인터페이스로 인해 할 수 없었던 클래스가 함께 작동할 수 있습니다. 엔터프라이즈 통합 패턴에 해당하는 것은 번역기(translator)입니다.

1년 동안 해외에서 살았기 때문에 어댑터의 개념은 이해하기 쉽습니다. 레거시 코드의 함수를 코드베이스의 새로운 부분에 있는 함수로 함수를 연결하는 유용한 방법이 될 수 있을 것 같나요?

어댑터 패턴의 사용 사례를 완벽하게 설명하는 정말 멋진 YouTube 동영상을 찾았고 이를 사용하여 두 개의 다른 NPM 라이브러리를 하나의 유틸리티로 결합하는 아래의 구현을 만들 수 있었습니다. 제가 작성한 코드는 이 포스트에 쉽게 복사하여 붙여넣을 수 없으므로 제 Github에서 코드를 자유롭게 체크아웃하세요.

이러한 디자인 패턴의 많은 사용 사례를 확실히 상상할 수 있으며 향후 코드를 보다 쉽게 유지 관리할 수 있도록 돕기 위해 사용하기를 기대합니다! 아마 지금까지 제가 가장 좋아하는 디자인 패턴일 것입니다 =)

Composite Pattern - 구조 디자인 (Structural Design)

오브젝트를 트리 구조로 구성하여 부분-전체(part-whole) 계층 구조를 나타냅니다. 컴포짓을 사용하면 클라이언트가 개별 오브젝트와 오브젝트 컴포지션을 균일하게 처리할 수 있습니다.

그냥 노드와 컴포넌트입니다! 이 디자인 패턴에 대해 확실히 알고 있습니다.: 하나의 부모, 여러 자녀. 그리고 그 자녀는 여러 자녀를 가질 수 있습니다. JSON과 HTML 트리에서 사용되는 일대다(one-to-many) 패턴입니다. 그리고 이제 디자인 패턴의 공식 이름을 알게 되었습니다 =)

const Node = function(name) {
  this.children = [];
  this.name = name;
};

Node.prototype = {
  add: function(child) {
    this.children.push(child);
    return this;
  }
}

// recursive console log of what's inside of the tree
const log = (root) => {
  if (!root) return;

  console.log('');
  console.log(`---Node: ${root.name}---`);
  console.log(root);

  root.children.forEach(child => {
    if(child?.children.length) {
      log(child);
    }
  });
}

const init = () => {
  const tree = new Node('root');
  const [left, right] = [new Node("left"), new Node("right")];
  const [leftleft, leftright] = [new Node("leftleft"), new Node("leftright")];
  const [rightleft, rightright] = [new Node("rightleft"), new Node("rightright")];

  tree.add(left).add(right);
  left.add(leftleft).add(leftright);
  right.add(rightleft).add(rightright);

  log(tree);
}

init();

Module Pattern - 구조 디자인 (Structural Design)

전역적으로 사용되는 클래스, 싱글톤, 메서드와 같은 여러 관련 요소를 단일 개념 엔터티로 그룹화합니다.

이 디자인 패턴은 내가 Java를 배웠을 때를 생각나게 합니다. 필요 함수와 변수만 노출하면서 함수에 프라이빗 함수와 변수를 제공하는 것입니다. 저는 확실히 제 경력에서 이 디자인 패턴을 여러 번 사용했습니다. 이름이 있다니 다행이군…

const userApi = () => {
  // private variables
  const users = [];

  // private function
  const addUser = (name) => {
    users.push(name);

    return users[users.length -1];
  }

  // private function
  const getAllUsers = () => {
    return users;
  }

  // private function
  const deleteUser = (name) => {
    const userIndex = users.indexOf(name);

    if (userIndex < 0) {
      throw new Error('User not found');
    }

    users.splice(userIndex, 1);
  }

  // private function
  const updateUser = (name, newName) => {
    const userIndex = users.indexOf(name);

    if (userIndex < 0) {
      throw new Error('User not found');
    }

    users[userIndex] = newName;

    return users[userIndex];
  }

  return {
    // public functions
    add: addUser,
    get: getAllUsers,
    del: deleteUser,
    put: updateUser
  }
}

const api = userApi();

api.add('Tim');
api.add('Hina');
api.add('Yasmeen');
api.add('Neeloo');

console.log(api.get());

api.del('Yasmeen');

console.log(api.get());

api.put('Tim', 'Tim Winfred');

console.log(api.get());

Decorator Pattern - 구조 디자인 (Structural Design)

동일한 인터페이스를 동적으로 유지하는 오브젝트에 추가 책임을 부여합니다. 데코레이터는 기능 확장을 위해 서브클래싱에 대한 유연한 대안을 제공합니다.

좋아요, 이것은 재미있고 이해하기 쉽습니다. 오브젝트에 공통 기능과 특성을 부여하는 기능을 설정할 수 있습니다. 그러면 해당 오브젝트의 각 개별 인스턴스를 고유한 기능과 특성으로 “장식(decorate)” 할 수 있습니다. 저는 미래 어느 시점에 이것을 사용하는 저 자신을 볼 수 있습니다.

const Animal = function(type) {
  this.type = type || 'dog';
}

const dog = new Animal('dog');
const cat = new Animal('cat');

dog.bark = function() {
  console.log('woof woof!');
  return this;
}

cat.meow = function() {
  console.log('meow meooooooow!');
  return this;
}

dog.bark();
cat.meow();

Facade Pattern (파사드 패턴) - 구조 디자인 (Structural Design)

서브 시스템의 인터페이스 집합에 통합 인터페이스를 제공합니다. Facade는 서브 시스템을 사용하기 쉽게 만드는 상위 수준 인터페이스를 정의합니다.

저는 사실 파사드 사용에 상당히 익숙합니다. 저는 현재 직장에서 작업 중인 프로젝트 중 하나에서 미리 만들어진 파사드를 활용하고 있습니다. (파사드(facade)라는 이름으로 수입(imported)되기도 하는데 이게 ‘디자인 패턴’인 줄은 몰랐네요.)

JavaScript 파사드 디자인에 관한 이 비디오는 정말 도움이 되었습니다. (그 비디오에서 발표하는 사람은 정말 유용한 비디오를 많이 가지고 있습니다.)

import axios from 'axios';

function getUsers() {
  return facade.get('https://jsonplaceholder.typicode.com/users');
}

function getUserById(id) {
  return facade.get('https://jsonplaceholder.typicode.com/users', { id });
}

const facade = {
  get: function(url, params) {
    return axios({
      url,
      params,
      method: 'GET'
    }).then(res => res.data);
  }
};

async function getData() {
  try {
    console.time('getUsers took');
    const users = await getUsers();
    console.timeEnd('getUsers took');
    console.log(`There are ${users.length} users`);

    console.time('getUserById took');
    const user1 = await getUserById(1);
    console.timeEnd('getUserById took');
    console.log(user1);
  } catch (error) {
    console.log(error);
  }
}

getData();

Proxy Pattern - 구조 디자인 (Structural Design)

다른 오브젝트에 대한 액세스를 제어하기 위해 대리(surrogate) 또는 플레이스홀더를 제공합니다.

Magic: The Gathering 플레이어로서 이것은 이론적으로 의미가 있습니다. 프록시 카드를 대신 사용할 수 있을 때 비싼 카드를 사용하지 마십시오.

저는 프록시 디자인 패턴과 JavaScript에서 캐시를 생성하여 시간을 절약하고 외부 API에 도달해야 하는 횟수를 줄이는 방법에 대한 높은 평가를 받은 YouTube 동영상을 찾았습니다.

// Mock External API
function CryptoCurrencyAPI() {
  this.getValue = function(coin) {
    console.log(`Calling Crypto API to get ${coin} price...`);

    switch(coin.toLowerCase()) {
      case 'bitcoin':
        return 38000;
      case 'ethereum':
        return 2775;
      case 'dogecoin':
        return 0.39;
    }
  }
}

function CryptoCurrencyAPIProxy() {
  this.api = new CryptoCurrencyAPI();
  this.cache = {};

  this.getValue = function(coin) {
    if(!this.cache[coin]) {
      console.log(`The value of ${coin} isn't stored in cache...`);
      this.cache[coin] = this.api.getValue(coin);
    }

    return this.cache[coin];
  }
}

const proxyAPI = new CryptoCurrencyAPIProxy();

console.log(proxyAPI.getValue('Bitcoin'));
console.log(proxyAPI.getValue('Bitcoin'));
console.log(proxyAPI.getValue('Ethereum'));
console.log(proxyAPI.getValue('Ethereum'));
console.log(proxyAPI.getValue('Dogecoin'));
console.log(proxyAPI.getValue('Dogecoin'));

Chain of Responsibility Pattern (책임 사슬 패턴) - 행동 디자인(Behavioral Design)

둘 이상의 오브젝트에 요청을 처리할 수 있는 기회를 제공하여 요청의 발신자를 수신자와 결합하지 않도록 합니다. 수신 오브젝트를 체인으로 연결하고 오브젝트가 처리할 때까지 체인을 따라 요청을 전달합니다.

이 패턴은 제가 이론적으로 확실히 이해하고 있는 패턴 중 하나이며 많은 실제 시나리오에 연결할 수 있지만 Lambda Test 포스트에 나와 있는 샘플 외에 코딩을 위한 구현을 즉시 떠올릴 수는 없습니다.

이것저것 가지고 놀다 보니 저는 이 디자인 패턴이 좋아하게 되고 메소드 체인의 용이함을 즐기게 되었습니다. 제가 만든 것에 대해 어떻게 생각하세요? Wikipedia의 “오브젝트”의 정의를 충족하는지는 잘 모르겠습니다.

const ATM = function() {
  this.withdrawl = function(amount) {
    console.log(`Requesting to withdrawl $${amount.toFixed(2)}`);

    if (amount % 1 !== 0) {
      console.log('Sorry, this ATM can\'t dispense coins. Please request another amount.');
      return;
    }

    const dispenseOutput = {};

    // chain or responsibility function
    function get(bill) {
      dispenseOutput[bill] = Math.floor(amount / bill);
      amount -= dispenseOutput[bill] * bill;

      return { get };
    }

    get(100).get(50).get(20).get(10).get(5).get(1);

    this.dispense(dispenseOutput);
  };

  this.dispense = function(bills) {
    console.log('--- Dispensing cash ---')
    Object.entries(bills).forEach(([key, value]) => {
      console.log(`- Dispensing ${value} $${key} bills`);
    });
  }
};

const myATM = new ATM();

myATM.withdrawl(126.10);
myATM.withdrawl(1287);
myATM.withdrawl(879);

Command Pattern - 행동 디자인(Behavioral Design)

요청을 오브젝트로 캡슐화하여 다른 요청이 있는 클라이언트의 매개 변수화와 요청 대기열 또는 로깅을 허용합니다. 또한 실행 취소 할 수 있는 작업의 지원을 허용합니다.

꽤 쉬워 보입니다. 그리고 저는 커맨드 오브젝트와 리시버(receiver)를 분리하는 것이 정말 좋습니다. 클린 코드를 만듭니다. 사실 최근에 React로 계산기를 만들려고 했었는데 이것이 완벽한 해결책이에요!

const calculationMethods = {
  add: function(x, y) {
    return x + y;
  },
  subtract: function(x, y) {
    return x - y;
  },
  multiply: function(x, y) {
    return x * y;
  },
  divide: function(x, y) {
    return x / y;
  }
};

const calculator = {
  execute: function(method, num1, num2) {
    if (!(method in calculationMethods)) return null;

    return calculationMethods[method](num1, num2);
  }
};

console.log(calculator.execute('add', 1, 2));
console.log(calculator.execute('subtract', 5, 2));
console.log(calculator.execute('multiply', 11, 2));
console.log(calculator.execute('divide', 10, 2));
console.log(calculator.execute('square root', 20));

Observer (Pub/Sub) Pattern - 행동 디자인(Behavioral Design)

한 오브젝트의 상태가 변경되면 모든 종속 항목이 자동으로 알림을 받고 업데이트 되는 오브젝트 간의 일대다(one-to-many) 종속성을 정의합니다.

제 직감적인 생각은 이것이 제가 많이 가지고 놀지 않았던(적어도 잠시 동안) 자바스크립트의 “관찰 가능(observables)”을 생각하게 만든다는 것입니다. 이 패턴의 pub/sub 아이디어는 웹 소켓과 Redux도 생각하게 합니다.

아래 구현에서는 관찰자 패턴에 대한 훌륭한 설명이 있는 freeCodeCamp 비디오의 예를 사용했습니다. 저는 상황을 약간 바꾸고 몇 가지 메소드 체이닝을 구현했습니다.

function Subject() {
  this.observers = [];
}

Subject.prototype = {
  subscribe: function(observer) {
    this.observers.push(observer);

    return this;
  },

  unsubscribe: function(observer) {
    const indexOfObserver = this.observers.indexOf(observer);
    if (indexOfObserver > -1) {
      this.observers.splice(indexOfObserver, 1);
    }

    return this;
  },

  notifyObserver: function(observer) {
    const indexOfObserver = this.observers.indexOf(observer);
    if (indexOfObserver > -1) {
      this.observers[indexOfObserver].notify();
    }

    return this;
  },

  notifyAllObservers: function() {
    this.observers.forEach(observer => {
      observer.notify();
    });

    return this;
  }
}

function Observer(name) {
  this.name = name;
}

Observer.prototype = {
  notify: function() {
    console.log(`Observer ${this.name} has been notified`);
  }
};

const subject = new Subject();

const observer1 = new Observer('user001');
const observer2 = new Observer('user002');
const observer3 = new Observer('user003');
const observer4 = new Observer('user004');
const observer5 = new Observer('user005');

subject.subscribe(observer1).subscribe(observer2).subscribe(observer3).subscribe(observer4).subscribe(observer5);
subject.notifyObserver(observer4);
subject.unsubscribe(observer4);
subject.notifyAllObservers();

Template Method Pattern - 행동 디자인(Behavioral Design)

일부 단계를 서브 클래스로 지연하여 연산에서 알고리즘의 뼈대(skeleton)을 정의합니다. 템플릿 메서드를 사용하면 서브 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다.

좋아요, 이 설명으로는 확실히 이해가 안 가요 하하. 클래스를 확장하고 새 서브 클래스(sub-class)에 새 메서드를 제공하는 것처럼 들리지만 이 항목에 대한 더 나은 설명을 찾을 수 있는지 알아봐야 합니다…

패턴의 정의와 달리 Microsoft의 템플릿 메서드 패턴에 대한 이 설명이 블로그 포스트에서와 같이 (JavaScript를 사용하지 않지만) 무슨 일이 일어나고 있는지 이해하는 데 도움이 되었습니다.

class HouseTemplate {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  buildHouse() {
    this.buildFoundation();
    this.buildPillars();
    this.buildWalls();
    this.buildWindows();
    console.log(`${ this.name } has been built successfully at ${ this.address }`);
  }

  buildFoundation() {
    console.log('Building foundation...');
  }

  buildPillars() {
    throw new Error('You have to build your own pillars');
  }

  buildWalls() {
    throw new Error('You have to build your own walls');
  }

  buildWindows() {
    console.log('Building windows');
  }
}

class WoodenHouse extends HouseTemplate {
  constructor(name, address) {
    super(name, address);
  }

  buildPillars() {
    console.log('Building pillars for a wooden house');
  }

  buildWalls() {
    console.log('Building walls for a wooden house');
  }
}

class BrickHouse extends HouseTemplate {
  constructor(name, address) {
    super(name, address);
  }

  buildPillars() {
    console.log('Building pillars for a brick house');
  }

  buildWalls() {
    console.log('Building walls for a brick house');
  }
}

const woodenHouse = new WoodenHouse('Wooden house', '123 Maple Road');
const brickHouse = new BrickHouse('Brick house', '456 Stone Lane');

woodenHouse.buildHouse();
brickHouse.buildHouse();

Strategy Pattern - 행동 디자인(Behavioral Design)

알고리즘 계열을 정의하고 각각을 캡슐화하고 상호 교환 가능하게 만드십시오. 전략(strategy)을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있습니다.

자, 마지막입니다! 그리고 고맙게도 Lambda Test 포스트 덕분에 매우 간단하게 이해할 수 있습니다. =) DRY 코딩을 허용하는 모든 방법에 감사합니다. 그리고 향후 이를 위한 다양한 구현을 확실히 상상할 수 있습니다.

function Regal() {
  this.getTicketPrice = function(quantity) {
    return quantity * 11.99;
  }
}

function AMC() {
  this.getTicketPrice = function(quantity) {
    return quantity * 10.99;
  }
}

function Cinemark() {
  this.getTicketPrice = function(quantity) {
    return quantity * 9.99;
  }
}

function TicketPrice() {
  this.theaterChain;

  this.setTheaterChain = function(chain) {
    this.theaterChain = chain;
  }

  this.calculate = function(quantity) {
    return this.theaterChain.getTicketPrice(quantity);
  }
}

const regal = new Regal();
const amc = new AMC();
const cinemark = new Cinemark();

const ticketPrice = new TicketPrice();
ticketPrice.setTheaterChain(regal);
console.log(ticketPrice.calculate(2));

ticketPrice.setTheaterChain(amc);
console.log(ticketPrice.calculate(3));

ticketPrice.setTheaterChain(cinemark);
console.log(ticketPrice.calculate(4));

마지막 생각들

(지금은) 이게 다에요!

솔직히 디자인 패턴을 파헤치는 것이 너무 재미있었고 너무 기뻤습니다. JavaScript에서 ES6 구현이 있는 몇 가지 패턴을 건너뛰었지만 전반적으로 많은 것을 배웠습니다.

여기까지 읽으셨다면 제 여정과 코드가 도움이 되었기를 바랍니다. 곧 다시봐요!

내 Github 저장소를 방문하여 위의 모든 코드를 한 곳에서 얻으세요.