본문 바로가기

JAVA

Java의 싱글톤과 정적클래스: Spring 마렵다...

현업에 있는 사람이라면 익숙하게 듣는 싱글턴패턴에 대해서, 그리고 추가적으로 정적클래스와 함께 이야기를 해보고자 한다.

(이 기회를 통해서 다시금 스프링 프레임워크에 대한 고마움이 생긴다..!)


싱글톤 패턴

클래스 인스턴스 하나만 생성하고, 어디서든 인스턴스를 참조할 있도록하는 패턴

생성자가 여러 호출되더라도 실제로 생성되는 객체는 하나

 

https://refactoring.guru/design-patterns/singleton

왜 쓸까?

1. 고정된 메모리 영역을 가지고 하나의 인스턴스만 사용하기 때문에 메모리낭비

2. 싱글턴 클래스의 인스턴스는 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉬움

3. DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러 생성해야 하는 상황에 많이 사용

 

구현하는 방법: 6가지

1.Eager initialization

  • 내꺼 바로 초기화 (빠르다는 장점 / 안써도 static이라 메모리 점유, 예외처리할 수 있는 방법이 없음)

2.Static block initialization

  • 예외 처리는되나, 역시 메모리 점유

3.Lazy initialization

  • 메소드 호출 시, 초기화 방법 -> 이외에는 인스턴스를 생성하지 않음
  • 1번 방법 단점 보완
  • 스레드 세이프하지 않음

4.Thread Safe initialization

  • Synchronized 키워드 붙이기 (나머지 스레드는 대기 / 성능저하, 느림)

5.Double-Checked Locking

  • Null 체크를 Synchronized 블록 밖에서 한번, 안에서 한번 총 두번 실행
  • 밖에서 하는 체크는 인스턴스가 있는 경우 빠르게 리턴하기 위해서 안쪽에서 하는 체크는 인스턴스가 생성되지 않은 경우 하나의 인스턴스만 생성하기 위해
  • 작업의 복잡성이 올라가는 단점이 있다.

6.Bill Pugh Solution

  • 이너 클래스라 하는 헬퍼 클래스 만든다. 
  • Double Checked에 비해 구현이 간단
  • 3번이 가능
  • Thread Safe

정적 클래스

1. 모든 메소드가 static인 경우

2. 또는 inner static class 뜻하기도

 

왜 쓸까?

1.상태를 가지고 있지 않고 global access를 제공할 떄 유용 

2.Static 은 컴파일할 때 static binding으로 싱글턴보다 좀 더 빠르다.

3.클래스 자체에 static 붙여 사용할 없다 (inner class 때만 가능)

 

1.Static variable

  • 메모리에 고정적으로 할당되어, 프로그램이 종료될 해제되는 변수

2.Static mrthod

  • 객체 생성없이 메소드 바로 호출가능
  • 객체 생성없이 접근하는 메소드이므로 Static이 아닌 변수는 사용 불가능
  • 객체지향을 방해

3.Static class

  • Non-Static Nested Class 라고도 불림
  • 외부클래스는 내부클래스를 멤버변수 처럼 사용
  • 내부클래스는 외부클래스의 자원 직접사용 가능
  • Static Nested Class 라고 한다.
  • 외부클래스의 자원 Static 붙은 것만 사용가능

싱글톤vs 정적 클래스: 누가 더 객체지향일까?

차이점 싱글톤 정적 클래스
원리 하나의 인스턴스를 생성하여 재사용 인스턴스 생성 X
인터페이스 구현 가능 불가능
Override 가능 불가능
Load 필요에 따라 Lazy 가능 static binding으로 빠르게 로딩
OOP O X
저장 영역 스택

1.static은 다형성을 사용할 수 없다.

2.싱글턴에서 멀티턴 패턴(하나가 아닌 여러 개 반환 즉 객체 최대 n개 반환)으로 바꿀 수 없다.

3.객체의 생성 시점을 제어할 수 없다.

  • Java의 static은 프로그램 실행 시에 초기화된다.
  • 싱글턴은 getInstance 호출할 때 객체를 만드므로 제어가 그나마 가능하다.

잠깐, 싱글톤은 정말 OOP일까?

  • 상태가 없는 객체나 설계 상 유일해야하는 시스템 컴포넌트를 싱글턴으로 구현 
  • 만약 싱글턴이 상태를 가진 객체라면? -> 전역으로 접근하는 여러 다른 스레드에서 상태를 바꾸게 되는 위험성이 존재
  • 그렇다고 해서 싱글턴이 안티패턴이 아닌가?
    • 구현하는 코드가 많아짐 / DIP 위반 / OCP 위반
    • 대부분의 싱글톤을 이용하는 경우 인터페이스가 아닌 클래스의 객체를 미리 생성하고 정적 메소드를 이용해 사용
      • 이는 싱글톤과 사용하는 클래스 사이에 강한 의존성, 높은 결합이 생기게 되어 수정, 단위테스트의 어려움이 발생한다.
  • 결론적으로는 패턴 자체적으로는 객체지향과는 거리가 먼 패턴이다.

 

그렇다면 어떻게 해야하나?

    • 종속성 주입(DI)를 이용하여 객체를 관리한다.
    • 가장 이상적인 방법은 설계를 통해 평범한 객체를 하나의 인스턴스만 존재하도록 관리하는
    • 실제로 모든 싱글톤 객체를 개발자가 관리하기는 쉽지 않다.
    • 그래서 스프링 프레임워크가 편하게 개발을 도와주는 것 아닐까?
    • 스프링에서 싱글턴은 이 때까지 언급한 싱글턴 패턴과는 다르다
    • 클래스의 제어를 IoC방식의 컨테이너에게 넘겨 컨테이너가 관리
    • 이를 통해 평범한 객체도 하나의 인스턴스 뿐인 싱글턴으로 존재가능

 

스프링 컨테이너 없이 싱글톤을 사용할 때 문제점

  • 싱글톤 패턴을 구현하는 코드량이 많다.
  • 의존관계상 클라이언트가 구체 클래스 의존한다. → DIP 위반
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트 코드를 작성하기 어렵다.
  • 내부 속성을 변경하거나 초기화하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어져서 안티패턴으로 불린다.

결론

  • 스프링 없이 싱글톤을 구현하기 위해서는 로직외의 것에 대해 집중해야하는 시간이 존재한다.
  • 한정된 자원으로 알뜰살뜰 쓰기 위한 개발자들의 노고를 다시금 느낄 수 있던 시간. (목마른자가 우물을 판다)

References