[Android] Destructuring 자세히 알아보자.

2

오늘 할 주제는 Destructuring 에 관한 내용입니다.

Destructuring은 무엇일까요?

디스트럭처링(Destructuring)은 구조화된 배열 또는 객체를 Destructuring(비구조화, 파괴)하여 개별적인 변수에 할당하는 것이다.
배열 또는 객체 리터럴에서 필요한 값만을 추출하여 변수에 할당하거나 반환할 때 유용하다.

설명만으로는 와닿지 않는것 같으니, 예시를 한번 알아볼까요?

어떻게 사용할 수 있을까?

지금 가지고 있는 Data Class는 구조화 되어있습니다.

val person = Person(1, "Jon Snow", 20)

만약 해당 프로퍼티를 변수로 지정해서 사용하고자 한다면, Destructuring이 좋은 해결책일 수 있습니다.



val person = Person(2, "ch8n", 25)

val id  = person.id
val name = person.name
val age = person.age

-------------------------------------------
//무엇이 더 readable 할까요~~?

val person = Person(1, "Jon Snow", 20)
val (id, name, age) = person



println(id)     //1
println(name)   //Jon Snow
println(age)    //20

Destructuringcomponent가 제공되는 함수의 리턴값에서도 사용 가능합니다.

// pair는 componentN을 지원한답니다!
fun twoValuesReturn(): Pair<Int, String> {
    // ...
    return Pair(1, "success")
}

val (result, status) = twoValuesReturn()

DestructuringList, Map에서도 사용할 수 있습니다.

//Map
val map: HashMap<Int, Person> = HashMap()
 map.put(1, person)

//키와 값을 쉽게 Destructuring 시킬수 있다.
 map.forEach { entry ->
 val (key, value) = entry
 }

// List or Arrays 
val threeItemList = listOf("one", "two", "three")
//Destructuring을 통해 0번째 자리와 1번째 자리 item을 가져올수 있다.
val (itemOne, itemtwo) = threeItemList

componentN을 지원하지 않는 Destructuring일반 Class도 componentN 을 구현함으로써 , Destructuring을 직접 구현할 수 있습니다.

class Person1(
    val i: Int,
    val s: String
) {
    operator fun component1() = i
    operator fun component2() = s
}

//data class와 똑같이 구현 가능하다.
val person = Person1(1, "Jon Snow")
val (i, s) = person

왜 이런일이 가능할까?

Kotlin은 어떻게 이런일을 가능하게 만들까요?
DataClass의 주 생성자의 프로퍼티에 대해 컴파일러가 알아서 Component를 만들어주기 때문입니다.

public final data class Pair<out A, out B> public constructor(first: A, second: B) : kotlin.io.Serializable /* = java.io.Serializable */ {
    public final val first: A /* compiled code */

    public final val second: B /* compiled code */

//자동으로 만들어주었음
    public final operator fun component1(): A { /* compiled code */ }

    public final operator fun component2(): B { /* compiled code */ }

    public open fun toString(): kotlin.String { /* compiled code */ }
}

---
public final data class Person public constructor(i: kotlin.Int, s: kotlin.String, i1: kotlin.Int) {
    public final val i: kotlin.Int /* compiled code */

    public final val i1: kotlin.Int /* compiled code */

    public final val s: kotlin.String /* compiled code */

//자동으로 만들어주었음
    public final operator fun component1(): kotlin.Int { /* compiled code */ }

    public final operator fun component2(): kotlin.String { /* compiled code */ }

    public final operator fun component3(): kotlin.Int { /* compiled code */ }
}

그래서 Destructuring을 사용하면, 프로퍼티의 위치를 기반(가장 중요)으로 componentN을 호출하게 됩니다.

      Person var4 = this.person;
      int id = var4.component1();
      String name = var4.component2();
      int age = var4.component3();

List나 Map은 아래와 같이 컴파일 됩니다.

for(Iterator var14 = $this$forEach$iv.entrySet().iterator(); var14.hasNext(); var19 = (Person)element$iv.getValue()) {
	element$iv = (Entry)var14.next();
	int var17 = false;
	int key = ((Number)element$iv.getKey()).intValue();
     }

String[] var21 = new String[]{"one", "two", "three"};
List threeItemList = CollectionsKt.listOf(var21);
String itemOne = (String)threeItemList.get(0);
String itemtwo = (String)threeItemList.get(1);

이제 단점에 대해서 알아보자!

Destructuring를 사용하면서 코드수가 줄고, 조금 더 이쁜코드가 되었습니다.
그런데 왜 사람들은 Destructuring을 잘 활용하지 않을까요??

문제는 Kotlin에서는 위치 구조 분해를 사용하기 때문에 일어납니다.

처음 시작으로 돌아봅시다.

    val person = Person(id = 2, name = "ch8n", age = 25)

    fun start() {
        val id = person.id
        val name = person.name
        val age = person.age

//        -------------------------------------------
// 무엇이 더 readable 할까요~~?

        val (id2, name2, age2) = person

        println(id) // 1
        println(name2) // Jon Snow
        println(age2) // 20

        println(id2) // 1
        println(name2) // Jon Snow
        println(age2) // 20
    }

이때, 만약 Person Class새로운 프로퍼티가 추가되면 어떻게 될까요?

data class Person(
    val id: Int,
    val name: String,
    //젠더가 추가되었다.
    val gender: Boolean = true,
    val age: Int
)
    val person = Person(id = 2, name = "ch8n", age = 25)

    fun start() {
        val id = person.id
        val name = person.name
        val age = person.age

//        -------------------------------------------
// 무엇이 더 readable 할까요~~?

        val (id2, name2, age2) = person

        println(id) // 2
        println(name) // Jon Snow
        println(age) // 25

        println(id2) // 2
        println(name2) // Jon Snow
        println(age2) // true
    }

componentN이 프로퍼티 위치를 기반으로 생성되기 때문에, 중간에 프로퍼티가 추가되면
해당 변수는 다른 프로퍼티를 참조하게됩니다.

줄여서 얘기하자면 , Destructuring 은 기존에 작성된것보다 리팩토링을 많이 불러옵니다.

개발에서는 이런말이 존재합니다.

keeping in mind having maintainable code has higher priority than writing less boilerplate or syntactic sugar
쉽게 표현된 설명이나, 보일러 플레이트를 줄이는것보다 유지보수가 훨씬 높은 가치를 둔다는것을 마음속에 깊이 새겨라

해당 단점을 생각하면서 중요한 부분에 잘 사용하는것이 좋을것 같습니다.


참고

코틀린(Kotlin) - 구조 분해 선언과 component 함수

Kotlin Unpacking Params

You might be creating your states wrong! — Jetpack Compose

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글