프로그래밍 언어/javaAndroid

[디자인 패턴] 싱글톤 패턴 구현

DongChyeon 2021. 1. 2. 21:46

사실 이거는 Java라는 언어에 한해서만 중요한 지식이 아니지만, 적당한 카테고리를 찾지 못해서 이 카테고리에 적는다. 싱글톤 패턴은 주로 그 프로그램 내에 오직 하나만 생성되고, 생성된 인스턴스를 어디서나 접근 가능하게 한다. 이러한 싱글톤 패턴은 어플 내 데이터베이스나 통신 클라이언트에서 쓰인다. 여기서는 Retrofit의 통신을 위한 Client를 싱글톤 패턴으로 구현하는 것을 예시로 들 것이다. 약간 처음 보는 코드도 있겠지만은, 객체가 오직 하나만 '생성'된다는 점에 주목하고 코드를 보자.

public class RetrofitClient {
    private RetrofitInterface retrofitInterface;
    private String baseUrl = "URL";

    public RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }

    public RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

예제로 사용하기에는 살짝 과한 감이 있지만, 다음의 코드가 있다고 생각해보자. 여기서 Retrofit에 대해서 잠깐 설명을 해보자면, 통신이 있을 때마다, RetrofitClient의 getRetrofitInterace 메소드를 통해 인터페이스를 받아와 통신에 대한 코드를 작성한다. (Retrofit에 대해서 아직 그렇게 깊은 지식을 가지고 있지 못해 확답을 못하겠다.)

예를 들어, 통신이 필요한 곳이 클래스1, 클래스2, 클래스3, 총 3 곳이 있다고 생각해보자.

public class one {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}
public class two {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}
public class three {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}

그러면 대충 이런 식의 코드를 짜게 될 것이다. 이렇게 해도 작동 상의 큰 문제는 없긴 하다. 하지만 사용할 때마다 RetrofitClient 객체를 만들게 되고 만약 이러한 작업이 많아지면 어떻게 될까? 사실상 RetrofitClient 객체는 이 프로그램에 하나만 필요함에도 불구하고 여러 개가 생성되며 메모리 낭비가 발생하고 만약 이 객체가 모든 클래스가 공유해야 될 객체라면 더더욱 문제가 된다.

자바의 문법을 익혔다면, private 접근 지정자를 알 것이다. private는 그 클래스 내에서만 호출이 가능하고 밖에서는 접근이 불가능하다. 싱글톤 객체를 만들기 위해서는 생성자를 private로 선언한다. 예를 들어서 이렇게 말이다. 

public class RetrofitClient {
    private RetrofitInterface retrofitInterface;
    private static String baseUrl = "URL";

    // 생성자를 private로 선언!!
    private RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }
    // 생성자를 private로 선언!!
    
    public RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

이제 밖에서는 이 클래스의 객체를 생성하지 못한다. 밖에서 생성하지 못하는 객체를 어떻게 사용하냐는 궁금증이 생길 수도 있지만, 해결법은 생각외로 간단한다. 내부에서 객체를 생성해주면 된다.

private static RetrofitClient instance = null;

public static RetrofitClient getInstance() {
    if (instance == null) {
        instance = new RetrofitClient();
    }
    return instance;
}

우선 클래스 내에 해당 객체를 담을 instance 변수를 선언하고 getInstance 메소드를 통해 이 instance를 받아올 수 있도록 한다. getInstance 메소드 내에서 instance가 비어있는 상태(null)인 상태일 때만 객체를 생성하므로, 객체가 오직 하나만 생성된다. 이를 만들 때 클래스에 고정된 멤버이므로 static으로 만들어주자.

public class RetrofitClient {
    private static RetrofitClient instance = null;
    private static RetrofitInterface retrofitInterface;
    private static String baseUrl = "URL";

    private RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }

    public static RetrofitClient getInstance() {
        if (instance == null) {
            instance = new RetrofitClient();
        }
        return instance;
    }

    public static RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

최종 형태는 다음과 같다. 싱글톤 패턴을 구현하는 방법의 경우, 인터넷을 보면 다양한 방법이 있고 이는 그 중 하나이다. 따라서 그때그때 알맞은 방법으로 구현해주면 된다.