본문 바로가기
Design Pattern

디자인 패턴 - Singleton Pattern

by SuldenLion 2022. 5. 22.
반응형

Singleton Pattern :

Program이 시작될 때 어떤 클래스가 최초 한번만 메모리를 static하게 할당하고 그 메모리에 instance를 만들어 사용하는 패턴. / 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나고 최초 생성 이후에 호출된 생성자는 이전에 생성했던 객체를 반환함. -> 자바에서는 생성자를 private으로 선언해서 생성 불가하게 하고 getInstance()로 받아서 쓰기도 함. / registry 같은 설정 파일의 경우 객체가 여러개 생성되면 설정 값이 변경될 위험이 생길 수 있다. Instance가 하나만 생성되는 특징을 가진 싱글턴 패턴을 이용하면, 하나의 instance를 메모리에 등록해서 여러 thread가 동시에 해당 instance를 공유하여 사용할 수 있게끔 할 수 있기 때문에 요청이 많은 곳에서 사용하면 효율을 높일 수 있음. 주의해야 할 점은 싱글턴을 만들때 Concurrency(동시성) 문제를 고려해서 설계해야 함.

요약 - 싱글턴 패턴은 단 하나의 인스턴스를 생성해 사용하는 디자인 패턴이다. (인스턴스가 필요할 때 똑같은 인스턴스를 만들어 내는 것이 아닌, 기존의 인스턴스를 사용하게 함)

 

Singleton Pattern 사용 이유 :

고정된 메모리 영역을 얻으면서 한번의 new로 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있음. 또한 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉬움. DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러개 생성해서 사용해야 하는 상황에서 많이 사용함. (Thread Pool, 캐시, Dialog Box, 사용자 설정, 레지스트리 설정, 로그 기록 객체 등등) / 안드로이드 앱 같은 경우는 각 Activity나 클래스별로 주요 클래스들을 일일히 전달하기가 번거롭기 때문에 싱글톤 클래스를 만들어 어디서나 접근하도록 설계하는것이 편함. (인스턴스가 절대적으로 한개만 존재하는 것을 보장하고 싶을때 사용. 두번째 이용할때부터는 객체 로딩 시간이 현저하게 줄어서 성능이 좋아짐)

 

Singleton Pattern의 단점 :

싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유할 경우 다른 클래스의 인스턴스들간의 결합도가 높아져(Coupling이 많아짐) [개방-폐쇄 원칙]을 위배하게 됨.(객체지향 설계 원칙에 어긋남 : Singleton의 사용은 Global state를 만들 수 있기 때문에 바람직한 방법은 아님. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역상태를 갖는 것은 객체지향 프로그래밍에서는 지양되야 할 모델임. 또한 Singleton은 자바에서 Private생성자를 가지므로 상속할 수 없음. 즉 다형성같은 객체지향의 특징 적용안됨) -> 수정이 어려워지고 테스트하기 어려워짐. / 멀티 스레드 환경에서 동기화 처리를 안하면 인스턴스가 두개가 생성된다든지 하는 경우가 생길 수 있음. 개발을 할 때 항상 들어온 goto문은 쓰면 안된다~ 라던가 전역 객체는 안좋은 거다~ 같은 말처럼 꼭 필요한 경우가 아니면 사용을 지양해야함(적절히 잘쓰면 좋음)

주의해야 할점 - 상태를 가진 객체를 Singleton으로 만들면 안됨. 앱 내의 단 한개의 인스턴스가 존재하고, 이를 전역에서 접근할 수 있다면 각기 다른 스레드에서 객체의 상태를 마구잡이로 변경시킬 여지가 있음. 상태가 공유된다는 것은 매우 위험한 일이기에 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 Singleton으로 만들어야 함.

Singleton이 안티패턴이라고 불리는 이유 :

SOLID 원칙(객체지향 설계원칙)의 대부분은 인터페이스 설계와 관련이 되어 있음. 의존성을 Interface에 두면 실제 구현 클래스의 구현이 변경되어도 이를 사용한 코드는 큰 영향을 받지 않음. 그렇기에 SOLID원칙을 지키기 위해서는 인터페이스로 설계하는게 좋음. 하지만 Singleton을 사용하는 경우 대부분 인터페이스가 아닌 구현 클래스의 객체를 미리 생성해놓고 정적 메소드를 이용하여 구현하게 됨. -> Singleton을 사용하는 곳과 Singleton Class 사이에 의존성이 생기게 됨. = 결합도를 높이는 행위. 수정 및 단위 테스트의 어려움이 생기게 됨.

리펙토링에서의 영향도는 프로그램 전체가 됨.

 

멀티 쓰레드에서 안전한(Thread-safe한) 싱글톤 클래스와 인스턴스를 만드는 방법 :

- Thread safe Lazy initialization(게으른 초기화) : private static으로 인스턴스 변수를 만들고 private 생성자로 외부에서 생성을 막았으며 synchronized 키워드를 사용해서 thread-safe하게 만든다. 하지만 synchronized 특성상 비교적 큰 성능저하가 발생하므로 권장하지 않는 방법.

- Thread safe Lazy initialization + Double-checked locking : 게으른 초기화의 성능저하를 완화시키는 방법. getInstance()에 synchronized를 사용하는 것이 아니라 첫번째 if문으로 인스턴스의 존재여부를 체크하고 두번째 if문에서 다시한번 체크할 때 동기화 시켜서 인스턴스를 생성하므로 thread-safe하면서도 처음 생성 이후에 synchronized 블럭을 타지 않기 때문에 성능저하를 완화. 완벽한 방법은 아님.

- Initialization on demand holder idiom (holder에 의한 초기화) : 클래스안에 클래스(holder)를 두어 JVM의 class loader 메커니즘과 class가 로드되는 시점을 이용한 방법. 개발자가 직접 동기화 문제에 대해 코드를 작성하고 문제를 회피하려 한다면 프로그램 구조가 그만큼 복잡해지고 비용 문제가 생길 수 있고 특히 정확하지 못한 경우가 많음. 그런데 이 방법은 JVM의 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하여 싱글턴의 초기화 문제에 대한 책임을 JVM에 떠넘김. holder안에 선언된 instance가 static이기 때문에 클래스 로딩시점에 한번만 호출될 것이며 final을 사용해 다시 값이 할당되지 않도록 만든 방법. 가장 많이 사용하고 일반적인 Singleton 클래스 사용 방법이라함.

반응형

'Design Pattern' 카테고리의 다른 글

디자인 패턴 - Factory Pattern  (0) 2022.06.25
디자인 패턴 - Compound Pattern  (0) 2022.06.01
디자인 패턴 - Proxy Pattern  (0) 2022.05.17
디자인 패턴 - State Pattern  (0) 2022.04.27
디자인 패턴 - Composite Pattern  (0) 2022.04.16

댓글