되풀이되는 사건이나 물체의 형태
본래 도시 환경 설계를 위한 언어였다.
이후 4명이 모여 객체지향 패턴을 제작하였고, 그 유명한 GOF책이 탄생하게 된다.
사실 몰라도 수년동안 프로그래머로 일할 수 있다. 많은 프로그래머가 패턴에 대한 지식없이 업무를 수행한다. 물론, 자신도 모르는 사이에 일부 패턴을 구현하고 있을수 도 있다. 가장 효율적인 방법을 간추린 것이 패턴이니까!
그럼에도 배워야 하는 이유는 다음과 같다.
패턴은 약한 프로그래밍 언어를 위한 치트키로 작동한다. 예를 들어 전략패턴은 람다함수로 쉽게 구현 가능하다.
이때 주의해야 할 점은 망치를 든 사람은 모든 것이 못으로 보이게 된다. 간단한 코드로 해결 가능한 일인데도 패턴을 강제로 적용한다. 이는 패턴을 막 배운 초보자에게서 잘 나타나기 마련! 나같은 경우도 그랬다. 패턴을 대충 알게됐을때 의존성을 주입할때 항상 팩토리 패턴을 사용했다.
어중간하게 알고가지 말자!
싱글톤 패턴이란, 클래스의 인스턴스를 단 하나만 생성하며 전역에서 접근할 수 있게 도와주는 생성 패턴중 하나다.
인스턴스를 하나만 생성하면 공유데이터의 관리가 쉬워진다. 또한 전역에서 접근 할 수 있다는 건 필수 데이터를 저장하기 용이하다.
모든 기술엔 단점 역시 존재한다. 전역변수를 남발하면 실수로 수정할 가능성이 높아진다.
두 가지 규칙을 적용해 싱글톤 패턴을 구현해보자.
먼저 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로도 싱글톤 패턴을 충분히 구현할 수 있다.
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
키워드가 존재한다. 현재 쓰레드가 공유데이터에 접근 할 시, 다른 쓰레드의 접근을 차단한다.
멀티쓰레딩 관련 자세한 내용은 추후 경험할 일이 있다면 다뤄보겠다.