싱글톤 패턴(Singleton Pattern)

케니스·2023년 1월 17일
0

Design Pattern

목록 보기
2/3
post-custom-banner

싱글톤 패턴

이번글에서는 디자인 패턴 중 하나인 싱글톤 패턴에 대해서 알아봅니다.

싱글톤 패턴은 객체 지향 소프트웨어에서 반복되는 문제를 해결하기 위해 잘 설명해진 GoF 디자인 패턴 중 하나입니다. 싱글톤 패턴은 클래스의 인스턴스화를 단일 인스턴스로 제한하는 패턴입니다.

싱글톤 패턴의 특징

  • 오직 하나의 객체 인스턴스를 가진다
  • 인스턴스에 접근하기 위한 쉬운 방법을 제공한다
  • 인스턴스화를 컨트롤한다 (ex.클래스 생성자를 숨김)

싱글톤 패턴의 장점

싱글톤 패턴을 사용하여 인스턴스를 한 개로만 가져가면 아래와 같은 이점이 있습니다.

  • 메모리 비용 절감 : 객체를 생성할 때 마다 메모리를 할당 받는데 한번의 최초 한번의 new를 통해 객체를 생성한다면 고정된 메모리 영역의 사용으로 메모리 비용을 절감할 수 있다
  • 데이터 공유 : 싱글톤 인스턴스는 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들과 데이터를 공유할 수 있다

싱글톤 패턴의 단점

싱글톤 패턴만의 장점도 분명하지만 반대로 안티패턴이라고 불릴만큼 단점도 많이 있습니다.

  • 테스트하기 어려움 : 많은 데이터를 공유할 수록 다른 클래스들 간의 결합도(Coupling)이 높아지고 개방-폐쇄원칙(OCP)을 위배하면서 테스트하기가 어려워짐
  • 멀티 스레드 처리 : 멀티 스레드 환경에서 여러 인스턴스가 생성되는 것을 방지하기 위한 동기화 처리를 위해 syncronized 키워드를 사용해야하고 이는 많은 양의 코드를 작성을 요구함
  • SOLID 원칙 위반(SRP, DIP, OCP) : 싱글톤은 Primary Function과 하나의 인스턴스만 생성 두 가지 책임이 있기 때문에 SRP를 위반하고 의존관계상 클라이언트가 구현체에 의존하면서 DIP를 위반하고 자연스럽게 OCP도 위반할 가능성이 높다.

구현 코드

Java

public class Coin {

    private static final int ADD_MORE_COIN = 10;
    private int coin;
    private static Coin instance = new Coin(); // eagerly loads the singleton

    private Coin() {
        // private to prevent anyone else from instantiating
    }

    public static Coin getInstance() {
        return instance;
    }

    public int getCoin() {
        return coin;
    }

    public void addMoreCoin() {
        coin += ADD_MORE_COIN;
    }

    public void deductCoin() {
        coin--;
    }
}

Kotlin

object Coin {
    private var coin: Int = 0

    fun getCoin():Int {
        return coin
    }

    fun addCoin() {
        coin += 10
    }

    fun deductCoin() {
        coin--
    }
}

멀티스레드 환경에서 싱글톤 생성

지연 초기화(Lazy initialization)

멀티 스레드에서 하나의 싱글톤 객체에 접근할 때 경쟁 상태(Race Condition)이 발생하여 여러개의 인스턴스가 생성될 수 있어 자바에서는 synchronized로 감싸서 지연 초기화를 더블체킹과함께 스레드 세이프하게 생성해야한다.

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

완벽한 싱글톤을 만들기 위해

앞서 제시한 싱글톤을 생성한 방법은 두가지 문제점이 있다

  1. 직렬화와 역직렬화
  2. 리플렉션
    싱글톤 클래스를 직렬화와 역직렬화 할 때 인스턴스가 새로 생성되어 싱글톤의 단일 인스턴스를 위반한다. 그리고 리플렉션을 이용하여 런타임에 싱글톤 private 생성자에 접근하여 새로운 인스턴스를 생성할 수 있다. 이 두가지 문제는 Enum을 통해 해결할 수 있다
enum EnumSingleton {
    INSTANCE;
    String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
// EnumSingleton singleton = EnumSingleton.INSTANCE;

Enum은 직렬화와 역직렬화가 가능하기 때문에 별도의 Serializable을 구현하지 않아도 되고 리플렉션을 시도하려고 하면 NoSuchMethodException 이 발생하기 때문에 외부에서 싱글톤 인스턴스를 생성하는 것은 불가능하므로 완벽한 싱글톤 형태라고 불린다.

결론

싱글톤은 이점이 있는 패턴이지만 앱 전역의 상태 관리를 하는데 있어서 안티패턴으로 간주된다. 이로 인해 싱글톤에 대한 잠재적인 종속성이 도입되어 실제 코드를 분석하기 위한 어려움과 크고 리팩토링에 대한 비용도 증가한다. 그리고 SOLID 원칙중 SRP, DIP, OCP를 위반 할 수 있고 이로인해 테스트하기 어렵다.

참고

profile
노력하는 개발자입니다.
post-custom-banner

0개의 댓글