프로그래밍 언어/Kotlin

[코틀린 기초] 코틀린 싱글턴, 동반객체 (object, companion)

코딩하는흑구 2022. 10. 5. 06:29

싱글톤이란

싱글톤 패턴에서 싱글톤, 즉 단하나의 인스턴스를 토대로 개발해나가는 디자인 패턴의 하나이다. 멀티스레드 환경에서도 안전하게 유일한 인스턴스를 가져야합니다.

 

싱글톤을 구현하는 방법은 굉장히 많습니다. 이러한 디자인 패턴은 한가지 언어에 국한되지 않고 다양하게 적용할 수 있기 때문에 싱글톤 패턴이라는 디자인 패턴을 보고싶은 분들의 경우에는 아래의 블로그 포스팅을 참조해주세요.

https://sas-study.tistory.com/478

 

[Java Design Pattern] 자바 디자인패턴, Singleton 싱글톤 패턴

싱글톤 패턴 시스템 런타임이나 환경 셋팅에 대한 정보등 클래스 인스턴스가 여러개일 때 문제가 발생할 수 있는 요구사항이 있다. 이럴 경우 싱글톤 패턴을 활용한다면 인스턴스를 오직 한개

sas-study.tistory.com

 


코틀린에서의 싱글톤

코틀린은 자바처럼 직접 클래스로 구현하는 형태가 아닌 문법적으로 지원해준다. object라는 객체 선언 키워드를 통해 싱글톤을 지원합니다.

object Singleton {
}

 

싱글톤 클래스의 함수나 변수를 사용할 때는 자바의 정적 static에 접근하듯이 클래스 한정자를 사용합니다.

object Singleton {
    val a = 1234

    fun printA() = println(a)
}

fun main() {

    println(Singleton.a)
    Singleton.printA()

// 1234
// 1234
}

 

이러한 객체 선언을 통해 좀더 응용해보자면, static 키워드를 이용하여 XXUtil 클래스를 만들고 사용해본적이 있을 것이다. 

예제) LocalDateTimeUtil

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

object LocalDatetimeUtils {

    val now: LocalDateTime
        get() = LocalDateTime.now()

    const val DEFAULT_FORMAT = "yyyy-MM-dd"

    fun same(a: LocalDateTime, b: LocalDateTime) :Boolean {
        return a == b
    }
    fun getNowString(): String {
        return now.format(DateTimeFormatter.ofPattern(DEFAULT_FORMAT))
    }
}

fun main() {
    println(LocalDatetimeUtils.now)
    println(LocalDatetimeUtils.now)
    println(LocalDatetimeUtils.now)

    println(LocalDatetimeUtils.DEFAULT_FORMAT) // yyyy-MM-dd

    val now = LocalDateTime.now()
    println(LocalDatetimeUtils.same(now, now)) // true

    val nowString = LocalDatetimeUtils.getNowString()
    println(nowString)
}

 


코틀린을 문법을 학습하지 않고 자바에서 싱글톤을 구현하듯이 코틀린에서 클래스를 설계하게 되면 static inner 클래스에서 막히게 된다.

필자도 어?? 왜 inner 클래스 선언이 안돼지?? 하고 당황했던 기억이 있다.

 

코틀린에서는 별도의 inner 클래스 키워드인 companion object 가 있다.

class MyClass {

	companion object {
		
	}
}

- 한 클래스 내에서 한개의 companion object를 선언할 수 있다.

- companion object는 익명 클래스처럼 동작하지만 스스로 이름을 가지게 할 수 있다.

- 이름을 가지게 된다면  접근하는 방법이 달라진다.

 

class MyClass {

    private constructor()

    companion object CompanionObjectName {
        val a = 1234

        fun newInstance() = MyClass()
    }
}

fun main() {
    println(MyClass.a)
    println(MyClass.newInstance())

    // 이렇게도 가능하나 생략 가능
    println(MyClass.Companion.a)
    println(MyClass.Companion.newInstance())
    
    // 이름이 바뀌게 된다면 -> CompanionObjectName
    println(MyClass.CompanionObjectName.a)
    println(MyClass.CompanionObjectName.newInstance())
}

- 여기서 MyClass는 일종의 간단한 싱글턴으로 볼 수 있다.

- companion 동반객체는 클래스당 하나만 존재하므로 사실 부모 클래스로부터의 공간이 이미 확보된 상태이기 때문에 접근하는 방법에 자바의 static 접근 방법과 동일하게 접근할수 있다. 하지만 별도로 이름을 통해 명시해서 접근도 가능하다.

- 이름이 없다면 Companion이라는 키워드로, 이름이 있다면 별도로 이름을 통해 접근할 수 있다.