싱글턴(Singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스
ex) 함수(무상태 객체), 설계상 유일해야 하는 시스템 컴포넌트
- 장점
- 고정된 메모리 영역을 얻으면서 한 번의 new로 인스턴스를 공유하기 때문에 메모리 낭비를 방지할 수 있다.
- 두 번째 사용부터는 객체 로딩 시간이 줄어들어 성능이 좋아진다.
- 단점
- 싱글턴 인스턴스가 너무 많은 일을 하면 인스턴스의 간의 결합도가 높아진다. (OCP(Open-Closed Principle, 개방 폐쇄 원칙) 의 원칙에 위배 )
- 디버깅이 어려움이 있다.
- 테스트 코드의 작성의 어려움이 있다.
: 싱글턴 인스턴스를 public static final 필드로 만들고, 생성자를 private 하게 지정하여 외부에서 기본 생성자를 생성할 수 없도록 만드는 방식.
장점 : 싱글턴임이 API에 명백히 드러남, 간결함
다만, Java Reflection API 를 이용하면 싱글톤이더라도 객체를 생성해낼 수 있다.
→ 이를 방어하기 위해 방어코드를 추가로 작성한다.
```java
class Car {
public static final Car INSTANCE = new Car ();
private Car() {
// private한 생성자에서 두번째 객체가 만들어지는 것을 방지.
if(INSTANCE != null){
throw new RuntimeException("이미 생성된 싱글톤 객체가 존재합니다.")
}
}
}
...
@Test
@DisplayName("두 객체가 같은 인스턴스를 참조함")
public void CarSameTest(){
// Car item1 = new Car();
Car item2 = Car.INSTANCE;
Car item3 = Car.INSTANCE;
assertThat(item2).isEqualTo(item3);
}
// 해당 테스트에서는 기본적으로 두 객체가 같다고 나온다.
```
: 아이템 1의 정적 팩터리 메서드를 public하게 만들어 싱글톤을 반환한다.
- 장점
- API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.
- 정적 팩터리를 제네릭 싱글턴 팩터리로 만들어 타입에 유연하게 대처할 수 있다.
- 정적팩터리의 메서드 참조를 공급자로 사용할 수 있다.
→ 다만, 이러한 장점들이 굳이 필요하지 않다면, public 필드 방식이 좋다.
```java
class Car {
public static final Car INSTANCE = new Car ();
private Car () {
if(INSTANCE != null){
throw new RuntimeException("이미 생성된 싱글톤 객체가 존재합니다.");
}
}
// 정적 팩터리 방식의 싱글턴.
public static Car getInstance(){
return INSTANCE;
}
}
@Test
@DisplayName("인스턴스가 전체 시스템에서 하나뿐임이 보장된다.")
public void singleton() {
Car instance1 = Car.getInstance();
Car instance2 = Car.getInstance();
assertThat(instance1).isEqualTo(instance2);
}
```
: public 필드 방식과 비슷하지만, 더 간결하며, 제2의 인스턴스가 만들어지는 것을 완벽히 막을 수 있다.
대부분의 상황에서는 원소가 하나뿐인 Enum 타입이 싱글턴을 만드는 가장 좋은 방식이다.
```java
public enum Car{
INSTANCE;
}
```