본문 바로가기
Android & Kotlin/Kotlin

[Kotlin] 코틀린 object/Companion Object(동반 객체)/Anonymous Object(무명 객체)

by 말린밴댕이_공부 2024. 5. 8.
반응형

객체 선언(Object Declaration) - 싱글 톤(Singleton)만들기

객체 선언은 클래스를 정의하고 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 단 한문장으로 처리합니다.

 

단, 생성자는 객체 선언에 쓸 수 없다 → 생성자 호출없이 즉시 인스턴스 생성

(ex) object NoConstructor(val id : Int) { /**/ } → 안됨.)

data class Product(val id : Int, val name :String, val price : Int)

object ProductRepo {
    private val products = mutableListOf<Product>()

    fun addProduct(product: Product){
        products.add(product)
    }

    fun getAllProducts() : List<Product>{
        return products.toList()
    }
}

fun main() {
    ProductRepo.addProduct(Product(1,"상품", 10000))
    ProductRepo.getAllProducts()
}

ProductRepo 인스턴스를 따로 생성하지 않고 싱글턴 객체를 사용할 수 있습니다.

Compaion Object(동반 객체)

 

코틀린 언어는 자바 static 키워드를 지원하지 않지만 패키지 수준의 최상위 함수와 객체 선언을 지원한다

.

따라서 클래스 인스턴스와 관계없이 호출해야 하지만 클래스 내부 정보에 접근해야 하는 함수가 필요할 때는 클래스에 중첩된 객체 선언의 멤버 함수로 접근해야 한다.

 

클래스 안 companion object를 붙히면 동반 객체를 만들 수 있으며 이름은 따로 지정할 필요는 없다.

 

간단한 예시

class Test {
    companion object {
        fun printTest() {
            println("Companion Object 메서드 호출")
        }
    }
}

Test.printTest()

 

compnaion object는 private 자신을 둘러싼 클래스의 모든 private 멤버에 접근 할 수 잇다.

클래스의 private 생성자도 같이 호출할 수 있다 → 팩토리 패턴을 구현하기 적합하다.

 

 

 

예제로 부 생성자 2개가 있는 클래스를 보고난 후 그 클래스를 Companion Object안에서 팩토리 클래스를 정의하는 방식으로 변경을 하는 예시

class User{
    val nickname : String

    constructor(email : String){
        nickname = email.substringBefore('@')
    }

    constructor(id : Int){
        nickname = id.toString()
    }
}
class User(val nickname : String){
    companion object{
        fun newEmailUser(email : String) = User(email.substringBefore('@'))
        fun newIdUser(id : Int) = User(id.toString())
    }
}

    User.newIdUser(1234)
    val emailUser = User.newEmailUser("밴댕이@asdf.com")
    println(emailUser.nickname) //밴댕이

 

두 클래스를 한 클래스로 합치면서 사용자 객체를 생성하는 방법인 부 생성자가 여럿 있는 클래스를 보고

클래스의 인스턴스를 생성하는 팩토리 메서드를 통해 companion object의 메서드를 호출 하는 것을 보았다.

 

생성할 필요가 없는 객체를 생성하지 않을 수 있는 팩토리 메서드이다.

 

이메일 주소별로 유일한 User 인스턴스를 만드는 경우 팩토리 메서드가 이미 존재하는 인스턴스에 해당하는 이메일 주소를 전달 받으면 새 인스턴스를 만들지 않고 캐시에 있는 기존 인스턴스 반환

 

 

Companion Object Interface 구현

Companion Object도 인터페이스를 구현할 수 있으며 위의 사용한 예시를 그대로 들고 변형시켜서 만들어보자

interface UserFactory<T> {
    fun createUser(input: T): User
}

class User private constructor(val nickname: String) {
    companion object : UserFactory<Any> {
        override fun createUser(input: Any): User {
            return when (input) {
                is String -> newEmailUser(input)
                is Int -> newIdUser(input)
                else -> throw IllegalArgumentException("Invalid input")
            }
        }

        private fun newEmailUser(email: String): User {
            return User(email.substringBefore('@'))
        }

        private fun newIdUser(id: Int): User {
            return User(id.toString())
        }
    }
}

다음과 같이 User은 UserFactory 구현을 제공할 수 있다.

안드로이드 companion Object Interface 예시

interface DomainMapper<in R : com.avengers.data.model.base.BaseDataModel, out D : BaseDomainModel> {
    fun R.toDomainModel() : D
}

data class FollowListResponse(
    val nickName: String,
    val region: String,
    val isFollow: Boolean,
    val profileImage: String
) : BaseDataModel {
    companion object : DomainMapper<FollowListResponse, FollowListData> {
        override fun FollowListResponse.toDomainModel(): FollowListData = FollowListData(
            nickName = nickName,
            region = region,
            isFollow = isFollow,
            profileImage = profileImage
        )
    }
}

서버와의 통신을하는 데이터 모델에서 도메인 모델로 매핑하는 도메인 메퍼 Interface를 구현을 하고 있습니다. 데이터 모델 타입을 입력 파라미터 도메인 모델 타입을 반환값으로 사용하는 매핑을 하는 예시입니다.

 

Companion Object 확장함수 정의

class People(val name : String, val age : Int){
    companion object{
		    //~~
    }
}

fun People.Companion.getInformationFromJson(json: String) : People {
    //정보 얻는 로직
}

val bendeng = People.getInformationFromJson("{~~~~~~~~}")

getInfromationFromJson은 클래스 밖에서 정의한 확장함수로 클래스 멤버함수가 아니다.

 

무명 객체(Anonymous Object)

 

무명 객체는 이름이 없는 클래스를 생성하는데 사용하며 자바에서는 무명 내부 클래스로 이벤트 리스너를 구현할 때 사용합니다.

 

이를 코틀린에서는 무명 객체로 대체하여 사용하며 이벤트 리스너를 예시로 보겠습니다.

public static interface OnClickListener {
    void onClick(android.view.View view);
}

//코틀린
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View) {
        // 버튼이 클릭되었을 때 수행할 동작
    }
})

//코틀린 람다
button.setOnClickListener {
    // 버튼이 클릭되었을 때 수행할 동작
}

위의 예시는 버튼을 클릭했을 때 발생하는 이벤트를 처리하기 위해 사용이 됩니다.

 

View.onCLicklistener 인터페이스를 구현한 객체 파라미터를 통해 setonClicklistener 메서드가 받습니다.

 

onClick메서드가 정의되어 있으며 사용자가 뷰를 클릭할 때 호출이 됩니다.

(추가적으로 interface OnClickListener는 메서드가 하나뿐인 인터페이스인 SAM이므로button.setOnClickListener 람다를 사용이 가능해진다.)

반응형

댓글