[Kotlin] 확장함수를 활용하는 법(how to use Extension Function)

김민주·2023년 5월 30일
1

SSAFYcial

목록 보기
7/8
post-thumbnail

안녕하세요 쭈피셜입니다🖐️
9기 1학기가 종료되었습니다🥲🥲 벌써 싸피에 입과한지 5개월이 지났다니 시간이 정말 빠르네요.

프로젝트하느라 어떻게 지나갔는지 모를 5월이었는데요!
짧은 시간이지만 많은 것을 배울 수 있었고, 만들어 낼 수 있어서 1학기 중 가장 뿌듯했던 기간이었던 것 같습니다💗

이번 쌒다지식에서는 코틀린 확장함수를 활용하는 법에 대해 소개해보겠습니다!

확장함수는 코틀린에서 많이 사용되는 기능 중 하나입니다. 코틀린 표준 라이브러리에서도 확장함수를 폭넓게 사용하고 있습니다.
그럼 확장함수는 왜 사용하는지, 무슨 장점이 있는지에 대해 알아보겠습니다.


확장함수는 사용하는 이유

  1. 코드 가독성 향상
    기존 클래스의 멤버함수처럼 함수를 호출할 수 있기 때문에 읽기 쉬운 코드가 됩니다

  2. 외부 라이브러리 확장
    확장함수를 사용하면 외부 라이브러리의 클래스에드 쉽게 기능을 추가할 수 있습니다. 내가 사용할 기능만 확장시킬 수 있어 업데이트나 유지보수에도 유연하게 대처할 수 있습니다.

  3. 코드의 재사용성
    필요한 기능을 추가하는 로직을 반복해서 작성하지 않고 중복을 줄일 수 있습니다. 기존 클래스를 수정하지 않고도 새로운 기능을 추가 수 있어 유연성이 높아집니다.


확장함수의 단점

  1. 내부 접근 제한
    확장함수는 public 멤버에만 접근할 수 있어, private/protected멤버에는 접근할 수 없습니다. private/protected 멤버에 접근하려면 클래스 내부의 멤버함수로 구 현하는 것이 적합하다.
  2. 상속 불가능
    확장함수는 클래스의 멤버함수처럼 상속되지 않습니다. 즉, 하위 클래스에서 확장함수를 재정의(오버라이드)할 수 없습니다.



이러한 단점도 있으니, 우리는 확장함수를 사용할지 클래스의 멤버함수로 구현할지를 상황에 따라 결정해야 합니다!


상속구조를 가지고 있고 거기에서 확장가능한 기능을 제공해야한다면!
캡슐화와 접근제어를 유지해야한다면! 멤버함수를 사용하고,

외부 라이브러리 또는 프레임워크의 클래스에 기능을 추가해야 한다면!
유틸리티 함수를 작성한다면! 확장함수를 사용하는 것이 좋습니다.


만약, 멤버 함수와 확장함수명이 같으면 멤버함수가 우선순위를 가지게 됩니다!



확장함수 사용법

1. 클래스 확장

안드로이드 View 클래스를 통해서 visibility를 조작한다고 하면 여러 클래스들의 visibility를 코드를 재사용하며 조작할 수 있습니다.

fun View.hide() {
    visibility = View.GONE
}

fun View.show() {
    visibility = View.VISIBLE
}

fun View.isVisible(): Boolean {
    return visibility == View.VISIBLE
}

String class에 대한 확장함수도 사용해보겠습니다.

fun String.isEmailValid(): Boolean {
    val pattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"
    return matches(pattern.toRegex())
}

fun String.toDate(format: String): Date? {
    val sdf = SimpleDateFormat(format, Locale.getDefault())
    return try {
        sdf.parse(this)
    } catch (e: ParseException) {
        null
    }
}

email validation 체크함수와 스트링형의 날짜를 Date형으로 바꾸는 예제입니다.
이번 프로젝트에서도 게시판을 만들다보니 날짜 데이터형식을 지정할 일이 많았는데, 날짜형식을 지정하는 함수들을 사용할 때 편리할 것 같습니다.

SharedPreferences 클래스에 대한 확장 함수로는 값을 저장하고 불러오는 확장함수를 작성해보겠습니다.

fun SharedPreferences.putString(key: String, value: String) {
    edit().putString(key, value).apply()
}

fun SharedPreferences.getString(key: String, defaultValue: String = ""): String {
    return getString(key, defaultValue) ?: defaultValue
}

1-1. 컬렉션 클래스 확장

fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
    val resultList = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) {
            resultList.add(item)
        }
    }
    return resultList
}

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    val evenNumbers = numbers.customFilter { it % 2 == 0 }
    println(evenNumbers) // 출력: [2, 4, 6, 8, 10]

    val oddNumbers = numbers.customFilter { it % 2 != 0 }
    println(oddNumbers) // 출력: [1, 3, 5, 7, 9]
}

조건을 사용자가 제공할 때 조건을 만족하는 요소들만 리스트로 가지고 있는 확장함수를 만들 수 있습니다.


2. Nullable Receiver

nullable타입에 대하여 null처리를 할 수 있습니다.

fun String?.isNullOrBlank(): Boolean {
    return this == null || this.isBlank()
}

3. 외부 라이브러리 확장

외부 라이브러리에 대해 확장함수를 사용하여 기존 라이브러리에 쉽게 기능을 추가할 수 있습니다.

예를 들어 레트로핏 빌더클래스에 인터셉터를 추가하는 확장함수를 구현한다고 해보면,

fun Retrofit.Builder.addCustomInterceptor() {
    val interceptor = Interceptor { chain ->
        // 인터셉터 동작을 정의합니다.
        val request = chain.request()
            .newBuilder()
            .addHeader("Authorization", "minju TOKEN")
            .build()
        chain.proceed(request)
    }
    this.client(OkHttpClient.Builder().addInterceptor(interceptor).build())
}

레트로핏 빌더에 확장함수로 요청 헤더에 토큰을 추가하는 인터셉터를 정의합니다.

*인터셉터: 네트워크 요청 및 응답을 가로채고 조작함


레트로핏 인스턴스를 생성할 때는 위에서 작성한 인터셉터를 추가하여 생성합니다.

val retrofit = Retrofit.Builder()
    .baseUrl("base url 주소")
    .addCustomInterceptor()
    .build()

4. 바인딩 어댑터 확장함수

RecyclerView의 어댑터에서 확장 함수를 사용하여 아이템 뷰를 간단하게 바인딩하는 방법입니다.

class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemLayoutBinding.inflate(inflater, parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.bindItem(item)
    }

    override fun getItemCount(): Int {
        return items.size
    }

    inner class ViewHolder(private val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bindItem(item: String) {
            binding.textView.text = item
        }
    }
}

fun ViewGroup.inflateBinding(layoutInflater: LayoutInflater, @LayoutRes layoutRes: Int, attachToRoot: Boolean = false): ViewDataBinding {
    return DataBindingUtil.inflate(layoutInflater, layoutRes, this, attachToRoot)
}

fun RecyclerView.ViewHolder.getString(@StringRes stringRes: Int): String {
    return itemView.context.getString(stringRes)
}

inflateBinding 확장함수로 간편하게 뷰 바인딩을 할 수 있습니다.
레이아웃 리소스 ID와 함께 호출되어 해당 리소스를 이용하여 뷰바인딩 객체를 생성합니다.

getString 확장함수는 리소스 문자열을 가져옵니다.





코틀린의 확장함수의 사용법, 그리고 안드로이드에서 어떻게 활용될 수 있는지에 대해 알아보았습니다!

계절학기에 못채운 지식들을 보충하면서 알차게 보내는 시간이 되셨으면 좋겠습니다!

profile
𝐃𝐨𝐧'𝐭 𝐛𝐞 𝐚 𝐩𝐫𝐨𝐜𝐫𝐚𝐬𝐭𝐢𝐧𝐚𝐭𝐨𝐫💫

0개의 댓글