Web/JS

[JS] 디자인 패턴(Design Pattern)_모듈 패턴(Module Pattern)

메바동 2023. 2. 16. 00:05
728x90

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

 


 

전에 디자인 패턴을 공부하고자, 재난 지원금으로 Head First Design Patterns 책을 구입했었다.

알 수 없는 외국 감성과 아직 디자인 패턴이 필요하지 않다고 생각했던(참으로 어리석은 생각) 나는 구입만 하고 책을 책장에 고이고이 모셔 두었다.

이제는 개발(이라고는 하지만 내가 제대로 하고 있는 건지 모르겠다.) 일을 한 지 만 3년이 다 되어가 진짜로 디자인 패턴에 대해 알아야 할 때라는 생각이 들었다.

하지만 개정판은 절대 나올 것 같지 않았던 헤드 퍼스트 디자인 패턴의 개정판이 나와 있었고, 디자인이 훨씬 깔끔했다. 알 수 없는 외국 감성인 위에서 바라보는 앵글 표지만큼은 변하지 않았지만...

 

아무튼 이런 이유로 헤드 퍼스트 디자인 패턴에 손이 가지 않았고, ChatGPT를 이용해 자바스크립트 디자인 패턴이나 배워보자는 생각이 들어 ChatGPT와 DeepL을 이용하여 자바스크립트 디자인 패턴을 배워보기로 하였다.

 


 

1. Module pattern (모듈 패턴)

모듈 패턴은 코드를 비공개 및 공용 멤버가 있는 재사용 가능한 모듈로 캡슐화하는 방법을 제공하는 JavaScript의 디자인 패턴이다.

모듈 패턴의 기본 개념은 자체 범위가 있는 독립적인 코드 단위를 만들어 전역 네임스페이스 오염과 잠재적인 명명 충돌을 방지하는 것이다.

 

모듈 패턴에서 모듈은 모듈의 공용(public) 멤버를 포함하는 객체를 반환하는 함수로 정의된다. 이 함수는 모듈의 비공개 상태를 캡슐화하기 위해 클로저(closure)를 생성한다.

 

다음은 카운터를 정의하는 간단한 모듈의 예이다.

 

const CounterModule = (function() {
  // 비공개 멤버
  let count = 0;

  function increment() {
    count++;
  }

  function decrement() {
    count--;
  }

  // 공개 멤버
  return {
    getCount: function() {
      return count;
    },
    increment,
    decrement
  };
})();

 

이 예제에서 CounterModule 함수는 모듈의 공용 멤버를 포함하는 객체를 반환하는 즉시 호출 함수 표현식(IIFE, Immediately Invoked Function Expression)으로 정의된다.

모듈의 비공개 멤버는 let 키워드를 사용하여 함수 내부에 정의되며 함수 외부에서 접근할 수 없다.

모듈의 공용 멤버는 반환된 객체의 메서드로 정의되며, 클로저를 통해 비공개 멤버에 접근할 수 있다.

 

카운터 모듈은 해당 공용 메서드를 호출하여 사용할 수 있다.

 

CounterModule.increment();
console.log(CounterModule.getCount()); // 출력: 1
CounterModule.decrement();
console.log(CounterModule.getCount()); // 출력: 0

 

 

 

 

2. Module Pattern에 관한 퀴즈

ChatGPT에게 "Give me a quiz to practice the Module Pattern in JavaScript."라고 질문한 뒤 나온 5가지 질문과 그에 대한 답변이다.

 

1. 자바스크립트에서 모듈 패턴이란 무엇인가요?

  • 모듈 패턴은 개발자가 비공개 및 공용 멤버를 가질 수 있는 독립된 모듈로 코드를 캡슐화하고 구성할 수 있도록 하는 JavaScript의 디자인 패턴이다.

 

2. 모듈 패턴을 사용하면 어떤 이점이 있나요?

  • 모듈 패턴은 다음과 같은 몇 가지 이점을 제공한다.
    • 비공개 변수와 함수를 캡슐화하여 명명 충돌을 방지하고 코드 유지 관리성을 개선할 수 있다.
    • 모듈을 여러 프로젝트와 애플리케이션에서 사용할 수 있으므로 코드 구성 및 재사용성이 향상된다.
    • 전역 변수의 사용이 줄어들어 전역 네임스페이스가 오염되는 것을 방지하고 원치 않는 부작용을 방지할 수 있다.

 

3. 모듈 패턴에서 비공개 및 공개 멤버를 만들려면 어떻게 해야 하나요?

  • 모듈 패턴에서는 클로저를 사용하여 비공개 멤버를 생성할 수 있으며, 이를 통해 모듈 외부에서 접근할 수 없는 변수와 함수를 정의할 수 있다. 공용 멤버는 노출하려는 공용 메서드와 프로퍼티가 포함된 객체를 반환하여 생성할 수 있다.

 

4. 모듈 패턴에서 명명된 함수와 익명 함수의 차이점은 무엇인가요?

  • 모듈 패턴에서 명명된 함수 또는 익명 함수를 사용하여 모듈을 정의할 수 있다.
    명명된 함수는 디버깅 및 스택 추적에 유용하며, 익명 함수는 코드를 더 간결하고 읽기 쉽게 유지하는데 도움이 될 수 있다.

 

5. 모듈 패턴에서 즉시 호출된 함수 표현식(IIFE)과 명명된 함수의 차이점은 무엇인가요?

  • 모듈 패턴에서 IIFE는 정의된 후 즉시 호출되는 익명 함수인 반면, 명명된 함수는 나중에 호출할 수 있다.
    IIFE는 여러 프로젝트와 애플리케이션에서 사용할 수 있는 독립된 모듈을 만드는 데 자주 사용된다.
  • IIFE를 사용하면 하나의 인스턴스를 공유하는 싱글톤으로 사용할 수 있는 반면, 명명된 함수를 사용하면 함수를 호출하여 여러 개의 인스턴스를 만들 수 있다.

 

 

 

3. Module Pattern 연습 코드

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

 

 

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

const shoppingCartModule = (function() {
  // Private variables and functions
  // ...

  // Public API
  return {
    addItem: function(name, price) {
      // ...
    },
    removeItem: function(name) {
      // ...
    },
    getTotalCost: function() {
      // ...
    }
  };
})();

 

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

 

const shoppingCartModule = (function () {
  let items = [];

  const findItemIndex = (name) => items.findIndex((item) => item.name === name);

  const addItem = (name, price) => {
    const index = findItemIndex(name);

    if (index !== -1) {
      items[index].quantity++;
    } else {
      items = [...items, { name, price, quantity: 1 }];
    }
  };

  const removeItem = (name) => {
    items = items.filter((item) => item.name !== name);
  };

  const getTotalCost = () =>
    items.reduce((total, item) => total + item.price * item.quantity, 0);

  // Public API
  return { addItem, removeItem, getTotalCost };
})();

shoppingCartModule.addItem('Apple', 1.99);
shoppingCartModule.addItem('Banana', 0.99);
shoppingCartModule.addItem('Apple', 1.99);
console.log(shoppingCartModule.getTotalCost()); // Output: 4.97

shoppingCartModule.removeItem('Banana');
console.log(shoppingCartModule.getTotalCost()); // Output: 3.98

 

위 코드는 공개 모듈 패턴(Revealing Module Pattern)에 더 가깝기 때문에 주어진 스켈레톤 코드에 맞춰

 

const shoppingCartModule = (function () {
  let items = [];

  const findItemIndex = (name) => items.findIndex((item) => item.name === name);

  // Public API
  return {     
    addItem: function(name, price) {
      const index = findItemIndex(name);

      if (index !== -1) {
        items[index].quantity++;
      } else {
        items = [...items, { name, price, quantity: 1 }];
      }
    },
    removeItem: function(name) {
      items = items.filter((item) => item.name !== name);
    },
    getTotalCost: function() {
      return items.reduce((total, item) => total + item.price * item.quantity, 0);
    } };
})();

shoppingCartModule.addItem('Apple', 1.99);
shoppingCartModule.addItem('Banana', 0.99);
shoppingCartModule.addItem('Apple', 1.99);
console.log(shoppingCartModule.getTotalCost()); // Output: 4.97

shoppingCartModule.removeItem('Banana');
console.log(shoppingCartModule.getTotalCost()); // Output: 3.98

 

위와 같이 하는 것이 기본 모듈 패턴에 더 맞는 것 같다.

728x90