Singleton 패턴

gang_shik·2022년 3월 11일
0

Singleton 패턴

  • 프로그램 실행시 보통 많은 인스턴스가 생성됨 그러나 클래스의 인스턴스단 하나만 필요한 경우도 있음

  • 시스템 안에서 1개 밖에 존재하지 않는 것을 프로그램으로 표현하고 싶을 때 그럼

  • 여기서 물론 주의를 기울여 인스턴스 생성을 1회만 실행되도록 작성하여 인스턴스 1개만 생성할 수 있지만 그런 상황이 아닐 수도 있음

  • 만약 지정한 클래스의 인스턴스가 절대로 1개 밖에 존재하지 않는 것을 보증하고 싶을 때, 인스턴스가 1개밖에 존재하지 않는 것을 프로그램 상에서 표현하고 싶을 때는 그렇게 주의를 기울인다고 될 문제가 아님

  • 그래서 인스턴스한 개 밖에 존재하지 않는 것을 보증하는 패턴을 Singleton 패턴이라고 함, Singleton은 요소를 1개밖에 가지고 있지 않은 집합을 의미함


예제

  • Singleton은 인스턴스가 1개만 존재하는 클래스임

  • 여기서 -가 붙어있는 것은 Singleton이 private인 것을 명시하기 위해서임

  • getInstance 또한 이 메소드가 static 메소드이기 때문임

Singleton 클래스

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가 준비되어 있음, 반드시 이 이름일 필요는 없음, 하지만 이 메서드는 유일한 인스턴스를 얻을 수 있는 메서드

Main 클래스

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개만 생성되므로 이 인스턴스는 검사하면 무조건 같은 인스턴스임

Singleton의 역할

  • Singleton 패턴에는 Singleton의 역할만이 존재함, Singleton 역할은 유일한 인스턴스를 얻기 위한 static 메서드를 가지고 있음, 이 메서드는 언제나 동일한 인스턴스를 반환

안드로이드?

  • 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로 쓰기 위해 만든 Entity 그리고 이를 DB에 맞게 처리하기 위한 DAO를 직접적으로 활용해서 AppDatabase를 동일하게 싱글톤으로 인스턴스를 얻어서 활용이 가능함, 아래와 같은 식으로
db = UserDatabase.getInstance(applicationContext)
db!!.userDao().insert(newUser)
  • 그럼 이 방식도 Retrofit과 같이 Database를 만들어서 직접적으로 DAO를 통해서 Entity에 접근해 처리하는 이 방식을 여기저기서 AppDatabase와 같은 인스턴스 생성을 일일이 하지 않고 하나의 싱글톤을 활용해서 위 방식대로 간단하게 불러와서 처리를 할 수 있는 것임

  • 위와 같이 자주 쓰는 라이브러리와 요소들을 간단한 사용법만 생각해서 그대로 썼지만 싱글톤 패턴을 활용해서 이 방식에 대해서 더 나은 개선을 할 수가 있음

profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글