Skip to content

알아야만하는4가지자바스크립트디자인패턴

scarlett.kim edited this page Oct 28, 2019 · 1 revision

알아야만 하는 4가지 자바스크립트 디자인 패턴

원문 : https://scotch.io/bar-talk/4-javascript-design-patterns-you-should-know

Every developer strives to write maintainable, readable, and reusable code. Code structuring becomes more important as applications become larger. Design patterns prove crucial to solving this challenge - providing an organization structure for common issues in a particular circumstance. 모든 개발자들은 운영할 수 있고, 읽기 쉽고, 재사용성이 가능한 코드 작성을 하려고 노력합니다. 코드 구조는 훨씬 큰 애플리케이션에게 더욱 더 중요합니다.

관련된 과정: Getting Started with JavaScript for Web Development

자바스크립트 웹 개발자는 애플리케이션을 만들 때 알게모르게 자주 디자인 패턴을 많이 접하게 됩니다.

특정 환경에서 사용되는 디자인 패턴의 종류들이 있지만 자바스크립트 개발자는 일부 패턴을 다른 패턴보다 관례적으로 사용하는 경향이 있습니다.

이 글에서는 프로그래밍 실력을 향상시키고 자바스크립트 내부에 대해 자세히 살펴볼 수 있는 일반적인 패턴에 대해 논의하고자 합니다.

아래의 디자인 패턴에 대해 설명을 합니다.

  • Module
  • Prototype
  • Observer
  • Singleton

각각의 패턴은 많은 속성들로 구성되어 있지만 다음 핵심 부분을 강조할 것입니다.

  1. Context: 어떤 상황에서 패턴이 사용됩니까?
  2. Problem: 무엇을 해결 하려고 합니까?
  3. Solution: 이 패턴을 사용하여 제안된 문제를 어떻게 해결합니까?
  4. Implementation: 구현된 코드는 어떻습니까?

자바스크립트 모듈은 특정 컴포넌트의 코드를 다른 구성 요소와 독립적으로 유지하기 위해 가장 널리 사용되는 디자인 패턴입니다.

Essential Reading: Learn React from Scratch! (2019 Edition)

객체지향 언어와 비슷하게 사용하기 위해 자바스크립트에서 모듈을 "classes"로 사용합니다. 클래스들 중 가장 많은 장점을 가진 클래스는 encapsulation(캡슐화) 입니다. encapsulation(캡슐화)는 다른 클래스로 부터의 접근 및 상태를 보호합니다. 모듈 페턴을 사용하면 public와 private(덜 알려져있고, 보호 권한이 있는) 액세스 수준이 허용 됩니다.

모듈은 private 스코프(변수와 메소드를 보호한 클로저입니다. 그러나 함수 대신에 객체가 반환 될 것입니다.)를 허용하기 위해 즉시 실행 함수(IIFE)을 사용합니다. 아래와 같습니다.

(function() {

    // declare private variables and/or functions

    return {
      // declare public variables and/or functions
    }

})();

반환하려고 하는 객체를 반환하기 전에 private한 변수 및 / 또는 함수를 인스턴스화 합니다. 클로저 외부 코드는 이러한 private한 변수가 동일한 스코프에 있지 않으므로 이러한 private한 변수에 접근할 수 없습니다. 해당 패턴이 구현된 코드 입니다.

var HTMLChanger = (function() {
  var contents = 'contents'

  var changeHTML = function() {
    var element = document.getElementById('attribute-to-change');
    element.innerHTML = contents;
  }

  return {
    callChangeHTML: function() {
      changeHTML();
      console.log(contents);
    }
  };

})();

HTMLChanger.callChangeHTML();       // Outputs: 'contents'
console.log(HTMLChanger.contents);  // undefined

callChangeHTML은 반환된 객체에 바인딩되며, HTMLChanger 네임 스페이스 내에서 참조죌 수 있습니다.

Revealing Module Pattern

모듈 패턴의 변형을 Revealing Module Pattern이라고 합니다. encapsulation(캡슐화)를 유지하고, 객체 리터럴로 반환되는 특정 변수와 메소드를 공개하는 것이 목적입니다.

var Exposer = (function() {
  var privateVariable = 10;

  var privateMethod = function() {
    console.log('Inside a private method!');
    privateVariable++;
  }

  var methodToExpose = function() {
    console.log('This is a method I want to expose!');
  }

  var otherMethodIWantToExpose = function() {
    privateMethod();
  }

  return {
      first: methodToExpose,
      second: otherMethodIWantToExpose
  };
})();

Exposer.first();        // Output: This is a method I want to expose!
Exposer.second();       // Output: Inside a private method!
Exposer.methodToExpose; // undefined

훨씬 코드가 깔끔해지지만, private한 메소드 참조는 할 수 없다는 명백한 단점이 있습니다. 이로 인해 단위 테스트와 관련된 문제가 발생할 수 있습니다. 마찬가지로 public한 함수는 오버라이드 될 수 없습니다.

모든 자바스크립트 개발자는 프로토타입 상속과 혼동되는 키워드인 prototype을 보았거나 코드로 프로토 타입을 구현해본적이 있습니다. 프로토타입 디자인 패턴은 JavaScript prototypical inheritance에 의존하고 있습니다.

프로토타입 모델은 주로 성능이 중요한 상황에서 객체를 생성하는데 사용됩니다.

생성되어진 객체는 전달된 원래 객체의 복사본(얕은 복사)입니다. 프로토타입 패턴에서 사용한 대표적인 사례는 광범위한 데이터베이스 작업을 수행하여 애플리케이션의 다른 부분에 사용되는 객체를 만드는 것입니다. 이 광범위한 데이터베이서 작업을 수행하는 대신 다른 프로세스에서 이 객체를 사용해야하는 경우 이전에 만든 객체를 복제하는 것이 좋습니다.

Prototype Design PatternPrototype Design Pattern on Wikipedia

이 UML은 프로토타입 인터페이스가 구현된 것을 복사하는데 사용되는 방법을 설명합니다.

객체를 복사하기 위해, constructor(생성자)는 첫번째 객체의 인스턴스로 존재해야만 합니다. 그 다음 프로토타입 키워드를 사용함으로써 프로토타입 변수와 메소드는 객체의 구조에 바인드됩니다.

기본 예제를 살펴봅시다.

var TeslaModelS = function() {
  this.numWheels    = 4;
  this.manufacturer = 'Tesla';
  this.make         = 'Model S';
}

TeslaModelS.prototype.go = function() {
  // Rotate wheels
}

TeslaModelS.prototype.stop = function() {
  // Apply brake pads
}

이 constructor(생성자)는 하나의 TeslaModelS 객체를 생성하는 것을 허용합니다. 새로운 TeslaModelS 객체가 만들어졌을 때, 생성자의 초기화된 상태를 유지합니다. 덧붙여서 gostop 함수를 유지하는 것은 프로토타입에 의해 gostop이 선언되었기 때문입니다.

프로토타입에서 함수를 동기적으로 함수를 확장하는 방법을 아래에서 설명합니다.:

var TeslaModelS = function() {
  this.numWheels    = 4;
  this.manufacturer = 'Tesla';
  this.make         = 'Model S';
}

TeslaModelS.prototype = {
  go: function() {
    // Rotate wheels
  },
  stop: function() {
    // Apply brake pads
  }
}

Revealing Prototype Pattern

모듈 패턴과 비슷하게, 프로토타입 패턴은 또한 변형이 가능합니다. Revealing Prototype Pattern은 객체 리터럴을 반환하기 때문에 public와 private한 멤버와 함께 캡슐화(encapsulation)를 제공합니다.

객체가 반환하기 때문에 프로토타입 객체는 prefix로 function을 붙일 것입니다. 위의 예제를 확장하여 현재 프로토타입에 노출할 항목을 선택하여 접근 수준을 유지할 수 있습니다.

var TeslaModelS = function() {
  this.numWheels    = 4;
  this.manufacturer = 'Tesla';
  this.make         = 'Model S';
}

TeslaModelS.prototype = function() {

  var go = function() {
    // Rotate wheels
  };

  var stop = function() {
    // Apply brake pads
  };

  return {
    pressBrakePedal: stop,
    pressGasPedal: go
  }

}();

반환된 객체의 스코프를 벗어나게된 함수로 인해 stop 그리고 go의 함수가 반환 객체에서 어떻게 보호가 되는지 확인해보세요. 기본적으로 자바스크립트는 프로토타입 상속을 지원하기 때문에 기본적인 기능을 다시 코드로 작성할 필요가 없습니다.

애플리케이션의 한 부분이 변경되는 경우가 많으며, 한 부분이 변경되면 다른 부분도 업데이트해야합니다. 앵귤러JS에서, 만약 $scope 객체가 업데이트 되면 다른 컴포넌트에게 알리기 위해 이벤트가 트리거 되어야 합니다. observer 패턴은 단지 객체가 수정되면 변경이 발생한 종속 객체로 broadcasts하는 것을 포함합니다.

또 다른 주요 예는 model-view-controller (MVC) 아키텍처입니다. 모델이 변경되면 뷰가 업데이트 됩니다. 한 가지 이점은 모델에서 뷰를 분리하여 의존성을 줄이는 것입니다.

Observer Design PatternObserver Design Pattern on Wikipedia

UML 다이어그램에서 객체는 subject, observer, concrete 객체가 필요합니다. subject는 모든 변경을 알리기 위한 observer에 대한 객체를 참조하고 있습니다. observer 객체는 구체적인 observer가 notify 메소드를 구현할 수 있도록하는 추상 클래스입니다.

이벤트 관리를 통한 observer 패턴을 사용한 앵귤러JS의 예제를 살펴보겠습니다.

// Controller 1
$scope.$on('nameChanged', function(event, args) {
    $scope.name = args.name;
});

...

// Controller 2
$scope.userNameChanged = function(name) {
    $scope.$emit('nameChanged', {name: name});
};

observer 패턴은 독립된 객체 또는 subject을 구별하는 것이 중요합니다. observer 패턴은은 많은 장점을 제공하지만, 단점 중 하나는 observer 수가 증가함에 따라 성능이 크게 저하된다는 점에 유의해야합니다.

자바스크립트에서 자기 자신의 Subject와 Observer들을 생성할 수 있습니다. 아래 구현된 코드를 살펴 보겠습니다.

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

  return {
    subscribeObserver: function(observer) {
      this.observers.push(observer);
    },
    unsubscribeObserver: function(observer) {
      var index = this.observers.indexOf(observer);
      if(index > -1) {
        this.observers.splice(index, 1);
      }
    },
    notifyObserver: function(observer) {
      var index = this.observers.indexOf(observer);
      if(index > -1) {
        this.observers[index].notify(index);
      }
    },
    notifyAllObservers: function() {
      for(var i = 0; i < this.observers.length; i++){
        this.observers[i].notify(i);
      };
    }
  };
};

var Observer = function() {
  return {
    notify: function(index) {
      console.log("Observer " + index + " is notified!");
    }
  }
}

var subject = new Subject();

var observer1 = new Observer();
var observer2 = new Observer();
var observer3 = new Observer();
var observer4 = new Observer();

subject.subscribeObserver(observer1);
subject.subscribeObserver(observer2);
subject.subscribeObserver(observer3);
subject.subscribeObserver(observer4);

subject.notifyObserver(observer2); // Observer 2 is notified!

subject.notifyAllObservers();
// Observer 1 is notified!
// Observer 2 is notified!
// Observer 3 is notified!
// Observer 4 is notified!

Publish/Subscribe

Publish/Subscribe 패턴은, 알림을 수신하려는 객체(subscribers)와 이벤트를 발생 시키는 객체(publisher) 사이에 있는 주제/이벤트 채널을 사용합니다. 이벤트 시스템을 통해 코드는 subscriber가 필요로 하는 값을 포함하는 사용자 커스텀 인수를 전달할 수 있는 애플리케이션별 이벤트를 정의할 수 있습니다. 여기서 아이디어는 subscriber와 publisher간의 의존성을 피하는 것입니다.

subscriber가 publisher을 통해 브로드 캐스트 한 토픽의 알림을 등록하고, 수신하기 위해 적절한 이벤트 핸들러를 구현하는 subscriber이므로 이는 Observer 패턴과 다릅니다.

많은 개발자들은 구별을 하지만 publish/subscribe 디자인 패턴 observer와 함께 사용하려 합니다. publish/subscribe 패턴의 subscriber는 일부 메시징 매체를 통해 알려지지만, obserber는 subject와 유사한 핸들러를 통해 알려집니다.

앵귤러JS에서는 subscriber 'subscribes'는 $on('event', callback)을 사용하여 이벤트를 발생하고, publisher 'publishes'는 $emit('event', args) 또는 $broadcast('event', args)을 사용하여 이벤트를 알립니다.

Singleton은 단일 인스턴스화만 허용하지만 동일한 객체들의 많은 인스턴스를 허용합니다. Singleton은 클라이언트가 여러 객체를 만들지 못하도록 제한합니다. 첫번째 객체를 만든 후에는 자체 인스턴스를 반환합니다.

아직 사용하지 않은 대부분의 사람들은 Singleton 사용 사례를 찾기가 어렵습니다. 한 가지 예는 사무용 프린터를 사용하는 것입니다. 사무실에 10명이 있고 모두 한 대의 프린터를 사용하는 경우 10대의 프린터가 한 대의 프린터(인스턴스)를 공유합니다. 하나의 프린터를 공유함으로써 동일한 리소스를 공유합니다.

var printer = (function () {

  var printerInstance;

  function create () {

    function print() {
      // underlying printer mechanics
    }

    function turnOn() {
      // warm up
      // check for paper
    }

    return {
      // public + private states and behaviors
      print: print,
      turnOn: turnOn
    };
  }

  return {
    getInstance: function() {
      if(!printerInstance) {
        printerInstance = create();
      }
      return printerInstance;
    }
  };

  function Singleton () {
    if(!printerInstance) {
      printerInstance = intialize();
    }
  };

})();

클라이언트는 이 메소드에 접근하지 않기 때문에 create메소드는 private이지만, getInstance 메소드는 public입니다.

각 담당자는 다음과 같이 getInstance 메소드와 상호 작엉하여 프린터 인스턴스를 생성할 수 있습니다.

var officePrinter = printer.getInstance();

앵귤러JS에서 Singleton이 주로 사용하고 있으며 가장 주목할 만한 것이 service, factories, provider 입니다. 상태를 유지하고 리소스 액세스를 제공하기 때문에 두 개의 인스턴스를 만들면 shared service/factory/provider이 상실됩니다. 레이스의 조건은 하나 이상의 스레드가 동일한 리소스에 접근하려고 할 때 다중 스레드 애플리케이션에서 발생합니다.

Singleton은 레이스 조건에 취약하므로 먼저 초기화된 인스턴스가 없는 경우 두 개의 스레드가 반환되고 인스턴스 대신 두 개의 객체를 만들 수 있습니다. 이것은 Singleton의 목적을 망칩니다. 따라서 개발자들은 멀티스레드 애플리케이션에서 Singleton을 구현할 때 동기화와 관련 지어야 합니다.

디자인 패턴은 어떤 것이 다른 것에 비해 유리할 지 알 수 있지만 연습과 함께 큰 규모의 애플리케이션에 자주 사용이 됩니다.

애플리케이션을 구현하기 전에, 각각의 역할들에 대해 그리고 그들이 어떻게 상호작용하는지에 대해 철저히 생각해야합니다. Module, Prototype, Observer, Singleton 디자인 패턴을 복습한 이후에, 이 패턴들을 식별하여 실제 현업에서 사용할 수 있어야합니다.

Clone this wiki locally