이직을 했습니다. 처음 마주한 코드는 SingleTon 형태의 자바 코드였습니다. 10년도 더 된 코드라 자바 코드가 많아 코틀린으로 넘어가려고 노력은 하지만, 점진적으로 천천히 이루어갈 예정이라고 합니다. 제가 면접때 강점을 마이그레이션이라고 표현했던 게 기억이 났습니다. 왜 이 곳에 합격했는지 알게되었고, 제가 할 수 있는 일이 좀 있겠다는 생각이 들었습니다.
그래서 그런 김에, 간만에 학생때 공부하던 SingleTon의 개념과, 그를 이용한 Kotlin의 Object에 대해 정리해야겠다는 생각이 들었습니다.
먼저 코틀린의 Object 키워드가 무엇인지 알아야 할 것 같습니다.
https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview
코틀린 문서에서의 object입니다. 공식 문서에서도 나와있다 시피 object declarations은 클래스를 싱글톤 형태로 생성하는 것을 의미합니다. 그렇다면 싱글톤이 무엇인지 알아야겠죠.
싱글톤(SlngleTon) 패턴
싱글톤이란, 객체의 인스턴스가 오직 1개로 구성되는 디자인 패턴을 의미합니다. 객체를 처음 사용할 때는 인스턴스를 생성하고, 그 후에는 미리 생성된 인스턴스를 계속 불러와 재사용하는 개념입니다. 이는 코드로 확인하면 더 간단합니다.
public class PrintUtil {
private static PrintUtil instance;
private PrintUtil() {
// 생성자는 외부에서 호출못하게 private 으로 지정해야 한다.
}
public static PrintUtil getInstance() {
if(instance == null) instance = new PrintUtil();
return instance;
}
public void print() {
System.out.println("hello world");
}
}
//외부에서는 PrintUtil.getInstance().print();로 사용할 수 있다.
위와 같은 코드로, getInstance를 호출할 때 instance 객체를 확인해 null인 경우 새로 호출해 instance를 반환하는 형태로 getInstance가 작성되어 있고, 이를 PrintUtil.getInstance()로 이용하면 항상 PrintUtil이 저장하고 있는 instance를 받을 수 있습니다.
위와 같이 싱글톤 패턴은 일반적으로 클래스의 생성자를 private으로 선언하고, 클래스 안에 static으로 자기 자신에 대한 레퍼런스를 두어 그 레퍼런스를 참조하게 하는 식으로 구현된다고 볼 수 있습니다.
이렇게 싱글톤 패턴을 사용해 하나의 인스턴스로 관리하는 것의 장점에는 하나의 인스턴스를 사용함으로 인해 클래스의 자원(데이터)의 공유가 쉽다는 점이 있습니다. 하나의 인스턴스 변수에 미리 생성해 둔 인스턴스를 저장해두니 그 클래스의 인스턴스를 불러오는 것 만으로 어디서 호출하든 그 클래스가 가지고 있는 모든 정보를 불러올 수 있습니다. 또한 하나의 인스턴스만을 사용함으로서 메모리에 올라가는 리소스를 줄일 수 있다는 점이 있습니다. 굳이 여러번 호출할 필요가 없는 코드를 사용할 때 마다 초기화하고 호출하는 것 보다는 미리 만들어둔 하나의 인스턴스를 재활용하는 것이 무조건적으로 효율적일 것입니다. 또한, 처음 로딩을 하고 난 이후부터는 추가적인 세팅이나 로딩을 필요로 하지 않기 때문에 초기화 부분의 생략이 가능하다는 장점도 있습니다.
이러한 장점들을 조합해 봤을 때 싱글톤 패턴을 이용하는 클래스는 주로 초기에 넣어야 할 데이터가 없고 데이터의 변경이 자주 일어나지 않고 주로 참조가 되며, 독립적으로 호출할 수 있는 메소드 및 필드가 있는 클래스의 경우에 사용했을 때 효율을 발휘할 수 있습니다.
하지만 다른 디자인 패턴도 그렇듯 싱글톤 패턴에도 당연하게 단점이라는 것이 존재하는데요. 가장 중요한 것은 싱글톤 패턴은 하나만 생성되어야 하지만, 단일 스레드가 아닌 멀티 스레드에서 생성하는 경우, 실제로 클래스의 인스턴스가 1개만 생성된다는 것을 보장할 수 없다는 점입니다. 물론 코틀린으로 안드로이드 개발을 주로 하는 저에게는 멀티스레드를 직접 구현해서 사용할 일은 드문데다, 네트워크 통신이나 파일 관리 등은 대부분 잘나온 오픈소스(retrofit, hilt 등)를 사용하니 더더욱 이로 인한 실수가 나올 확률은 크지 않습니다.(그래서인지 비슷한 문제가 생기면 헤멜 일이 많습니다. 하하) 하지만 일반적인 서버 환경에서는 중요한 부분인지라, 스프링의 경우 빈을 통해 프레임워크 단에서 싱글톤을 관리해주기 때문에 요즘에는 이 부분으로 인해 크게 문제를 겪기는 어려워 보입니다. 물론 이를 방지하기 위한 여러가지 방지책(volatile annotation, synchronized, holder 등)들이 있으니 그 부분까지 같이 공부한다면 실수를 많이 줄일 수 있습니다.
추가적으로 자기 자신의 생성자를 private로 두었기 때문에 자바나 코틀린(애초에 open 키워드 없이 상속도 어렵지만)에서 상속을 받는 것에 어려움을 가지게 됩니다. 이는 싱글톤 패턴이 private한 생성자와 static한 필드 및 메소드를 가지는 이상 객체지향 언어의 특성을 살릴 수 없게 되므로 언어에 대한 매력을 떨어뜨리는 요소가 될 수 있습니다.
또한 기술적으로 에러를 발생시키는 코드는 아니지만, 싱글톤으로 만들어지는 클래스의 경우 기본적으로 자기 자신의 인스턴스를 관리하는 역할과 작업을 처리하는 역할 2개를 같이 맡게 되는 것이므로 객체 지향의 5원칙인 SOLID중 단일 책임(Single Responsibility Principle)을 위반하게 됩니다. 객체 지향 언어의 디자인 패턴인 주제에 사용하는 것 만으로 객체 지향의 5원칙을 무시한다니 좀 의아하지만, 당연하게도 모든 규칙과 패턴, 아키텍쳐에는 어느 정도 융통성이 있어야 하기 때문에 이러한 이유 때문에 쓸 수 없다 이런 느낌은 받지 않아도 되겠습니다.
Object의 특징
그렇다면 위와 같은 싱글톤 패턴을 적용시켜주는 코틀린의 object 키워드는 무엇인지 알아보겠습니다. 공식 문서에서는 아래와 같이 정의하고 있습니다.
The Singleton pattern can be useful in several cases, and Kotlin makes it easy to declare singletons
간단하게 말해서 싱글톤을 쉽게 선언할 수 있도록 예약된 키워드입니다. 자바에서 복잡하게 INSTANCE를 만들어서 getInstance를 통해 사용하던 것들을 object 키워드 하나로 해결하게 해주겠다는 의미죠.
아무래도 싱글톤 패턴을 구현하는 키워드이다 보니, 아마 추측상으로는 기본적으로 구현하는 싱글톤 패턴처럼 처음 액세스시에 인스턴스를 생성하는 늦은 초기화를 지원해줄 것 같은데요. 공식문서에 인스턴스 생성 시점에 대한 이야기가 나와 있습니다.
The initialization of an object declaration is thread-safe and done on first access.
개체 선언의 초기화는 스레드로부터 안전하며 처음 접근할 때 완료됩니다.
공식문서의 설명에 따라보니, 기존의 싱글톤처럼 인스턴스에 처음 접글할 때 완료되며, 스레드로부터 안전하게(싱글톤 패턴의 주의사항이 멀티 스레드라는 점을 생각하면 이를 코틀린에서는 내부적으로 처리해준다는 것으로 알 수 있겠습니다.) 보호해주는 특징이 있다고 합니다.
이 object 키워드를 통해 생성한 인스턴스는 기존 자바의 싱글톤처럼 getInstance를 이용해 사용하는 것이 아니라 바로 ClassName.method() 형태로 사용할 수 있습니다. 물론 자바에서는 그런 기능이 없지만, ClassName.INSTANCE.method()의 형태로 사용할 수 있어 자바에서는 기존 싱글톤과 유사하게 사용할 수 있습니다.
또한 object를 통해 익명 객체를 생성할 수 있지만, 이는 이 글에서 설명하고자 하는 object 키워드와 다른 개념이기 때문에 시간이 날 때 작성해보도록 하겠습니다.
추가적으로 설명해야 할 부분들이 있을까 싶은데, 특별하게 생각하지 않고 써와서 그런지 추가적으로 떠오르는 부분들이 별로 없네요. 생각이 날 때 마다 추가사항으로 넣어야 할 것 같습니다.
'안드로이드 > 코틀린' 카테고리의 다른 글
AAC Navigation의 특징 (0) | 2023.06.02 |
---|---|
JvmStatic 어노테이션 (0) | 2023.03.16 |
Coroutine 관련 설명 글 링크 (0) | 2022.06.30 |
(Android) square - Retrofit (0) | 2022.05.18 |
안드로이드 startActivityForResult의 대체 (0) | 2022.04.24 |
댓글