많은 프로그래머들이 코드를 짜면서 좀 빈번하게 나왔던 문제점들이 있을 거예요.
그럼 이런 문제와 해결방법이 많은 프로그래머들에게 익숙해지면서 얘는 이런 식으로 해결하면 돼~라고 반사적으로 나오게 되겠죠?
그런 자주 쓰이는 해결방법들을 소프트웨어의 디자인 패턴이라고 합니다.
그럼 싱글톤 패턴은 문제점을 해결한 해결법이라는 것인데 어떤 문제점이었는지 확인해 봅시다.
문제점 파악
우리가 만들던 프로젝트에서 예시를 만들겠습니다.
저희가 예를 들어 MemberServiceImpl을 만든 걸 한 곳이 아닌 여러 곳에서 사용한다고 가정할게요. 그리고 그걸 스프링의 기능이 아니라 우선 순수 자바의 방식으로 불러와볼게요!
singleton 테스트 패키지를 만들어서 SingletonTest 클래스를 만들어보아요~
테스트 코드를 확인해 보시면 isNotSameAs 그러니까 두 개가 다르면 테스트가 성공하는 것입니다.
똑같은 appConfig내의 memberService()를 불러왔는데 과연 테스트가 성공할까요?
이전에 설정했던 AppConfig는 기억하시죠? 기억 안 나시다면 복습해 보는 것도 좋아요!
실행 결과
출력결과의 memberService1, memberService2 맨 뒤를 주의깊게 봐주세요!
각각의 생성된 클래스는 고유 아이디값을 가지게 됩니다. 지금 memberSerivce1과 memberService2는 각각 @cecf639, @183ec003으로 다른 아이디값을 가진 같은 기능을 하지만 엄연히 분리된 클래스입니다.
이렇게 우리가 해당 서비스를 쓰기 위해 클래스를 계속 새로 만들게 된다면 클래스가 엄청나게 많이 만들어지고, 엄청나게 소멸되면서 우리의 프로젝트에 엄청난 부하를 주게 될 수 있습니다.
싱글톤 패턴이란 해당 문제를 해결하기 위해 프로그램이 실행되면서 바로 딱 클래스당 하나의 클래스만 새로 생성되면서 여기저기서 클래스를 쓸 때는 이미 만들어진 클래스를 불러와서 이용하기만 하도록 적용시키는 것입니다.
싱글톤 패턴은 어떻게 쓰지?
이제 우리의 프로젝트를 위해 싱글톤 패턴을 써서 굳이 중복으로 새로 만들 필요 없는 클래스들을 하나로 사용하고 싶어 지셨을 거예요. 임의로 그럼 싱글톤 서비스를 간단하게 만들어볼까요?
지금 singleton 패키지 내에서 바로 서비스를 만들어볼게요. 실제 프로젝트에 쓸 건 아니고 확인용이니까요!
정리해 보자면
본인 스스로의 클래스를 new를 통해 생성해서 private static final형태로 선언한다.
그렇게 선언한 결과물을 return 하는 함수를 public으로 외부에서 사용할 수 있도록 한다.
본인 클래스의 생성자를 private로 선언하여 외부에서 new를 통해 생성할 수 없도록 만든다.
그럼 다시 Test클래스로 돌아와서 아래 테스트를 추가해서 이번엔 isSameAs로 두 객체가 같은지 확인해 봅시다!
실행결과
아이디값 보이시나요!!! 두 서비스가 @4149c063으로 동일합니다!!!! 딱 한 번만 new 된 서비스 클래스를 singletonService1과 singletonService2가 이미 생성된 객체를 불러와 사용하도록 된 겁니다.
싱글톤 구현의 단점 해결하기
(아 그거 그렇게하는거 아닌데..)
아니 그 위대한 싱글톤 패턴을 적용해 놓고 왜 불편한 거죠? 왜냐면 사실 싱글톤 패턴을 위와 같이 적용하면 단점이 많이 따라옵니다
싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
의존관계상 클라이언트가 구체 클래스에 의존한다. → DIP를 위반한다.
클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
테스트하기 어렵다. 내부 속성을 변경하거나 초기화하기 어렵다.
private 생성자로 자식 클래스를 만들기 어렵다.
결론적으로 유연성이 떨어진다.
안티패턴으로 불리기도 한다.
음… 대를 위해 이 정도는 감수해야 하는 법… 이라기엔 사실 감수를 안 해도 되는 방법이 있습니다. 바로 스프링을 사용하는 것!
(또 당신입니까… G.O.A.T)
그리고 사실 이와 같은 방법은 우리가 사실 써봤었습니다. 바로 @Configuration 데코레이터를 쓰고 그 이름 이상했던 AnnotationConfigApplicationContext을 썼던 지난 시간 기억하시나요? 이 방식으로 클래스를 사용하면 사실을 알아서 싱글톤패턴으로 불러왔었던 것입니다. 한번 해볼까요? 테스트를 하나 추가해 봅시다!
실행결과
두 서비스가 @a486d78로 같은 모습입니다.
세상에 데코레이터 하나 썼다고 귀찮은 싱글톤 생성 과정 다 스킵하고 싱글톤 패턴으로 클래스를 불러오다니!!
이렇게 간편하게 싱글톤 패턴을 적용하게 할 수 있는 Spring을 보니 문득 롤 초창기부터 지금까지 현역으로 달려온 페이커가 새삼 대단하다고 느껴지는군요…
(항시 숭배하십시오)
Configuration에서 중복 new 해결하기
우리의 설정 파일인 AppConfig에서 궁금한 부분이 하나 있는데 한번 보러 가보시죠.
보시면 memberRepository()가 실행될 때마다 new MeoryMemberRepository()가 실행되죠?
근데 제가 주석으로 적은 두 부분에서 memberRepository()가 실행되는데 그렇다면 MeoryMemberRepository()가 두 번이나 new 된다는 건데… 그럼 MeoryMemberRepository가 여러 개 생기는 건데… 그럼 싱글톤이 안 지켜지는 게 아닐까요? 엇??!?!
한번 직접 돌려서 확인해 봅시다.
우선 MemberServiceImpl과 OrderServiceImpl에 해당 서비스에서 사용하고 있는 MemberRepository를 그대로 return해주는 함수를 각각 만들어줍시다.
각각 memberService에서 쓰고 있는 memberRepository, orderService에서 쓰고 있는 memberRepository 그리고 AppConfig에서 바로 뽑아온 memberRepository 세 개를 비교하는 테스트입니다.
실행결과
(어캐했누?)
뭐지? 분명 따로 new가 되어서 id가 각각 다른 MemoryMemberRepository가 생겨야 할 텐데 아이디가 다 같습니다. 근데 이건 저도 어떻게 한 건지 잘 모르겠어서.. spring이 Configuration 내에서 알아서 중복으로 생성될 클래스를 중복으로 생성 안되게 해주는 기능이 있다 정도로 알아주시면 될 것 같습니다.
중요한 건!! @Configuration을 써서 스프링이 아! 얘는 설정파일이구나라고 알게 해 준 클래스한테만 이렇게 적용시켜 줍니다.
@Configuration을 쓰지 않은 클래스 내에서 아무리 @Bean데코레이션을 써서 선언한다 한들 이렇게 완벽하게 싱글톤 패턴을 적용시켜주지 않습니다. 주의해 주세요!
마치며
오늘은 싱글톤 패턴의 장점과 이걸 편하게 잘 적용시켜 주는 Spring의 위대함을 살짝 맛보는 시간을 가져봤습니다.
앞으로 한동안 이렇게 구현보다는 스프링의 기능에 대해 자세하게 알아보도록 하겠습니다.