본문 바로가기

기타/안드로이드

Kotlin의 class, property delegation

Kotlin의 class와 property delegation

Kotlin에서 클래스 및 객체 사용  |  Android Developers

 

Kotlin에서 클래스 및 객체 사용  |  Android Developers

Kotlin에서 클래스 및 객체의 사용 방법을 알아봅니다.

developer.android.com

위의 페이지를 참고하여

객체지향프로그래밍의 가장 중요한 개념인 class에 대해 짚고 넘어가자

그리고 class를 이용한 기법 중 하나인 property delegation도 확인해보자

 

클래스의 정의와 선언

클래스의 기본 정의와 인스턴스 선언은 다음과 같다

fun main() {
    val jc = JustClass()
}

class JustClass {

}

모든 단어의 시작이 대문자로 시작하는 PascalCase로 이름을 지어야 한다

val로 선언하면 변수에 할당된 대상객체를 변경할 수는 없지만 객체의 상태(속성 등)를 변경할 수는 있다

c로 생각하면 변수에 저장된 객체의 주솟값, 포인터는 변경이 안되는데 그 주소에 있는 객체의 내용은 변경이 가능하다

 

 

메소드의 정의와 호출은 다음과 같다

fun main() {
    val jc = JustClass()
    jc.sayHello()
}

class JustClass {
    fun sayHello() {
        println("hello")
    }
}

 

 

 

속성은 그냥 클래스 내부에 변수를 선언하면 되는데

kotlin은 속성의 getter와 setter를 자동으로 만들어준다고 한다

내가 원하는 방식으로 getter와 setter를 만들고 싶으면 다음과 같이 해주면 된다

fun main() {
    val jc = JustClass()
    jc.sayHello()
}

class JustClass {
    var name = "nonaninona"
        get() = field
        set(value) {
            if(value is String)
                field = value
        }
    fun sayHello() {
        println("hello ${name}")
    }
}

name을 설정할 때(setter에서) 데이터타입이 String이 맞는지 확인하는 구문을 추가한 예시이다

 

중요한 것은 getter나 setter 안에서 property를 명시할 때

name이나 this.name이 아니라 field라는 친구를 사용한다는 것이다

얘를 필드변수라고 부른다

 

 

클래스의 생성자

kotlin의 생성자는 하나의 기본생성자와 몇 개든 가능한 보조생성자로 이루어진다(0개 이상)

 

기본생성자를 생성하려면 다음과 같이 뒤에 constructor()를 붙여줘야 한다

fun main() {
    val jc = JustClass()
    jc.sayHello()
}

class JustClass constructor() {
    var name = "nonaninona"
    var age = 30

    fun sayHello() {
        println("hello ${name}, your age is ${age}")
    }
}

그러나 매개변수가 있으면 constructor까지

매겨변수가 없으면 constructor()까지 생략해도 된다

그러니까 매개변수가 없으면 그냥 이름만 띡 써놓으면 된다

 

매개변수가 있는 경우는 이름 뒤에 괄호를 붙여서 매개변수를 명시해줘야 한다

이때 매개변수를 초기화는 방법이 class body에서 초기화, 매개변수 자체에서 바로 초기화 두 가지로 나뉜다

먼저 class body에서 초기화 하는 경우를 보겠다

fun main() {
    val jc = JustClass("nonaninona")
    jc.sayHello()
}

class JustClass(name: String) {
    var age = 30
    var name = name

    fun sayHello() {
        println("hello ${name}, your age is ${age}")
    }
}

이렇게 매개변수를 명시해놓고

클래스 본문에서 변수에 대입해주면 된다

var name = name에서 왼쪽 name은 클래스 멤버변수, 오른쪽 name은 매개변수이다

 

다음으로 매개변수 자체에서 바로 초기화 하는 경우를 보겠다

fun main() {
    val jc = JustClass("nonaninona")
    jc.sayHello()
}

class JustClass(val name: String) {
    var age = 30

    fun sayHello() {
        println("hello ${name}, your age is ${age}")
    }
}

이처럼 매개변수를 변수 선언하듯이 써놓으면 자동으로 할당이 된다

이게 훨씬 쉬우니까 매개변수를 받아서 따로 가공 후 변수에 넣어주는 경우가 아닌이상

이 방법을 애용하자

 

추가로 init 블럭이라는 친구를 좀 보고 가자

코틀린의 init 블럭은 클래스 내부에서 정의하는 함수로 매개변수와 리턴값이 없다

그냥 실행만 하는 친구인데 호출되는 타이밍이 중요하다

기본생성자가 호출된 직후에 바로 호출이 된다

그러니까 이름답게 인스턴스가 생성된 직후에 뭔가 할 일이 있으면 init 블럭에 넣어주면 된다

fun main() {
    val jc = JustClass("nonaninona")
}

class JustClass(name: String) {
    var age = 30
    var name = name

    init {
        println("hello my name is $name and my age is $age")
    }
}

<출력결과>

hello nonaninona, your age is 30

 

보조생성자는 클래스 내에서 constructor 키워드로 정의한다

얘는 정의할 때 키워드를 생략할 수 없다

그리고 기본생성자를 상속하는 느낌으로 만들어준다

뒤에 : this(매개변수)를 붙여줘야 한다

사실 이 this가 기본생성자인셈이다

거두절미하고 예시를 보자

fun main() {
    val jc = JustClass("nonaninona")
    jc.sayHello()
    val jc2 = JustClass(20, "hong")
    jc2.sayHello()
}

class JustClass(val name: String) {
    var age = 30

    constructor(age: Int, name: String) : this(name){
        this.age = age
    }
    fun sayHello() {
        println("hello ${name}, your age is ${age}")
    }
}

이런 느낌으로 해주면 된다

결국 객체를 만드는 행위를 하는 것을 기본생성자에게 위임하는 것이라고 보면 되겠다

 

클래스의 상속

상속관계를 만드려면 슈퍼클래스(상위클래스)에서 open이라는 키워드를 넣어주어야 가능하다

Java에서 상속 못하게 final이라는 얘를 넣었던 거랑은 반대인 개념이라고 생각하면 되겠다

그리고 서브클래스(하위클래스)를 정의하려면 클래스 헤더를 서브클래스 : 슈퍼클래스 형식으로 작성해야한다

fun main() {
    val sub = SubClass()
    sub.sayHello()
}

open class SuperClass{
    open fun sayHello(){
        println("hello, this is superclass")
    }
}

class SubClass : SuperClass(){
    override fun sayHello(){
        println("hello, this is subclass")
    }
}

그리고 메소드를 override 하려면 역시 open 키워드를 붙여줘야한다

안 그러면 final이라고 에러가 난다

 

위와 같은 is-a, 상속 관계 뿐 아니라 has-a, 포함 관계도 있다

그냥 parentclass의 속성에 childclass가 들어가있는 관계다

fun main() {
    val parent = ParentClass()
    parent.sayHello()
}

class ParentClass(val child: ChildClass = ChildClass()){
    fun sayHello(){
        println("hello, this is parentclass")
        child.sayHello()
    }
}

class ChildClass{
    fun sayHello(){
        println("hello, this is childclass")
    }
}

이처럼 말이다

 

super 키워드를 통해서 슈퍼클래스의 메소드 등에 접근이 가능하다

또한 변수를 override 할 수도 있다

fun main() {
    val sc = SubClass()
    sc.sayHello()
}

open class SuperClass{
    open val name = "unknown"
    open fun sayHello(){
        println("hello, ${name}")
    }
}

class SubClass() : SuperClass()  {
    override val name = "nonaninona"
    override fun sayHello(){
        super.sayHello()
        println("hello, ${name}")
    }
}

<결과>

hello, nonaninona

hello, nonaninona

 

 

공개상태수정자

공개상태수정자란 private, protected, public 이런 걸 말하는 건데 internal이라는 애도 있다고 한다

internal은 동일한 모듈까지만 접근가능하게 해준다고 하는데 모듈이 뭐지? 찾아봤지만..

 

참고: 모듈은 소스 파일 및 빌드 설정으로 구성된 모음이며, 이를 통해 프로젝트를 별개의 기능 단위로 분할할 수 있습니다. 프로젝트에는 모듈이 하나 이상 포함될 수 있습니다. 각 모듈을 독립적으로 빌드, 테스트, 디버그할 수 있습니다.

패키지는 기본적으로 관련 클래스를 그룹화하는 디렉터리나 폴더이고, 모듈은 앱의 소스 코드, 리소스 파일, 앱 수준 설정을 위한 컨테이너를 제공합니다. 한 모듈에 여러 패키지가 포함될 수 있습니다.

 

출처 : https://developer.android.com/codelabs/basic-android-kotlin-compose-classes-and-objects?hl=ko&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-2-pathway-1%3Fhl%3Dko%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-classes-and-objects#7 

 

Kotlin에서 클래스 및 객체 사용  |  Android Developers

Kotlin에서 클래스 및 객체의 사용 방법을 알아봅니다.

developer.android.com

...라고 하는데 뭐 솔직히 내 수준에서 잘 이해는 안된다

그냥 패키지를 모으면 모듈이라고 생각하자

 

 

아무튼 공개상태수정자는 4가지 종류가 있고 속성, 클래스, 메소드 앞에 붙일 수 있다

이런 느낌으로 말이다

fun main() {
    val sc = SubClass()
    sc.sayHelloInSub()
}

open class SuperClass{
    private var justVar1 = 1
    var justVar2 = 2
        private set
    protected fun sayHello(){
        println("hello ${justVar1}, ${justVar2}")
    }
}

class SubClass : SuperClass(){
    fun sayHelloInSub(){
        super.sayHello()
        //justVar1 = 3
        println(justVar2)
    }
}

justVar1과 justVar2를 보면서

속성에 공개상태수정자를 붙이는 방법이 두 가지라는 것을 확인하자

private으로 선언된 justVar1에는 당연히 SubClass가 접근할 수 없다

justVar2는 setter만 private 상태이기 때문에 SubClass에서 값을 읽어올 수는 있다

그리고 getter에 공개상태수정자를 붙이는 경우 속성자체와 getter함수의 공개상태가 일치해야한다

 

 

인터페이스

interface는 코틀린에도 있다

기본적으로 abstract method(메소드 이름과 타입만 있는, 헤더만 있는 메소드)와

accessor(setter, getter)나 abstract property(이름과 타입만 있는 변수)를 가지고 있다

abstract class라는 친구가 interface와 유사하게 생겼는데

abstract class는 값이 있는 속성을 가질 수 있다는 점에서 차이가 있다

 

interface의 문법은 다음과 같다

fun main() {
    val jc = JustClass()
    jc.justFun()
}

interface justInterface{
    var justVar: Int
    fun justFun(): Unit
}

class JustClass : justInterface{
    override var justVar = 1
    override fun justFun(){
        println("just Function")
    }
}

interface를 상속한 클래스가 구현을 하고 있는 모습을 볼 수 있다

 

property delegation

이 interface는 property delegation에 자주, 유용하게 쓰인다고 한다

근데 property delegation이 뭘까?

내가 지금까지 이해한 바로는 속성 값의 getter-setter 로직을 재활용하는 행위이다

 

예를 들어 문자열을 입력받는데 공백 없이 입력 받고 싶어서 setter 함수에 공백 제거처리 로직을 구현했다고 해보자

그런데 이 로직을 모든 문자열 속성을 입력받는데 이용하고 싶다면?

각각의 속성의 setter 함수마다 이 로직을 복붙하기는 싫으니까

interface를 활용한 property delegation을 이용하면 된다는 것이다

 

그럼 그걸 어떻게 쓰느냐 하면.. 처음 보니까 좀 어렵긴 하다..

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

fun main() {
    var strVar by TrimDelegation()
    strVar = "       hello"
    println(strVar)
}

class TrimDelegation() : ReadWriteProperty<Any?, String>{

    var fieldValue = ""

    override fun setValue(
        thisRef : Any?,
        property : KProperty<*>,
        value : String
    ){
        fieldValue = value.trim()
    }

    override fun getValue(
        thisRef : Any?,
        property : KProperty<*>
    ): String
    {
        return fieldValue
    }
}

<결과>

hello

일단 차근차근 보자

KProperty는 속성을 표현하는 인터페이스고

ReadWriteProperty는 property delegation할 때 쓰는 인터페이스다

fieldValue는 앞서 소개했던 setter, getter가 관여하는 필드변수이다

그냥 내 맘대로 지은 이름이다

Any는 Kotlin의 모든 객체의 상위클래스로, Java의 Object와 비슷한 친구이다

KProperty하고 꺽쇠 안에 있는 애스터리스크, 별표는 '모든 타입'을 뜻하는 generic을 의미한다

 

위 예제처럼 property delegation을 이용하고 싶다면

setValue, getValue는 ReadWriteProperty의 상세에 따라

매개변수를 저런식으로 작성하면 된다

위의 예제의 setter를 보면 입력받은 value에 공백을 지우고 필드변수에 넣는다

 

TrimDelegation 클래스를 전부 이해했다면

main 함수를 보면 된다

저런 식으로 by라는 키워드를 이용해서 strVar이라는 변수에 setter-getter 로직을 넣어줄 수 있다

그리고 값을 할당해보면 공백 없이 문자열이 들어간 것을 알 수 있다

'기타 > 안드로이드' 카테고리의 다른 글

Dice Roller App - remember API  (0) 2022.10.14
Kotlin의 lambda expression  (0) 2022.10.14
Kotlin의 null  (0) 2022.10.14
Kotlin의 조건문  (0) 2022.10.14
UI 구성 실습 - 블로그 명함 UI 작성  (0) 2022.10.14