Singleton 패턴이란
싱글턴 패턴은 객체의 인스턴스가 오로지 한 개만 생성되도록 설계하는 디자인 패턴을 의미한다. 이는 전역적으로 접근할 수 있는 하나의 객체를 제공하는데 유용하다.
이러한 싱글턴 패턴은 다음과 같은 특징을 갖는다.
1. 유일성
- 싱글턴 클래스는 단 하나의 인스턴스만 생성함
- 여러 번 인스턴스화를 시도해도 항상 같은 인스턴스를 반환함
2. 전역 접근
- 싱글턴 객체는 전역적으로 접근할 수 있는 방법을 제공한다.
- 이를 통해 어플리케이션의 다른 부분에서 언제든지 동일한 인스턴스에 접근할 수 있다.
Singleton 패턴 구현 방법
자바에서 싱글턴 패턴을 구현하는 방법은 여러가지가 있다. 하지만 여기에서는 권장되는 2가지 방법만 제시할 것이다. (나머지 방법들은 단점이 뚜렷하기 때문)
1. Bill Pugh Solution(LazyHolder)
- 멀티스레드 환경에서 안전하고 Lazy Loading(나중에 객체 생성) 도 가능한 완벽한 싱글턴 기법
- 클래스 안에 내부 클래스(holder)를 두어 JVM의 클래스 로더 매커니즘과 클래스가 로드되는 시점을 이용한 방법 (스레드 세이프함)
- static 메소드에서는 static 멤버만을 호출할 수 있기 때문에 내부 클래스를 static으로 설정
- 이밖에도 내부 클래스의 치명적인 문제점인 메모리 누수 문제를 해결하기 위하여 내부 클래스를 static으로 설정
- 다만 클라이언트가 임의로 싱글턴을 파괴할 수 있다는 단점을 지님 (Reflection API, 직렬화/역직렬화를 통해)
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;
}
}
- 우선 내부클래스를 static으로 선언하였기 때문에 싱글턴 클래스가 초기화되어도 SingleInstanceHolder 내부 클래스는 메모리에 로드되지 않음
- 어떠한 모듈에서 getInstance() 메서드를 호출할 때 SingleInstanceHolder 내부 클래스의 static 멤버를 가져와 리턴하게 되는데, 이때 내부 클래스가 한번만 초기화되면서 싱글턴 객체를 최초로 생성 및 리턴하게 된다.
- 마지막으로 final 로 지정함으로서 다시 값이 할당되지 않도록 방지
2. Enum 이용
- enum은 애초에 멤버를 만들때 private로 만들고 한번만 초기화 하기 때문에 thread safe함
- enum 내에서 상수 뿐만 아니라 변수나 메서드를 선언해 사용이 가능하기 때문에 이를 이용해 싱글턴 클래스처럼 응용이 가능
- 위의 Bill Pugh Solution 기법과 달리 클라이언트에서 Reflection을 통한 공격에도 안전
- 하지만 만일 싱글턴 클래스를 멀티턴(일반적인 클래스)로 마이그레이션 해야 할 때 처음부터 코드를 다시 짜야 되는 단점이 존재 (개발 스펙은 변경될 수 있기 때문에)
- 클래스 상속이 필요 할 때, enum 외의 클래스 상속은 불가능하다.
enum SingletonEnum {
INSTANCE;
private final Client dbClient;
SingletonEnum() {
dbClient = Database.getClient();
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public Client getClient() {
return dbClient;
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.getInstance();
singleton.getClient();
}
}
이 둘의 사용 선택은 싱글턴 클래스의 목적에 따라 갈린다.
- Bill Pugh Solution(LazyHolder) : 성능이 중요시 되는 환경
- Enum : 직렬화, 안정성 중요시 되는 환경
Singleton 패턴의 장단점
싱글턴 패턴은 고정된 메모리 영역을 가지고 하나의 인스턴스만 사용하기 때문에 메모리 낭비 방지할 수 있으며, DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러 개 생성해야 하는 상황에서 많이 사용된다.
하지만 싱글턴 패턴은 다음과 같은 단점도 가지고 있다.
1. 모듈간 의존성 높아짐
- 대부분의 싱글턴을 이용하는 경우 인터페이스가 아닌 클래스의 객체를 미리 생성하고 정적 메소드를 이용해 사용하기 때문에 클래스 사이에 강한 의존성과 높은 결합이 생김
- 즉, 하나의 싱글턴 클래스를 여러 모듈들이 공유를 하는데 만일 싱글톤의 인스턴스가 변경되면 이를 참조하는 모듈들도 수정이 필요하게 된다.
2. SOLID 원칙 위배
- 싱글턴 인스턴스 자체가 하나만 생성되기 때문에 여러가지 책임을 지니게 되는 경우가 많아 단일 책임 원칙(SRP)를 위반하는 경우가 많음
- 싱글턴 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유하면 다른 클래스들 간의 결합도가 높아지게 되어 개방-폐쇄 원칙(OCP)에도 위배됨
- 의존 관계상 클라이언트가 인터페이스와 같은 추상화가 아닌, 구체 클래스에 의존하게 되어 의존 역전 원칙(DIP)에도 위배됨
- 이러한 이유로 싱글턴 패턴을 객제 지향 프로그래밍의 안티 패턴이라고도 함
3. 테스트 어려움
- 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행 할 수 있어야 한다.
- 싱글턴 인스턴스는 자원을 공유하고 있기 때문에 테스트가 결함없이 수행되려면 매번 인스턴스의 상태를 초기화 시켜야 함
4. 멀티스레드 환경 문제
- 멀티스레드 환경에서 싱글턴 클래스를 적절히 구현하지 않으면 인스턴스가 여러 개 생성될 수도 있다.
Singleton 클래스와 정적 클래스의 차이
자바에서 정적 클래스란 정적 메소드(static method)만 가지고 있는 클래스를 의미한다. 참고로static method는 클래스 초기화 시에 JVM의 method area에 저장되며 프로그램 종료 시 제거된다.
그런데 정적 클래스 또한 싱글턴 객체처럼 전역으로 접근 가능한 클래스라 비슷한 상황에서 싱글턴 객체를 사용할지 정적 클래스를 사용할지 고민될 수 있다.
아래는 둘의 특징을 비교한 표이다.
항목 | 싱글턴 패턴 | 정적 클래스 |
특징 | 1. 상속받아서 사용 가능 2. 메소드 파라미터로 사용 가능 |
1. 객체 생성을 하지 않는다.(효율성) |
권장 환경 | 1. 완벽한 객체지향을 필요로 할 때 2. 레이지 로딩이 필요할 때 |
1. 유틸 메소드를 보관하는 용도로 사용할 때 2. 다형성이나 상속이 필요 없는 클래스 |
사용 예시 | 1. DB 연결 관리 2. 설정 관리 |
1. 유틸리티 함수(ex. Math.MAX()) 2. 상수 관리 |
- LazyLoading(레이지 로딩) : 객체의 초기화를 실제로 필요할 때까지 미루는 방법
'Java' 카테고리의 다른 글
SOLID 원칙 (0) | 2024.07.13 |
---|---|
추상 클래스와 인터페이스 (0) | 2024.07.13 |
인터페이스(Interface) (0) | 2024.07.12 |
상속(Inheritance) (0) | 2024.07.12 |
동일성(identity)과 동등성(equality) (1) | 2024.06.07 |