프로그램 실행시 보통 많은 인스턴스가 생성됨 그러나 클래스의 인스턴스가 단 하나만 필요한 경우도 있음
시스템 안에서 1개 밖에 존재하지 않는 것을 프로그램으로 표현하고 싶을 때 그럼
여기서 물론 주의를 기울여 인스턴스 생성을 1회만 실행되도록 작성하여 인스턴스 1개만 생성할 수 있지만 그런 상황이 아닐 수도 있음
만약 지정한 클래스의 인스턴스가 절대로 1개 밖에 존재하지 않는 것을 보증하고 싶을 때, 인스턴스가 1개밖에 존재하지 않는 것을 프로그램 상에서 표현하고 싶을 때는 그렇게 주의를 기울인다고 될 문제가 아님
그래서 인스턴스가 한 개 밖에 존재하지 않는 것을 보증하는 패턴을 Singleton 패턴이라고 함, Singleton은 요소를 1개밖에 가지고 있지 않은 집합을 의미함
Singleton은 인스턴스가 1개만 존재하는 클래스임
여기서 -가 붙어있는 것은 Singleton이 private인 것을 명시하기 위해서임
getInstance 또한 이 메소드가 static 메소드이기 때문임
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("인스턴스를 생성했습니다");
}
public static Singleton getInstance() {
return singleton;
}
}
Singleton 클래스에서는 인스턴스를 1개 밖에 만들 수 없으며 singleton은 static 필드로서 Singleton 클래스의 인스턴스에서 초기화됨, 이 초기화는 Singleton 클래스를 로드할 때 1회만 실행됨
Singleton 클래스의 생성자는 private임, 이것은 클래스 외부에서 생성자의 호출을 금지하기 위해서임
new Singleton()
이 해당 위 클래스의 외부에 있으면 컴파일 에러가 발생함, 이는 Singleton 패턴은 프로그래머가 실수를 해도 인스턴스가 1개만 생성되도록 보증하는 패턴이기 때문임
여기서 Singleton 클래스의 유일한 인스턴스를 얻는 메소드로 getInstance가 준비되어 있음, 반드시 이 이름일 필요는 없음, 하지만 이 메서드는 유일한 인스턴스를 얻을 수 있는 메서드임
public class Main {
public static void main(String[] args) {
System.out.println("Start.");
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
if (obj1 == obj2) {
System.out.println("obj1과 obj2는 같은 인스턴스입니다.");
} else {
System.out.println("obj1과 obj2는 다른 인스턴스입니다.");
}
System.out.println("End.");
}
}
여기서 singleton 클래스의 getInstance 메서드를 사용해서 Singleton 인스턴스를 얻고 있음
getInstance 메서드는 두 번 호출되고 있으며, 반환값은 각각 obj1, obj2에 대입됨, 그리고 유일한 인스턴스 1개만 생성되므로 이 인스턴스는 검사하면 무조건 같은 인스턴스임
Retrofit이라던지 아니면 DB Connection의 경우 인스턴스를 프로젝트에서 하나 이상 사용하는 경우 문제가 생길 수 있음, 왜냐면 이를 1개만을 보증하지 않고 여러개의 인스턴스가 생성되거나 쓰인다면 하나의 DB에서 A라고 쓴 것이 다른 곳에서 DB에 인스턴스가 생겨서 B라고 쓸 수 있고 Retrofit에서 특정 URL 연결해서 처리한 통신이 다른곳에서 생성되서 또 다른 통신을 하는 등의 상황이 나올 수 있음
이런 상황에서 싱글톤 패턴을 통해서 인스턴스를 제한하고 활용할 수 있음, 즉 싱글톤 패턴에서 말한대로 인스턴스 1개 임을 보증하고 일부러 제한을 하는 것임
그 예시를 한 번 직접 본다면 먼저 Retrofit을 보면
public class ApiClient{
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
Gson gson = new GsonBuilder()
.setLenient()
.create();
retrofit = new Retrofit.Builder()
.baseUrl(GroundApplication.GROUND_DEV_API)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
return retrofit;
}
}
위와 같이 싱글톤으로 구성함, 이는 private으로 객체를 정의하고 getClient로 인스턴스를 생성함으로써 싱글톤을 구성함, 이런 구성을 통해서 Retrofit 사용에 있어서 기본적인 빌드한 값에 대해서 1개를 보증하고 어디서든 Retrofit 통신을 위한 기본 인스턴스를 쓸 수 있음
이러면 API 통신을 위한 인터페이스에 HTTP 통신에 대한 메서드를 구현하면 실제 사용시 아래와 같이 쓰면 됨
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<LoginResponse> call = apiService.registerAPI(uid, loginType, nickName);
call.enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
}
@Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Log error here since request failed
Log.e("tag", t.toString());
}
});
이를 통해 알 수 있는 강점은 간단하게 getClient를 통해 동일한 Retrofit에 대한 인스턴스를 사용할 수 있는 것, 그리고 그렇게 만든 Retrofit을 인터페이스에 정의된 함수를 활용 쓸 수 있음
만약 이렇게 싱글톤으로 만들지 않았다면 Retrofit을 사용할 모든 곳에서 ApiClient와 같은 방식으로 계속해서 생성 과정을 써주면서 처리해야함
DB도 동일함, 자주 쓰는 RoomDB로 예시를 들어보면 결국 RoomDB를 활용하기 위해서 Entity, DAO, 등을 구성하고 이를 AppDatabase에서 위에서 Retrofit에서 ApiClient처럼 만들어서 활용이 가능함
@Database(entities={User.class}, version=1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
private static AppDatabase INSTANCE;
private static final Object sLock = new Object();
public static AppDatabase getInstance(Context context) {
synchronized (sLock) {
if(INSTANCE==null) {
INSTANCE=Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "Users.db")
.build();
}
return INSTANCE;
}
}
}
db = UserDatabase.getInstance(applicationContext)
db!!.userDao().insert(newUser)
그럼 이 방식도 Retrofit과 같이 Database를 만들어서 직접적으로 DAO를 통해서 Entity에 접근해 처리하는 이 방식을 여기저기서 AppDatabase와 같은 인스턴스 생성을 일일이 하지 않고 하나의 싱글톤을 활용해서 위 방식대로 간단하게 불러와서 처리를 할 수 있는 것임
위와 같이 자주 쓰는 라이브러리와 요소들을 간단한 사용법만 생각해서 그대로 썼지만 싱글톤 패턴을 활용해서 이 방식에 대해서 더 나은 개선을 할 수가 있음