들어가며
- 인프런 김영한 님의 싱글톤 강의 정리입니다.
- 웹 어플리케이션은 보통 수십, 수백명의 고객의 요청을 동시에 수행 합니다.
- 이 때 각 요청이 들어 올 때 마다 Service class 에서 객체를 새로 만들어 요청을 수행 한다면 너무 많은 객체가 생성 되어 메모리가 낭비 됩니다.
싱글톤(순수 자바)
- 싱글톤을 간단히 정의 하면 클래스의 인스턴스가 딱 하나만 생성 되는것을 보장 하는 패턴 입니다.
- 즉 위와 같은 상황에서 단 하나의 인스턴스 만을 만들어 이를 공유 하며 사용 하게 하는 것입니다.
- 이렇게 하기 위한 방법중 하나가 아에 2개가 생성되지 못하게 하는 것 입니다.
. 아래와 같이 Service 를 만들게 되면 생성자가 private 이기 때문에 외부에서는 생성 할 수 없게 됩니다.
. 따라서 해당 instance 가 필요 할 때는 getInstance 를 통해 Service 내에서 생성된 instance 를 가져와야 합니다.
. 이렇게 되면 무조건 단 하나의 인스턴스 만을 생성 할 수 있게 됩니다.
public class SingletonService{
private static SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService() {}
}
테스트(사용법)
- 실제로 하나의 인스턴스 만이 생성 되는지 확인 해 보기 위해 테스트를 진행 해 보겠습니다.
. 위 조건에 맞추어 getInstance 로 인스턴스를 가져 오고 동일 한지 확인 해 보았습니다.
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}
문제점
- 싱글톤에 장점만 존재 할 수는 없기 때문에 여러 단점도 존재 합니다.
. 우선 싱글톤을 구현 하는데 코드가 많이 들어 갑니다.
. 또한 의존관계상 클라이언트가 여러 구현 클래스를 의존 해야 하기 때문에 DIP 를 위반 합니다.
. 이에 따라 OCP 를 위반할 가능성이 높습니다.
. 결국 코드가 유연하지 못하게 됩니다.
스프링 컨테이너(싱글톤 컨테이너)
- 스프링 컨테이너는 위의 문제를 해결하면서 싱클톤으로 관리 할 수 있는 방법 입니다.
. 컨테이너는 빈으로 등록된 객체들을 생성해 모아 두고 관리 합니다.
. 즉 별도의 작업을 하지 않고 싱글톤을 유지 할 수 있습니다.
- 이렇게 되면 코드가 지저분 해 지지도 않고 DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있습니다.
- 테스트로 확인 할 수 있습니다.
@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
주의해야 할 점
- 다만 인스턴스가 하나만 생성 되기에 생기는 문제점도 존재 합니다.
. 다음과 같이 본인이 구매한 금액이 아닌 B 가 구매한 물품의 금액이 출력 되는게 그 예시 입니다.
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//A 사용자가 10000원을 주문 함
statefulService1.order("userA", 10000);
//B 사용자가 20000원을 주문 함
statefulService2.order("userB", 20000);
//A 사용자가 자신의 주문 금액을 조회 -> 20000원이 나와 버림 하나의 해결 책은 위에 넣을때 바로 반환 받아 버리면 됨
int price = statefulService1.getPrice();
}
- 이렇듯 인스턴스를 하나만 생성해서 공유 할 때는(싱글톤 객체 일때는) 객체의 상태를 유지하게 설계해서는 안됩니다.
. 위 상황에서도 A 가 10000원을 주문 한 후 10000원이 라는 상태가 유지 되었기 때문에 문제가 발생 하였습니다.
. 이 문제를 어떻게 해결 할 수 있을까요?
- 바로 무상태(stateless) 로 설계 하면 됩니다.
. 위 예시에서 하나의 해결책을 제시 하자면 주문을 함과 동시에 바로 orderPrice 를 return 해 버리면 됩니다.
. 그렇게 되면 A 의 주문 상태가 유지 되지 않기 때문에 문제가 발생 하지 않습니다.
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//A 사용자가 10000원을 주문 함
int orderPriceA = statefulService1.order("userA", 10000);
//B 사용자가 20000원을 주문 함
int orderPriceB = statefulService2.order("userB", 20000);
System.out.println(orderPriceA);
}
'백앤드 > SpringBoot + Java 좋은 객체 지향 설계' 카테고리의 다른 글
[SpringBoot + Java] 좋은 객체 지향 설계(spring 의 container) (0) | 2023.07.31 |
---|---|
[SpringBoot + Java] 좋은 객체 지향 설계(DI) (0) | 2023.07.31 |
[SpringBoot + Java] 좋은 객체 지향 설계(SOLID) (0) | 2023.07.28 |