현업에 있는 사람이라면 익숙하게 듣는 싱글턴패턴에 대해서, 그리고 추가적으로 정적클래스와 함께 이야기를 해보고자 한다.
(이 기회를 통해서 다시금 스프링 프레임워크에 대한 고마움이 생긴다..!)
싱글톤 패턴
클래스 인스턴스 하나만 생성하고, 어디서든 그 인스턴스를 참조할 수 있도록하는 패턴
생성자가 여러 번 호출되더라도 실제로 생성되는 객체는 하나
왜 쓸까?
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
'JAVA' 카테고리의 다른 글
JPA N+1 문제: 실무에서 어떻게 다룰까? (0) | 2022.11.09 |
---|---|
상속과 조합: 상속은 아는데, 조합은 뭐지..? (1) | 2022.10.05 |
DTO vs VO: 우린 왜 자꾸 나눌까? (0) | 2022.09.14 |