Web/JS

[JS] 디자인 패턴(Design Pattern)_싱글톤 패턴(Singleton Pattern)

메바동 2023. 2. 20. 22:42
728x90

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

 


 

1. 싱글톤 패턴(Singleton Pattern)

싱글톤 패턴은 객체 지향 프로그래밍(OOP, Object-Oriented Programming)에서 클래스가 하나의 인스턴스만 가질 수 있도록 하고, 이에 대한 전역 접근 지점을 제공하기 위해 사용되는 디자인 패턴이다.

동일한 객체의 인스턴스를 여러 개 생성할 필요 없이 데이터베이스 연결이나 configuration 설정과 같은 특정 리소스에 대한 중앙 집중식 접근 지점을 제공하는 데 자주 사용된다. 즉, 한 번에 하나의 인스턴스만 가질 수 있는 리소스를 관리해야 하는 상황에서 자주 사용된다.

 

JavaScript에서 싱글톤 패턴은 객체 리터럴 또는 생성자 함수로 구현할 수 있다.

다음은 객체 리터럴로 싱글톤 패턴을 구현하는 예이다:

 

const singleton = {
  instance: null,
  getInstance: function() {
    if (!this.instance) {
      this.instance = {
        // 싱글톤 인스턴스의 속성 및 메서드
        foo: "bar",
        baz: function() {
          console.log("baz");
        }
      };
    }
    return this.instance;
  }
};

const mySingleton1 = singleton.getInstance();
const mySingleton2 = singleton.getInstance();

console.log(mySingleton1 === mySingleton2); // true

 

이 예제에서 getInstance() 메서드는 인스턴스 프로퍼티가 설정되었는지 확인한다.

그렇지 않은 경우 싱글톤 인스턴스의 프로퍼티와 메서드가 포함된 새 객체를 생성하고 인스턴스 프로퍼티를 이 새 객체로 설정한다.

마지막으로 인스턴스 프로퍼티를 반환한다.

 

다음은 객체의 단일 인스턴스를 포함하는 모듈을 생성하고 해당 인스턴스에 접근하는 메서드를 노출하는 예이다:

 

const singleton = (function() {
  let instance;

  function createInstance() {
    const object = new Object("I am the instance");
    return object;
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = singleton.getInstance();
const instance2 = singleton.getInstance();

console.log(instance1 === instance2); // true

 

이 예제는 싱글톤은 비공개 인스턴스 변수와 객체의 새 인스턴스를 생성하는 비공개 createInstance() 함수를 포함하는 모듈이다.

이 모듈은 객체의 단일 인스턴스를 반환하는 공용 메서드 getInstance()를 노출한다.

getInstance() 메서드는 인스턴스가 이미 생성되었는지 확인하고, 그렇지 않은 경우 createInstance() 함수를 호출하여 인스턴스를 생성한다.

 

다음은 생성자 함수를 이용하여 싱글톤 패턴을 구현한 예이다:

 

function Singleton() {
  if (Singleton.instance) {
    return Singleton.instance;
  }

  Singleton.instance = this;

  // 싱글톤의 속성 및 메서드
  this.foo = 'bar';
  this.baz = function () {
    console.log('baz');
  };
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

 

이 예제에서 Singleton() 함수는 인스턴스 프로퍼티의 존재 여부를 확인하다.

존재하면 이를 반환하여 싱글톤으로 만든다.

존재하지 않으면 인스턴스 프로퍼티를 이(새 인스턴스)로 설정하여 새 싱글톤을 생성한다.

 

모듈 패턴을 사용한 싱글톤 패턴과 생성자 함수로 생성한 싱글톤 패턴은 다음과 같은 특징이 있다.

 

  • 모듈 패턴: 이 접근 방식에서 싱글톤은 객체의 단일 인스턴스를 반환하는 모듈로 구현된다. 이 접근 방식의 가장 큰 장점은 캡슐화뿐만 아니라 개인 변수와 함수를 사용할 수 있다는 것이다.
  • 생성자 함수: 이 접근 방식에서 싱글톤은 인스턴스화하여 싱글톤의 인스턴스를 생성할 수 있는 생성자 함수로 구현된다. 이 접근 방식의 가장 큰 장점은 상속과 프로토타입 체인이 가능하다는 점이다.

 

 

 

2. Singleton Pattern 연습 코드

 

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

 

 

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

 

const UserProfile = (function() {
  // private variables
  let instance = null;
  let firstName = '';
  let lastName = '';
  let email = '';

  // private methods
  function validateEmail(email) {
    // TODO: 이메일 유효성 검사 구현
  }

  // public API
  return {
    getFirstName: function() {
      return firstName;
    },

    setFirstName: function(value) {
      // TODO: 입력 유효성 검사 구현
      firstName = value;
    },

    getLastName: function() {
      return lastName;
    },

    setLastName: function(value) {
      // TODO: 입력 유효성 검사 구현
      lastName = value;
    },

    getEmail: function() {
      return email;
    },

    setEmail: function(value) {
      // TODO: 입력 유효성 검사 구현
      email = value;
    }
  };
})();

// TODO: UserProfile 싱글톤 객체를 테스트합니다.

 

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

 

const UserProfile = (function () {
  // private variables
  let instance = null;

  function createInstance() {
    let firstName = '';
    let lastName = '';
    let email = '';

    function validateEmail(email) {
      const regex = /\S+@\S+\.\S+/;
      if (!regex.test(email)) {
        throw new Error('유효하지 않은 이메일입니다.');
      }
    }
    const object = {
      getFirstName: function () {
        return firstName;
      },

      setFirstName: function (value) {
        if (!value || typeof value !== 'string') {
          throw new Error('유효하지 않은 성입니다.');
        }
        firstName = value;
      },

      getLastName: function () {
        return lastName;
      },

      setLastName: function (value) {
        if (!value || typeof value !== 'string') {
          throw new Error('유효하지 않은 이름입니다.');
        }
        lastName = value;
      },

      getEmail: function () {
        return email;
      },

      setEmail: function (value) {
        validateEmail(value);
        email = value;
      },
    };

    return object;
  }

  function getInstance() {
    if (!instance) {
      instance = createInstance();
    }
    return instance;
  }

  // public API
  return { getInstance };
})();

// TODO: UserProfile 싱글톤 객체를 테스트합니다.
const instance1 = UserProfile.getInstance();
const instance2 = UserProfile.getInstance();

console.log(instance1 === instance2);

console.log(instance1.getFirstName());
console.log(instance1.getLastName());
console.log(instance1.getEmail());

instance2.setFirstName('Kim');
instance2.setLastName('Mebadong');
instance2.setEmail('mebadong@example.com');

console.log(instance1.getFirstName());
console.log(instance1.getLastName());
console.log(instance1.getEmail());

try {
  instance1.setFirstName(123);
} catch (e) {
  console.log(e.message); // '유효하지 않은 성입니다.'
}

try {
  instance1.setLastName(null);
} catch (e) {
  console.log(e.message); // '유효하지 않은 이름입니다.'
}

try {
  instance1.setEmail('invalid_email');
} catch (e) {
  console.log(e.message); // '유효하지 않은 이메일입니다.'
}

 

스켈레톤 코드가 위의 예제와는 조금 달라 다른 방법으로 바꿔보았다.

728x90