CS - 디자인패턴 (1) 싱글톤과 모듈

김영현·2024년 11월 28일
0

CS

목록 보기
31/32

디자인 패턴이란?

되풀이되는 사건이나 물체의 형태

본래 도시 환경 설계를 위한 언어였다.
이후 4명이 모여 객체지향 패턴을 제작하였고, 그 유명한 GOF책이 탄생하게 된다.

패턴을 굳이 알아야 할까?

사실 몰라도 수년동안 프로그래머로 일할 수 있다. 많은 프로그래머가 패턴에 대한 지식없이 업무를 수행한다. 물론, 자신도 모르는 사이에 일부 패턴을 구현하고 있을수 도 있다. 가장 효율적인 방법을 간추린 것이 패턴이니까!

그럼에도 배워야 하는 이유는 다음과 같다.

  • 일반적인 문제들에 대해 시도되고 검증된 해결책임
  • 특정 문제를 다루지 않더라도 쓸모가있음.
  • 다양한 문제를 해결하는 방법을 배움
  • 패턴을 배운 팀원과 효율적인 의사소통 가능.
    xx를 aa로 yy처럼... => xx문제를 해결하기 위해 싱글톤 패턴 사용하세요

패턴은 만능이 아니다

패턴은 약한 프로그래밍 언어를 위한 치트키로 작동한다. 예를 들어 전략패턴은 람다함수로 쉽게 구현 가능하다.

이때 주의해야 할 점은 망치를 든 사람은 모든 것이 못으로 보이게 된다. 간단한 코드로 해결 가능한 일인데도 패턴을 강제로 적용한다. 이는 패턴을 막 배운 초보자에게서 잘 나타나기 마련! 나같은 경우도 그랬다. 패턴을 대충 알게됐을때 의존성을 주입할때 항상 팩토리 패턴을 사용했다.

어중간하게 알고가지 말자!


싱글톤 패턴(Singleton)

싱글톤 패턴이란, 클래스의 인스턴스를 단 하나만 생성하며 전역에서 접근할 수 있게 도와주는 생성 패턴중 하나다.
인스턴스를 하나만 생성하면 공유데이터의 관리가 쉬워진다. 또한 전역에서 접근 할 수 있다는 건 필수 데이터를 저장하기 용이하다.
모든 기술엔 단점 역시 존재한다. 전역변수를 남발하면 실수로 수정할 가능성이 높아진다.

how to make 싱글톤 패턴

  1. new 연산자를 호출하지 못하게 기본 생성자를 비공개로 만든다.
  2. 생성자 역할을 하는 공개 메서드를 만든다. 해당 공개 메서드는 캐시된 인스턴스를 반환한다.

두 가지 규칙을 적용해 싱글톤 패턴을 구현해보자.

먼저 c#코드로 나타내보겠다.

public class Singleton
{
    private static Singleton instance = null; //클인스턴스가 아닌, 클래스 자체에 속한 비공개 멤버
    private Singleton() { } // 생성자를 이용하여 인스턴스 생성을 하지 못하게 막음
    
    public static Singleton GetInstance()
    {
            if (instance == null)
            {
                instance = new Singleton();
            }
        return instance;
    }
}

//사용할땐 아래처럼 사용하게 된다.
Singleton singleton = Singleton.GetInstance();

JS로 싱글톤 구현

js로도 싱글톤 패턴을 충분히 구현할 수 있다.

class Singleton {
  static #instance = null;

  constructor() {
    if (new.target === Singleton) {
      throw new Error("new 키워드로 호출 불가능");
    }
  }

  static getInstance() {
    if (!this.instance) {
      this.instance = this;
      console.log("정상적으로 인스턴스가 생성됨");
    }

    return this.instance;
  }
}

const instance1 = Singleton.getInstance(); // 정상 작동
const instance2 = new Singleton();// 에러 발생

js에서는 생성자를 비공개 메서드로 만들 수 없는 대신, new.target 키워드를 이용하여 new를 이용해 클래스를 호출했는지 확인할 수 있다.

위보다 더 간단한 방식은 JS의 모듈을 이용하는 것이다.

let instance = null;
class Singleton{
  constructor(){
    if(instance) throw new Error('이미 인스턴스가 존재합니다!')
    instance = this;
  }
  
  getInstance(){
    return this;
  }
}

export default Object.freeze(new Singleton());

이 방법은 기존의 싱글톤 패턴보다 간단하다. 하지만 본인처럼 디자인패턴에 미숙한 사람은 이런 의문을 가질수도 있다.
어떻게 모듈만으로 싱글톤 패턴을 구현할 수 있을까? JS만의 특징인걸까?


모듈과 싱글톤

This pattern can be implemented in several ways depending on the host programming language, such as the singleton design pattern
출처 : 위키피디아 모듈문서

싱글톤 패턴은 인스턴스(객체)를 하나만 생성하여 계속 캐싱된 인스턴스에 접근하는 패턴이다. 모듈은 코드를 독립적이고 재사용 가능하게 캡슐화한 패턴이다.

그러므로 싱글톤 패턴을 이용하여 모듈패턴을 구현할수 있다.
예를들어 C#으로 제작한 싱글톤 패턴도 하나의 모듈로 간주할 수 있다.

그럼 js의 모듈로 객체를 생성하고, 두 번 임포트해서 사용시 어떤 결과가 나올까?

//counter.js
export default {
  value: 1,
  inc() {
    return this.value++;
  },
  dec() {
    return this.value--;
  },
};
//app.js

import counter1 from "./counter.js";
import counter2 from "./counter.js";

counter1.inc();
console.log(counter1.value); //1

counter2.inc();
console.log(counter2.value); //2

console.log(counter1.value, counter2.value); //2 2

console.log(Object.is(counter1, counter2)); //true

예상한 결과와 똑같이 나오는 것을 볼 수 있다.

그렇다면 객체 프로퍼티가 아닌, 전역변수를 이용한 모듈 패턴은 어떨까?

//counter.js
let value = 0;
export default {
  inc: () => ++value,
  dec: () => --value,
};

//app.js
import counter1 from "./counter.js";
import counter2 from "./counter.js";

console.log(counter1.inc()); //1
console.log(counter2.inc()); //2
console.log(counter1.inc()); //3
console.log(counter2.inc()); //4

역시 예상한 결과가 나온다.

요약: 싱글톤 패턴은 모듈패턴을 구현하는 방법 중 하나다. 언어네 모듈 패턴이 존재한다면 싱글톤 패턴을 쉽게 구현할 수 있다.


멀티쓰레딩과 싱글톤 패턴

싱글톤 패턴을 사용할때 주의해야할 부분 중 하나는 멀티 쓰레딩이다. 토이프로젝트 수준의 웹같은 단순한 개발만 해봤기에 멀티쓰레딩 경험은 있다고 말하기 어렵다. 하지만 조심해야하는 이유는 운영체제에서 배운 지식을 기반으로 추측해 볼 수 있다. 다른 쓰레드에서 싱글톤으로 제작된 클래스를 인스턴스화 했을때, 쓰레드간 정보교환이 이루어지지않아 동시접근 문제가 일어날 수 있기 때문이다.

public class Singleton
{
    private static Singleton instance = null;
    private static readonly object lockObj = new object();
    // private 생성자: 외부에서 인스턴스 생성을 막음
    private Singleton() { }
    public static Singleton GetInstance()
    {
        // 스레드 안전성을 보장
        lock (lockObj)
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

C#에서는 위와같은 동시성 문제를 해결하기 위한 lock키워드가 존재한다. 현재 쓰레드가 공유데이터에 접근 할 시, 다른 쓰레드의 접근을 차단한다.

멀티쓰레딩 관련 자세한 내용은 추후 경험할 일이 있다면 다뤄보겠다.

profile
모르는 것을 모른다고 하기

0개의 댓글