The Module Pattern

Modules

모듈은 강력한 응용 프로그램 아키텍처의 필수 요소이며 일반적으로 프로젝트의 코드 단위를 명확하게 분리하여 구성하는 데 도움이 됩니다.

자바 스크립트에는 모듈 구현을 위한 몇 가지 옵션이 있습니다. 여기에는 다음이 포함됩니다.

  • 모듈 패턴
  • 객체 리터럴 표기법
  • AMD 모듈
  • CommonJS 모듈
  • ECMAScript 하모니 모듈

나중에 Modern Modular JavaScript Design Patterns 섹션에서 이 옵션들 중 후자의 세 가지를 탐구할 것이다.

Module 패턴은 부분적으로 객체 리터럴을 기반으로 하기 때문에 먼저 객체 리터럴에 대해 상기해야 할 필요가 있습니다.

Object Literals

객체 리터럴 표기법에서 객체는 중괄호 ({})로 묶인 쉼표로 구분 된 name/value 쌍의 집합으로 설명됩니다. 객체 내부의 이름은 문자열이거나 콜론 뒤에 오는 식별자 일 수 있습니다. 객체의 마지막 name/value 쌍 뒤에 쉼표를 사용하면 오류가 발생할 수 있습니다.

var myObjectLiteral = {

    variableKey: variableValue,

    functionKey: function () {
      // ...
    }
};

객체 리터럴은 new연산자를 사용하여 인스턴스화 할 필요가 없지만 구문이 시작되면 {블록의 시작으로 해석 될 수 있으므로 사용하지 말아야합니다. 객체 외부에서 myModule.property = "someValue"와 같이 할당을 사용하여 새 멤버를 추가 할 수 있습니다.

아래에서 객체 리터럴 표기법을 사용하여 정의 된 모듈의 보다 완벽한 예를 볼 수 있습니다.

var myModule = {

  myProperty: "someValue",

  // object literals can contain properties and methods.
  // e.g we can define a further object for module configuration:
  myConfig: {
    useCaching: true,
    language: "en"
  },

  // a very basic method
  saySomething: function () {
    console.log( "Where in the world is Paul Irish today?" );
  },

  // output a value based on the current configuration
  reportMyConfig: function () {
    console.log( "Caching is: " + ( this.myConfig.useCaching ? "enabled" : "disabled") );
  },

  // override the current configuration
  updateMyConfig: function( newConfig ) {

    if ( typeof newConfig === "object" ) {
      this.myConfig = newConfig;
      console.log( this.myConfig.language );
    }
  }
};

// Outputs: Where in the world is Paul Irish today?
myModule.saySomething();

// Outputs: Caching is: enabled
myModule.reportMyConfig();

// Outputs: fr
myModule.updateMyConfig({
  language: "fr",
  useCaching: false
});

// Outputs: Caching is: disabled
myModule.reportMyConfig();

객체 리터럴을 사용하면 코드를 캡슐화하고 체계화하는 데 도움이 될 수 있으며 이 주제에 더 관심이 있다면 Rebecca Murphey이 이전에 작성한 깊이있는 설명을 참고하기 바랍니다.

즉, 우리가 이 기법을 선택한다면 모듈 패턴에도 똑같이 사용할 수 있습니다. 여전히 객체 리터럴을 사용하지만 Module Pattern에서는 범위 지정 함수의 리턴 값으로 만 사용됩니다.

The Module Pattern

Module 패턴은 원래 전통적인 소프트웨어 엔지니어링에서 클래스에 대한 개인 및 공용 캡슐화를 제공하는 방법으로 정의되었습니다.

JavaScript에서는 Module 패턴을 사용하여 클래스의 개념을 에뮬레이션하여 public/private 메서드와 변수를 단일 객체 안에 포함 할 수 있으므로 특정 영역을 전역 범위에서 보호 할 수 있습니다. 이를통해 함수 새로 추가된 스크립트에 정의 된 다른 함수 기존 페이지의 함수가 충돌 할 가능성을 줄입니다.

Privacy

모듈 패턴은 클로저를 사용하여 "개인 정보", 상태 및 조직을 캡슐화합니다. 이것은 public 및 private 메소드 및 변수의 조합을 랩핑하는 방법을 제공하여 조각을 전역 범위로 누출시키지 않고 실수로 다른 개발자의 인터페이스와 충돌시키지 않게합니다. 이 패턴을 사용하면 클로저에있는 모든 항목을 비공개로 유지하면서 공용 API 만 반환됩니다.

이것은 우리가 응용 프로그램의 다른 부분이 사용하기를 원하는 인터페이스 만 드러내는 동안 heavy 리프팅을하는 로직을 차폐하기위한 깨끗한 솔루션을 제공합니다. 패턴은 즉각적으로 호출되는 함수 식 (IIFE - 자세한 내용은 네임 스페이스 패턴에 대한 섹션 참조)을 사용하여 객체가 반환됩니다.

일부 전통적인 언어와 달리 액세스 한정자가 없기 때문에 JavaScript 내에서 실제로는 "프라이버시"라는 명확한 의미가 없다는 점에 유의해야합니다. 변수는 기술적으로 public 또는 private로 선언 할 수 없으므로 함수 범위를 사용하여이 개념을 구현합니다. Module 패턴 내에서 선언 된 변수 또는 메소드는 클로저 덕분에 모듈 자체에서만 사용할 수 있습니다. 그러나 반환 객체 내에 정의 된 변수 또는 메서드는 모든 사용자가 사용할 수 있습니다.

History

역사적인 관점에서, 모듈 패턴은 원래 2003 년 Richard Cornford를 포함한 많은 사람들이 개발했습니다. 나중에 Douglas Crockford의 강의에서 대중화되었습니다. 또 다른 부분은 야후의 YUI 라이브러리를 사용해 본 적이 있다면 그 일부 기능이 꽤 친숙한 것처럼 보일 수 있으며 그 이유는 모듈 패턴이 구성 요소를 만들 때 YUI에 강한 영향을 미친다는 것입니다.

Examples

자체 포함 된 모듈을 작성하여 Module 패턴의 구현을 살펴 보겠습니다.

var testModule = (function () {

  var counter = 0;

  return {

    incrementCounter: function () {
      return counter++;
    },

    resetCounter: function () {
      console.log( "counter value prior to reset: " + counter );
      counter = 0;
    }
  };

})();

// Usage:

// Increment our counter
testModule.incrementCounter();

// Check the counter value and reset
// Outputs: counter value prior to reset: 1
testModule.resetCounter();

여기서 코드의 다른 부분은 incrementCounter() 또는 resetCounter()의 값을 직접 읽을 수 없습니다. 카운터 변수는 실제로 전역 변수에서 완전히 차폐되어 있으므로 private 변수처럼 작동합니다. 즉, 해당 변수에 액세스 할 수있는 유일한 코드가 두 가지 함수가 되도록 모듈의 클로저 내에 한정됩니다. 우리의 메서드는 효과적으로 네임 스페이스를 사용하므로 코드의 테스트 섹션에서 모든 호출 앞에 모듈의 이름 (예 : "testModule")을 추가해야합니다.

Module 패턴으로 작업 할 때 우리가 시작하기 위해 사용하는 간단한 템플릿을 정의하는 것이 유용 할 수 있습니다. 다음은 네임 스페이스, 공개 변수 및 비공개 변수를 다루는 예제입니다.

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();

아래 예제를 보면이 패턴을 사용하여 구현 된 장바구니를 볼 수 있습니다. 모듈 자체는 basketModule이라는 전역 변수에 완전히 포함되어 있습니다. 모듈의 basket 배열은 비공개로 유지되므로 응용 프로그램의 다른 부분에서 직접 읽을 수 없습니다. 모듈의 클로저와 함께 존재하기 때문에 접근 할 수있는 유일한 메소드는 범위 (예 : addItem(), getItemCount() 등)에 액세스 할 수있는 메소드입니다.

var basketModule = (function () {

  // privates

  var basket = [];

  function doSomethingPrivate() {
    //...
  }

  function doSomethingElsePrivate() {
    //...
  }

  // Return an object exposed to the public
  return {

    // Add items to our basket
    addItem: function( values ) {
      basket.push(values);
    },

    // Get the count of items in the basket
    getItemCount: function () {
      return basket.length;
    },

    // Public alias to a private function
    doSomething: doSomethingPrivate,

    // Get the total value of items in the basket
    getTotal: function () {

      var q = this.getItemCount(),
          p = 0;

      while (q--) {
        p += basket[q].price;
      }

      return p;
    }
  };
})();

모듈 내부에서 우리는 object를 반환한다는 것을 알았을 것입니다. 이것은 다음과 같이 basketModule과 자동으로 상호 작용할 수 있도록 basketModule에 자동으로 할당됩니다.

// basketModule returns an object with a public API we can use

basketModule.addItem({
  item: "bread",
  price: 0.5
});

basketModule.addItem({
  item: "butter",
  price: 0.3
});

// Outputs: 2
console.log( basketModule.getItemCount() );

// Outputs: 0.8
console.log( basketModule.getTotal() );

// However, the following will not work:

// Outputs: undefined
// This is because the basket itself is not exposed as a part of our
// public API
console.log( basketModule.basket );

// This also won't work as it only exists within the scope of our
// basketModule closure, but not in the returned public object
console.log( basket );

위의 메서드는 실제로 basketModule 내부에서 이름 공간으로 지정됩니다.

위의 바구니 모듈의 범위 지정 함수가 모든 함수를 감싸는 방식에 주목하십시오. 그런 다음 호출하여 즉시의 반환 값을 저장합니다. 여기에는 다음과 같은 여러 가지 이점이 있습니다.

  • 우리의 모듈만이 private function과 private member를 자유롭게 사용할 수 있습니다. 페이지의 나머지 부분에 노출되지 않으므로 (내 보낸 API 만 있음) 진정한 사적인 것으로 간주됩니다.
  • 함수가 정상적으로 선언되고 이름이 지정되면 예외를 던진 함수를 발견하려고 할 때 디버거에서 호출 스택을 표시하는 것이 더 쉬울 수 있습니다.
  • T.J Crowder는 과거에 지적했듯이 환경에 따라 다른 기능을 반환 할 수 있습니다. 과거에는 개발자가 이 도구를 사용하여 Internet Explorer 전용 모듈에 코드 경로를 제공하기 위해 UA 테스트를 수행했지만 비슷한 목표를 달성하기 위해 요즘에는 기능 검색을 쉽게 선택할 수 있습니다.

Module Pattern Variations

Import mixins

이 패턴의 변형은 전역 (예 : jQuery, Underscore)이 모듈의 익명 함수에 인수로 전달 될 수있는 방법을 보여줍니다. 이렇게하면 효과적으로 가져올 수 있으며 원하는대로 로컬 별칭을 지정할 수 있습니다.

// Global module
var myModule = (function ( jQ, _ ) {

    function privateMethod1(){
        jQ(".container").html("test");
    }

    function privateMethod2(){
      console.log( _.min([10, 5, 100, 2, 1000]) );
    }

    return{
        publicMethod: function(){
            privateMethod1();
        }
    };

// Pull in jQuery and Underscore
})( jQuery, _ );

myModule.publicMethod();
Exports

이 다음 변형은 전역 변수를 소비하지 않고 전역 변수를 선언 할 수 있게 하며 마지막 예제에서 볼 수있는 전역 가져 오기 개념을 유사하게 지원할 수 있습니다.

// Global module
var myModule = (function () {

  // Module object
  var module = {},
    privateVariable = "Hello World";

  function privateMethod() {
    // ...
  }

  module.publicProperty = "Foobar";
  module.publicMethod = function () {
    console.log( privateVariable );
  };

  return module;

})();

툴킷 및 프레임 워크 관련 모듈 패턴 구현

Dojo

Dojo는 dojo.setObject()라는 오브젝트에 대한 작업을 위한 편리한 메소드를 제공합니다. 이것은 첫 번째 인수로 myObj.parent.child와 같이 점으로 구분 된 문자열을 취합니다.이 문자열은 "myObj"내부에 정의 된 "parent"개체 내에서 "child"라는 속성을 참조합니다. setObject()를 사용하면 자식 값을 설정하여 전달 된 나머지 경로에 중간 객체가 없으면 전달할 수 있습니다.

예를 들어, store.corestore 네임 스페이스의 객체로 선언하려면 다음과 같이 전통적인 방식으로 구현할 수 있습니다.

var store = window.store || {};

if ( !store["basket"] ) {
  store.basket = {};
}

if ( !store.basket["core"] ) {
  store.basket.core = {};
}

store.basket.core = {
  // ...rest of our logic
};

또는 Dojo 1.7 (AMD 호환 버전) 이상을 사용하여 다음과 같이하십시오.

require(["dojo/_base/customStore"], function( store ){

  // using dojo.setObject()
  store.setObject( "basket.core", (function() {

      var basket = [];

      function privateMethod() {
          console.log(basket);
      }

      return {
          publicMethod: function(){
                  privateMethod();
          }
      };

  })());

});

dojo.setObject()에 대한 자세한 내용은 공식 문서를 참조하십시오.

ExtJS

Sencha의 ExtJS를 사용하는 경우 프레임 워크에서 Module 패턴을 올바르게 사용하는 방법을 보여주는 예제는 아래에서 확인할 수 있습니다.

여기서는 네임 스페이스를 정의하는 방법에 대한 예를 살펴본 다음 네임 스페이스에 private API와 public API가 모두 포함 된 모듈을 채울 수 있습니다. 어떤 의미론적 차이점을 제외하면 보통 자바 스크립트에서 Module 패턴이 구현되는 방식에 매우 가깝습니다.

// create namespace
Ext.namespace("myNameSpace");

// create application
myNameSpace.app = function () {

  // do NOT access DOM from here; elements don't exist yet

  // private variables
  var btn1,
      privVar1 = 11;

  // private functions
  var btn1Handler = function ( button, event ) {
      console.log( "privVar1=" + privVar1 );
      console.log( "this.btn1Text=" + this.btn1Text );
    };

  // public space
  return {
    // public properties, e.g. strings to translate
    btn1Text: "Button 1",

    // public methods
    init: function () {

      if ( Ext.Ext2 ) {

        btn1 = new Ext.Button({
          renderTo: "btn1-ct",
          text: this.btn1Text,
          handler: btn1Handler
        });

      } else {

        btn1 = new Ext.Button( "btn1-ct", {
          text: this.btn1Text,
          handler: btn1Handler
        });

      }
    }
  };
}();
YUI

마찬가지로 YUI3을 사용하여 응용 프로그램을 빌드 할 때도 Module 패턴을 구현할 수 있습니다. 다음 예제는 Eric Miraglia의 원래 YUI 모듈 패턴 구현을 기반으로하지만 보통 JavaScript 버전과 크게 다르지 않습니다.

Y.namespace( "store.basket" ) ;
Y.store.basket = (function () {

    var myPrivateVar, myPrivateMethod;

    // private variables:
    myPrivateVar = "I can be accessed only within Y.store.basket.";

    // private method:
    myPrivateMethod = function () {
        Y.log( "I can be accessed only from within YAHOO.store.basket" );
    }

    return {
        myPublicProperty: "I'm a public property.",

        myPublicMethod: function () {
            Y.log( "I'm a public method." );

            // Within basket, I can access "private" vars and methods:
            Y.log( myPrivateVar );
            Y.log( myPrivateMethod() );

            // The native scope of myPublicMethod is store so we can
            // access public members using "this":
            Y.log( this.myPublicProperty );
        }
    };

})();
jQuery

플러그인과 관련되지 않은 jQuery 코드를 Module 패턴 내에 래핑 할 수있는 방법에는 여러 가지가 있습니다. Ben Cherry는 이전에 모듈간에 많은 공통점이 있는 경우 모듈 정의를 둘러싼 함수 래퍼가 사용되는 구현을 제안했습니다.

다음 예제에서는 새 library 를선언하고 새 library (모듈)가 생성 될 때 init 함수를 document.ready에 자동 바인딩하는 라이브러리 함수를 정의합니다.

function library( module ) {

  $( function() {
    if ( module.init ) {
      module.init();
    }
  });

  return module;
}

var myLibrary = library(function () {

  return {
    init: function () {
      // module implementation
    }
  };
}());

장점

Constructor 패턴이 유용한 이유는 무엇인지 알았지 만, 왜 Module 패턴이 좋을까요? 처음에 자바 스크립트를 접하는 관점에서, 사실 캡슐화의 개념과 JavaScript 측면 보다는 객체 지향적 배경을 가진 개발자들에게는 훨씬 더 명확합니다.

둘째, 개인 데이터를 지원합니다. 따라서 모듈 패턴에서 public 코드 부분은 private 부분을 만질 수 있지만 외부 세계는 클래스의 private 부분을 만질 수 없습니다 (웃지 마세요!) David Engfer 농담).

단점

Module 패턴의 단점은 public 및 private member 모두 다르게 액세스 할 때 가시성을 변경하려면 실제로 멤버가 사용 된 각 장소를 변경해야한다는 것입니다.

나중에 체에 추가되는 메서드에서 전용 멤버에 액세스 할 수도 없습니다. 즉, 대부분의 경우 모듈 패턴은 여전히 유용하며 올바르게 사용될 경우 확실히 응용 프로그램의 구조를 개선 할 가능성이 있습니다.

다른 단점으로는 private 멤버에 대한 자동화 된 단위 테스트를 만들 수 없으며 버그에 핫픽스가 필요할 때 추가적인 복잡성이 있습니다. 단순히 private를 패치하는 것은 불가능합니다. 대신, 버그가있는 private와 상호 작용하는 모든 public 메소드를 무시해야합니다. 개발자는 개인 정보 보호 정책을 쉽게 확장 할 수 없기 때문에 개인 정보 보호 정책은 처음에 나타나는 것처럼 융통성이 없다는 것을 기억해야합니다.

모듈 패턴에 대한 자세한 내용은 Ben Cherry의 심층적 인 기사를 참조하십시오.

results matching ""

    No results matching ""