[5장] Singleton Pattern

ss0510s·2022년 7월 10일
0

Singleton Pattern

클래스 인스턴스를 하나만 만들고, 그 인스턴스로의 전역 접근을 제공한다.

구현 기법

1) Lazy initialization

public class Singleton {
	private static Singleton uniqueinstance; // 하나뿐인 인스턴스를 저장하는 정적 변수
    private Singleton() {} // 생성자를 private로 선언
    
    public static Singleton getInstance() {
    // 클래스 인스턴스를 하나만 생성
    	if(uniqueInstance == null) { // 인스턴스가 생성되지 않았을 때
        	uniqueInstance = new Singleton(); // 객체 생성
        }
        return uniqueInstance;
    }
}
  • 자원을 많이 차지하는 것을 방지(오버헤드 방지)하고 필요할 때만 사용할 수 있다.

문제점: 멀티스레딩 문제 발생한다. 동시에 실행되었을 때,인스턴스가 생성되었는지 모르고 2개의 인스턴스를 생성하게 된다

2) Thread safe initialization

public class Singleton {
	private static Singleton uniqueinstance; // 하나뿐인 인스턴스를 저장하는 전역변수
    private Singleton() {} // 생성자를 private로 선언
    
    public static synchronized Singleton getInstance() {
    // 클래스 인스턴스를 하나만 생성
    	if(uniqueInstance == null) { // 인스턴스가 생성되지 않았을 때
        	uniqueInstance = new Singleton(); // 객체 생성
        }
        return uniqueInstance;
    }
}
  • 메소드가 시작할 때만 동기화가 필요하기 때문에 불필요한 오버헤드가 발생할 수 있다.

3) Eager Initialization

  • 인스턴스가 필요할 때는 생성하지 말고 처음부터 생성한다.
public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
    private Singleton() {}
    
    public static Singleton getInstance() {
    	return uniqueInstance;
    }
}
  • compile 시에 객체가 생성되므로, 동기화 걱정이 없다.
  • 공간 낭비가 심하고, 예외 처리를 할 수 없다.

4) Static block initialization

class Singleton {
    private static Singleton instance;

    private Singleton() {}
    
    // static 블록을 이용해 예외 처리
    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("객체 생성 오류");
        }
    }

    public static Singleton getInstance() {
        return instance;
    }
}
  • 예외처리가 가능하지만, static으로 인해 사용하지 않음에도 공간을 차지한다.

5) DCL(Double-Checked Looking)

  • 처음(인스턴스 생성시)에만 동기화하고 나중에는 동기화하지 않는다.
public class Singleton {
	private volatile static Singleton uniqueinstance; 
    private Singleton() {} 
    
    public static Singleton getInstance() {
    // 클래스 인스턴스를 하나만 생성
    	if(uniqueInstance == null) { // 인스턴스가 있는지 확인
        	synchronized (Singleton.class) { // 동기화
            	if(uniqueInstance == null) { // 인스턴스가 있는지 재확인
        			uniqueInstance = new Singleton(); // 객체 생성
                }
            }
        }
        return uniqueInstance;
    }
}

6) enum 사용

  • enum은 모든 자바 열거체의 공통된 조상 클래스이다.
  • enum은 멤버변수를 private로 만들고 한번만 초기화하기 때문에 멀티 쓰레드로 부터 안전한다.
  • 직렬화가 자동으로 처리되므로 여러 객체가 생성될 일이 없다.
  • 리플렉션(Reflection)을 통한 접근이 불가능하다.
  • 직접적인 접근을 허용한다.
public enum Singleton {
	UNIQUE_INSTANCE;
}

public class SingletonClient {
	public static void main(String[] args) {
    	Singleton singleton = Singleton.UNIQUE_INSTANCE;
    }
}
  • 일반적인 클래스로 마이그레이션할 경우, 처음부터 코드를 다시 짜야된다.

** 리플렉션: 런타임에 결정될 클래스 타입을 컴파일 시에 결정한 것처럼 행동할 수 있게 하는 API

7) Bill Pugh Solution (LazyHolder)

  • 내부클래스를 static으로 선언하여 싱글톤 클래스가 초기화되어도 SingleInstanceHolder 내부 클래스는 메모리에 로드되지 않는다.
  • getInstance() 메서드 호출시 SingleInstanceHolder 내부의 static 변수를 반환하기 때문에 최초 생성 및 호출이 가능하다.
  • final로 지정되었기 때문에 값이 바뀌지 않는다.
  • 멀티쓰레드 환경에서도 안전하고, Lazy Loading도 가능하다.
class Singleton {

    private Singleton() {}

    // static 내부 클래스를 이용
    // Holder로 만들어, 클래스가 메모리에 로드되지 않고 getInstance 메서드가 호출되어야 로드됨
    private static class SingleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingleInstanceHolder.INSTANCE;
    }
}

문제점

1. 모듈 간 의존성이 높아진다.

  • 대부분의 싱글톤을 이용하는 경우 클래스의 객체를 미리 생성하고 정적 메소드를 이용해 사용하기 때문에 클래스 사이에 강한 의존성과 높은 결합이 생긴다.
  • 의존성이 높아져 모듈 간의 결합을 강하게 할 수 있다. => 느슨한 결합 위반

2. 객체지향 원칙에 위배되는 경우가 많다.

  • 단일 책임 원칙(SRP) : 인스턴스 하나만 생성하여 여러 책임을 지니게 되는 경우가 많다.
  • 개방-폐쇄 원칙(OCP) : 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 된다. 이는 하나의 객체를 수정하면 다른 객체 또한 수정해야하는 경우가 생기기 때문에 위반하게 된다.
  • 의존 역전 원칙(DIP): 의존 관계상 클라이언트가 인터페이스와 같은 추상화가 아닌, 구체 클래스에 의존하게 된다.

3. TDD 단위테스트시에 불리하다.

  • 단위 테스트는 독립적인 반면, 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하기 때문에 각 테스트마다 독립적인 인스턴스 생성이 어렵다.
  • 테스트가 결함없이 수행되려면 매번 인스턴스의 상태를 초기화 시켜주어야 한다

적용

데이터베이스 연결 모듈

  • 하나의 인스턴스를 기반으로 DB 연결을 한번만 수행할 수 있어 DB 연결에 관한 인스턴스 생성 비용이 감소하여 DB 연결 모듈에서 싱글톤 패턴을 주로 사용한다.
    • MongoDB: Node.js에서 MongoDB 를 연결할 때 쓰는 mongoose 모듈에서 볼 수 있다.
    • MySQL: Node.js에서 MySQL연결시 연결에 관한 인스턴스를 한번만 정의하고 다른 모듈에서 해당 인스턴스를 기반으로 쿼리를 보내는 형식으로 구현된다.
profile
개발자가 되기 위해 성장하는 중입니다.

0개의 댓글