싱글톤 패턴은 객체의 생성이 단 한번만 있는 패턴을 말한다. 즉 해당 객체의 인스턴스가 애플리케이션 전역에(static) 단 하나만 존재하는 패턴이다.
이 패턴을 사용하여 생성된 객체의 생성자는 static으로 선언되지만, private으로 선언되어 생성자를 호출할 수 없고 생성된 인스턴스를 반환하는 메서드를 참조하여 최초의 생성자가 생성한 인스턴스를 반환받는다.
public class Singleton {
// private static으로 인스턴스 생성
private static Singleton instance = new Singleton();
// 생성자는 외부에서 호출못하게 private 으로 지정
private Singleton() {
}
// 외부에서 인스턴스를 사용하기 위해서는 해당 메서드로
// 이미 생성된 인스턴스만 참조하게 함
public static Singleton getInstance() {
return instance;
}
public void say() {
System.out.println("hi, there");
}
}
이 패턴의 특징을 보면 알 수 있듯이 장점은 매우 분명하다. 최초 한번의 new 연산자로 생성된 인스턴스를 전역에서 참조하여 사용하기 때문에 메모리의 낭비가 없고, 이미 생성된 인스턴스를 사용하기 때문에 속도 측면에서의 이점도 있다.
뿐만 아니라, 인스턴스가 전역에 하나만 존재하기 때문에 다른 클래스들 간에 데이터 공유가 용이하다는 이점도 있다. 하지만 여러 클래스에서 동시에 해당 인스턴스를 참조하게 될 경우 동시성 문제가 발생할 수 있으니 반드시 유의해야 한다.
장점이 뚜렷한 만큼 단점도 굉장히 뚜렷하다. 대표적인 문제점은 다음과 같다.
- 구현 코드가 길다.
앞서 설명한 구현 방법 외에도 정적 팩토리 메서드에서 객체 생성을 확인하고 생성자를 호출하는 경우 멀티스레드환경에서 동시성 문제를 해결하기 위해 synchronized 키워드를 사용해야 한다. synchronized는 현재 자원을 사용하고 있는 스레드를 제외하고 다른 스레드는 해당 자원에 접근하지 못하도록 막는 방식인데, 잘 사용하지 않으면 오히려 성능 저하의 원인이 될 수 있다.
- 테스트가 어렵다.
싱글톤 패턴의 특징상 한 인스턴스의 자원이 공유되기 때문에 테스트가 수행될 때 매번 인스턴스를 초기화 해줘야 한다.
- 의존 관계상 클라이언트가 구현체 클래스에 의존하게 된다.
new 키워드를 직접 사용하여 클래스 안에서 객체를 생성하고 있으므로(추상화에 의존하지 않고 직접 구현체와 결합), 이는 SOLID 원칙 중 의존성 역전의 원칙(DIP)을 위반하게 되고 객체의 기능 확장으로 다른 객체가 필요할 경우 코드가 변경되어야 하므로 개방-폐쇄의 원칙(OCP) 또한 위반할 가능성이 높다. 또한 구현과 사용 책임이 하나의 클래스에 몰려있으므로 엄밀히 따지면 단일 책임 원칙을 위배하고 있다.
싱글톤 패턴은 애플리케이션 실행 중 인스턴스를 한 개만 생성하고 싶을 때 사용하는 디자인 패턴이다. 싱글톤 패턴은 장단점이 명확히 구분되기도 하지만, 프레임워크 도움없이 사용하게 될 경우 단점 때문에 오히려 안티패턴으로 간주되기도 한다. SOLID 관점에서 보더라도 여러 원칙들을 위배하기 때문에 객체지향스럽지 못한 패턴이기도 한다.
스프링 같은 프레임워크의 도움을 받는다면, 객체를 직접 생성할 필요없이 프레임워크가 단점을 커버해주는 부분이 많으므로 싱글톤 패턴의 장점을 누릴 수 있다. 그래서 스프링이 관리하는 빈들은 기본 스코프가 싱글톤으로 되어있다.
공감하며 읽었습니다. 좋은 글 감사드립니다.