싱글톤 패턴

 

클래스의 인스턴스가 1개만 생성되는 것을 보장하는 디자인 패턴이다.

 

그렇다면 객체 인스턴스를 2개 이상 생성하지 못하도록 하는 방법은?

private 생성자를 이용해 외부에서 new 키워드를 사용못하게 막기!

 

 

 

싱글톤 패턴이 생겨야 하는 이유

먼저 웹 애플리케이션을 살펴보자면 

보통 웹 애플리케이션은 여러 클라이언트의 요청이 동시에 들어오기 때문에 객체는 무한생성이 된다. 

아래 그림보면 고객이 3번 요청을 하면 3번 객체가 생성이 됨

 

 

위의 그림 코드

@Configuration
public class AppConfig {

	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
	
	@Bean
	public MemoryMemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}
	
	@Bean
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy()); //discountPolicy()로 대체해주기
	}
	
	@Bean
	public DiscountPolicy discountPolicy() {
//		return new FixDiscountPolicy();  // 1만원이든 2만원이든 천원 할인적용
		return new RateDiscountPolicy(); // 1만원 천원할인, 2만원이면 2천원할인
	}
}

 

 

그렇다면 요청할때마다 객체가 계속 생성되는데 모두 다른지 확인해보기

 

[테스트]

public class SingletonTest {
 	@Test
 	@DisplayName("스프링 없는 순수한 DI 컨테이너")
 	void pureContainer() {
    
            AppConfig appConfig = new AppConfig();

            //1. 조회: 호출할 때 마다 객체를 생성
            MemberService memberService1 = appConfig.memberService();

            //2. 조회: 호출할 때 마다 객체를 생성
            MemberService memberService2 = appConfig.memberService();

            //참조값이 다른 것을 확인
            System.out.println("memberService1 = " + memberService1);
            System.out.println("memberService2 = " + memberService2);

            //memberService1 != memberService2
            assertThat(memberService1).isNotSameAs(memberService2);
     }
}

 

 

[결과] 

▶memberService1, memberService2 는 각각 새로운 MemberService 객체를 생성해낸다 

콘솔에 보이듯 요청할때마다 객체가 모두 새로 생성되어 다른 인스턴스인걸 확인할 수 있고

jvm 메모리에 계속 객체가 생성되고 소멸되어 메모리 낭비가 심해진다.

 

 

[해결방안]

그렇기 때문에 객체가 딱 하나만 생성을 해서 공유하도록 설계하는것이 '싱글톤 패턴'이라고 한다. 

 

 

 

 

 

싱글톤 패턴 적용하기

클래스의 인스턴스 1개만 생성하기

private 생성자를 사용하여 외부에 객체가 생성하지 못하도록 한다. 

 

 

싱글톤 패턴 적용한 테스트 코드를 main말고 test 폴더에 생성해보기 

[비 싱글톤]

public class NotSingletonService {

    //생성자
    private NotSingletonService(){
    }

    public NotSingletonService getInstance(){
        return new NotSingletonService();;
    }
}

getInstance() 를 호출할때마다 NotSingletonService 객체가 계속 생성되기 때문에 싱글톤 패턴이 아니다.  

 

 

 

[싱글톤]

public class SingletonService {

    //static 영역에 객체가 딱 1개만 생성되도록
    private static final SingletonService instance = new SingletonService();

    //생성자 private 을 통해 외부로부터 막음
    private SingletonService(){
    }

    //static 메서드를 통해 객체 인스턴스를 조회하도록 
    // instance 를 반환하여 동일한 객체가 반환됨.
    public static SingletonService getInstance(){
        return instance;
    }
}

 

1. private static final SingletonService instance = new SingletonService();

: static 영역의 instance 변수를 통해 SingletonService 객체를 생성한다.

 

2. private SingletonService() {} 

: 생성자 private을 통해 외부에서 객체를 생성하지 못하도록 막아준다.

 

3.  public static SingletonService getInstance(){
        return instance; }

:  getInstance() 메서드를 호출할때 이미 1번에서 객체가 생성된 상태여서 이미 생성된 동일한 객체가 반환이 된다. 

 

 

※ 여기서 SingletonService 클래스를 SingletonService  타입인 getInstance () 를 정의하는데
클래스 내에서 클래스를 다시 재정의 한다?


public static SingletonService getInstance(){
        return instance;
}

>> 자바는 클래스 내 클래스를 정의가능하며 이것을 중첩 클래스 또는 내부 클래스라고 한다.
그렇다면 내부 클래스를 정의하는 이유는? 
>> 외부적으로 사용될 일이 없고 내부적으로 별도의 클래스를 정의가 필요할때 사용한다. 
여기서는 SingletonService 타입을 반환하는 이유는 싱글톤 패턴에서 유일한 인스턴스( instance )를 가져오기 위한 설계 구조상 필요한 부분이기 때문에

 

 

 

 

instance 변수와 getInstance() 메서드에 static을 쓰는 이유??

  • 1. 클래스 레벨에서 접근 가능 / 간편 접근성
    • static으로 선언된 정적 변수인 instance는 클래스 로딩시점에 메모리에 올라가서 객체가 생성된다.
    • 그러므로 같은 클래스 내에 static으로 선언한 메서드에서 객체 생성 없이 바로 접근가능하다.
      • ▶ SingletonService.getInstance()
    • 2. 싱글톤 유지
      • 단 하나만의 객체를 생성하고 재사용하는데에 적합하다. 
    • static을 쓰면 클래스 로딩 시점에 객체를 생성하고 동시에 메모리에 할당하기 때문에 시점이 안 맞는 오류를 방지하기도 한다.
  • 만약 getInstance() 메서드를 static으로 선언하지 않으면?
    • 싱글톤으로 구현은 가능
    • getInstance() 메서드를 호출했을때 이미 객체가 생성된 이후여서 메서드를 호출할 수는 있다. 
    • 하지만 static을 선언하면 클래스 내의 명확한 접근성을 보장하기 때문에 쓰는게 좋다. 

 

 

 

[테스트]

public class SingletonTest {
    @Test
    @DisplayName("싱글톤 패턴을 적용한 객체 사용")
    public void singletonServiceTest() {
        
        //private으로 생성자를 막아두었다. 컴파일 오류가 발생한다.
        //new SingletonService();
        //1. 조회: 호출할 때 마다 같은 객체를 반환
        SingletonService singletonService1 = SingletonService.getInstance();

        //2. 조회: 호출할 때 마다 같은 객체를 반환
        SingletonService singletonService2 = SingletonService.getInstance();

        //참조값이 같은 것을 확인
        System.out.println("singletonService1 = " + singletonService1);
        System.out.println("singletonService2 = " + singletonService2)
}

 

[결과]

객체 같은걸 볼 수 있다. 

 

 

SingletonService는 private를 사용했기때문에 클래스 외부에서 객체 생성을 막아놨다. 

그래서 SingletonTest에서 new SingletonService()를 사용하지 않고, getInstance() 메서드를 통해 이미 만들어진 객체를 가져와서 인스턴스를 반환했다 

 

여기서 중요한 점은 getInstance() 메서드를 두번 호출했지만 반환되는 객체는 동일한 것!

이것이 싱글톤 패턴이다. 

 

 

 

 

싱글톤의 문제점

 

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    private SingletonService(){
    }

    public static SingletonService getInstance(){
        return instance;
    }
}

 

  • 싱글톤 패턴을 구현하는 위의 코드를 모두 넣어야하는 문제점이 있다.  
  • private 생성자로서 자식클래스를 만들기 어렵다. 
  • 유연성이 떨어진다. (? 정확한 이해는 못하겠지만 DIP 위반이 되기 때문)

 

 

 

 

하지만 스프링 컨테이너는 싱글톤 패턴의 문제점을 해견해준다. 

 

반응형
LIST

+ Recent posts