본문 바로가기

Domain/디자인패턴

[디자인패턴] 싱글톤 (Singleton)

싱글톤 (Singleton)

싱글톤이란 프로세스 내에서 한 클래스에서 여러개의 객체 생성을 방지하는 생성 패턴이다. 즉, 하나의 프로세스에는 하나의 객체만 생성되게하는 패턴이다.

 

실제 사용 가능한 예시

모바일 애플리케이션의 경우 최근 다크모드를 지원하는 경우가 많다. 이런 다크모드의 경우 여러 액티비티(스크린)을 전환하면서도 같은 상태로 유지되어야 한다. 이럴 경우 다크모드 관련된 클래스는 메모리에 항상 적재되어 있으며 모든 클래스가 이를 공유해야 한다. 이 상황에서 적합한게 바로 싱글톤이다.

 

 

자바 코드

public final class Singleton{
    private static Singleton instance = null;
    
    private Singleton(){}
    
    public getInstance(){
    	if(instance==null)
        	instance = new Singleton();
        return instance;
    }
}

위에서 Singleton 클래스 내에 Singleton 객체가 private static으로 정의되어있다. 이는 외부에서 접근이 불가능하며, 컴파일 과정에서 메모리에 적재됨을 의미한다. 그리고 private 생성자를 통해 외부에서 new 메서드를 통해 Singleton을 생성할 수 없다. 마지막 public getInstance() 메서드를 실행하면 instance가 생성되지 않은 경우에 객체를 생성하고 instance를 반환하게 된다.

 

 

코틀린 코드

코틀린에서는 object를 통해 싱글톤을 생성할 수 있다.

object ApiService{
    private const val BASE_URL = "https://api.github.com"

    private val retrofit:Retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val service:GithubService = retrofit.create(GithubService::class.java)
}

//접근
//val service = ApiService.service

object는 싱글톤 그 자체라고 보면 된다.

 

 

다른 방법으로는 Companion Object를 통해 Java와 비슷하게 싱글톤을 생성할 수 있다.

class Singleton private constructor(private val name: String) {
    companion object {
        @Volatile private var instance: Singleton? = null

        fun getInstance(param: String) : Singleton{
            if(instance == null){
            	synchronized(this){
                	instance = Singleton(param)
                }
            }
            return instance
        }
    }
}

이 방법은 조금 귀찮긴 하지만 싱글톤 생성 과정에서 파라미터가 필요한 경우 사용하기에 적합하다. object는 파라미터를 따로 전달받을 수 없기 때문이다.

 

Companion Object는 코틀린에서 자바의 static과 유사한 역할이다. 클래스가 메모리에 할당되지 않더라도 클래스 내부의 Companion Object는 컴파일 과정에서 메모리에 할당되어 어디서든 접근이 가능하다.

 

 

싱글톤을 잘 사용하기

싱글톤은 결국 여러 클래스에서 접근하게 되는 메모리 영역이다. 그렇기에 멀티스레드 환경에서 예상하는 방향으로 동작하지 않을 수 있다. 따라서, 싱글톤에는 동기화를 제공해야한다. 다행히도 object는 그 자체로 동기화가 보장되고, companion object를 활용하여 구현한 방법에는 위에서 소개한 코딩 방식으로 동기화를 제공할 수 있다.

volatile 키워드. 변수는 보통 캐시에 할당하여 빠른 접근을 보장한다. 이 경우에 여러 스레드가 접근할 경우 데이터가 일치하지 않을 수 있다. volatile 키워드로 선언된 변수는 변수가 캐시에 할당되는 것을 막는다. 단점으로는 캐싱을 하지 않기 때문에 오버헤드가 발생한다.

synchronized 키워드. 여러 스레드가 접근할 수 있는 상황에서 임계 영역에 대해 synchronized로 영역을 지정해주면 해당 영역은 하나의 스레드만 접근가능해진다.