싱글톤 패턴이란?
싱글톤 패턴이란 인스턴스를 하나만 만들어 사용하기 위한 패턴이다.
이번 Blackjack 미션에서 카드 객체를 싱글톤으로 만들게 되었다. 그 이유는 프로그램 실행 시 사용할 카드 객체가 무한대로 생성되는 것을 방지하기 위해서이다. 또한 카드 객체는 프로그램 전역에서 사용되므로 한번 생성한 카드 객체를 프로그램 전역에서 공유하는 것이 메모리를 아끼는 방법이라고 생각했다.
추가적으로 다른 크루가 짠 코드를 보니 하나의 프로그램이 실행되는 동안은 Dealer 또한 하나의 객체만 생성되어야 하므로 Dealer를 싱글턴으로 만들 수 있을 것 같다.
싱글톤 패턴의 필요성
- 메모리 낭비를 줄일 수 있다.
- 생성자를 호출할 때마다 객체를 생성하지 않고, 생성했던 객체를 다시 사용하기 때문에 같은 객체가 여러번 생성되지 않는다.
- 하나의 단일 객체로 프로그램이 종료될 때까지 재사용할 수 있을 때 사용한다.
싱글톤 패턴으로 구현한 Deck 예시
public class Deck {
private static final Deck deck;
private static List<Card> cards;
private Deck() {
List<Card> createdCards = CardFactory.of();
cards = createdCards;
}
public static Deck getInstance() {
if (deck == null) {
return new Deck();
}
return deck;
}
/* 생략 */
}
싱글턴 패턴의 특징
- private 생성자를 갖는다.
- 객체 생성의 책임은
getInstance()
에게 위임하였으므로 추가적인 객체 생성을 막기 위해 생성자 접근제한자를 private으로 설정한다.
- 단일 객체 참조 변수를 정적 속성으로 갖는다.
- 싱글턴 패턴은 클래스의 인스턴스, 즉 객체를 하나만 만들어 사용하는 패턴이다.
- 단일 객체 참조 변수가 참조하는 단일 객체를 반환하는 getInstance() 정적 메서드를 갖는다.
getInstance()
메서드가 new 생성자를 대체한다.
- 객체를 생성할 때 해당 정적 변수가 null이라면 객체를 생성한 뒤 반환하고, 이미 생성되어 있다면 생성된 객체를 반환한다.
- 단일 객체는 불변을 원칙으로 하므로 쓰기 가능한 속성을 갖지 않는 것이 정석이다.
싱글턴 패턴의 단점
- 동시성 문제
멀티 스레드 환경에서 객체를 생성하고자 할 때 동시성 문제가 발생할 수 있다. 객체가 생성되기 전, 스레드 A가 조건 분기문을 실행하는 도중 이미 조건 분기문을 통과한 스레드 B에서 객체를 생성해버릴 수 있다. 그렇다면 스레드 A는 스레드가 B가 객체를 생성한 이후 또 하나의 객체를 생성하게 될 수 있다.
이 문제를 해결하기 위해 Synchronized
를 사용하거나 Holder initialization
를 사용할 수 있다.
- 객체 지향의 특성을 살리기 어렵다
생성자 접근제한자를 private으로 막아주고 있다. 상속을 했을 때 부모의 필드 또는 메서드에 접근할 수 있어야 하기 때문에 부모의 생성자를 먼저 호출한 뒤 자식 생성자를 호출한다. 즉, 상속 받은 자식의 생성자를 호출할 경우 반드시 부모 생성자를 호출해야 한다. 따라서 생성자를 private으로 막아준다면 상속을 할 수 없게되고, 상속을 할 수 없다는 것은 다형성과 같이 객체 지향의 특성을 활용하지 못하는 것과 같다.
- 객체지향 설계 원칙의 ‘개방-폐쇄 원칙’을 위반하게 된다.
리팩토링 시 전체 프로그램에 영향을 미칠 수 있다. 단일 객체가 생성되면 프로그램의 전역에서 사용되기 때문에 싱글톤 패턴에 사용된 메서드 또는 객체가 변경된다면 이를 사용하고 있는 모든 서비스에 영향을 미칠 수 있다. 즉, 객체의 접근 제한을 전역으로 열어둠으로써 다른 클래스 인스턴스 간 결합도가 높아진다.