2023. 12. 18. 14:14ㆍ프로그래밍 언어/Kotlin
안드로이드 스튜디오 개발자 페이지를 기반으로 작성됨.
https://developer.android.com/courses/pathways/android-basics-compose-unit-1-pathway-1?hl=ko
Kotlin 프로그래밍 소개 | Android Basics Compose - First Android app | Android Developers
Kotlin에서 Android 앱 빌드를 준비하기 위해 Kotlin의 입문 프로그래밍 개념을 알아봅니다.
developer.android.com
이번 블로그에서는 Kotlin에서 클래스와 객체를 사용하는 방법에 대해서 살펴보도록 하자.
필자가 프로그래밍에서 처음으로 클래스에 대해서 공부했을 때에는 붕어빵과 붕어빵 틀로 클래스를 비유하며 배웠었다.
붕어빵 틀? 붕어빵 기계가 붕어빵이라는 특정 객체를 만들어내는 어떤 설계된 기계, 대명사라고 할 수 있다. 이러한 대명사를 프로그래밍에서는 클래스라고 부른다.
붕어빵 기계를 통해 만들어진 개개인의 붕어빵들은 프로그래밍에서 객체 혹은 클래스 인스턴스라고 부르게 된다.
객체 지향 프로그래밍에서 객체를 뜻하는 것이 바로 위에서 설명한 개념이며, 클래스와 객체에 대해서 잘 이해하려면 객체 지향 프로그래밍 Object Oriented Programming (OOP)를 잘 알아야 한다.
Android Developer 페이지에서는 OOP가 복잡한 문제를 더 작은 객체로 단순화하는데 도움이 된다고 소개하며, 잘 알려진 OOP의 4가지 개념에 대해서 소개한다.
- 캡슐화: 클래스 내부에 속성 및 메서드를 래핑 한다. 클래스 구성 요소와 메서드를 사용자가 어떤 식으로 연관 지어 있는지 알 필요가 없다.
- 추상화: 캡슐화의 확장 개념으로, 내부 구현 로직을 최대한 추상화 시켜서 숨긴다. 메서드의 동작원리를 클래스 외부에서 건들지 않고, 클래스 내부 구현로직을 통해 사용자는 특정 목적에 집중을 할 수 있도록 한다.
- 상속: 상위 - 하위 관계를 설정하여 클래스간 특성과 동작을 토대로 코드의 재사용성을 늘린다.
- 다형성: 그리스어 어원 poly(많음을 의미)와 morphism(형태를 의미)이 변형된 것으로, 여러 객체를 한 가지 공통 방식으로 사용하는 것이다. 다른 말로 하면 클래스의 객체를 공통 특징을 기반으로 다양한 형태로 사용하는 특성이다.
위 네 가지 개념을 잘 이해하고 앞으로 소개할 클래스 개념과 연관 지어 생각을 해보자.
클래스와 객체
클래스라는 개념에 대해서는 앞서 설명했으니, 이제 프로그래밍에서 어떻게 정의하고 활용할 수 있는지 다뤄보자.
클래스를 기본적으로 정의할 때에는 class 키워드, class name, class body로 3가지 구성요소가 있다.
나중에 상속을 위한 다른 형태도 배우겠지만 상속을 제외한 가장 기본적인 클래스의 정의는 위와 같은 형태로 이루어진다.
클래스 이름을 짓는 관례 또한 존재하는데 다음과 같다.
- Kotlin 키워드를 클래스 이름으로 지으면 안된다. ex) fun
- PascalCase로 작성하고, 각 클래스 이름은 대문자로 시작한다.
- 단어 사이에 공백이 없다.
- 명사형으로 짓는다.
사실 네 번째 "명사형으로 짓는다"는 Android Developer에서 소개하고 있지는 않는데 경험상 동사 및 동사구나 다른 명사형이 아닌 형태는 본 적이 없는 거 같다.
여하튼 클래스 naming에 대한 관례는 위와 같고, Body에 들어가는 구성 요소는 주로 세 가지이다.
- 속성 (Property): 클래스 인스턴스의 속성을 지정하는, 클래스 내부의 변수
- 메서드 (Method): 클래스의 동작과 작업이 포함된, 클래스 내부의 함수
- 생성자 (Constructor): 클래스 객체를 초기화시키는, 클래스가 정의된 프로그램 전체에서 객체를 만드는 특수 함수
우리는 사실 변수를 활용하는 데 있어서 항상 객체를 사용해 왔다.
Int, Float, String, Double 등 데이터 유형에 대한 변수를 선언할 때 특정 데이터 유형에 대한 객체를 만드는 것이다.
객체를 만드는 방법은 아래와 같다.
fun main() {
class SmartDevice {
// Empty Body
}
val smartTvDevice = SmartDevice()
// 객체를 선언
}
기존에 우리는 val 키워드를 통해 변수를 선언할 경우 값을 읽는 것만 가능하고 새로운 값으로 변경하는 것은 불가능하다고 배웠다.
객체의 경우 val 키워드로 선언하면, 다른 객체로 할당하는 것이 불가능하다. 하지만 객체 속성의 값을 업데이트하는 행위, 즉 객체의 상태를 바꾸는 행위는 가능하다.
속성과 메서드
이제 클래스 내부에 래핑 되어 있는 속성과 메서드에 대해서 살펴보도록 하자.
먼저, 속성을 알아볼 건데 이 개념은 매우 간단하다. 클래스 본문에 정의된 변수이다.
class SmartDevice {
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
}
위와 같이 스마트 기기 클래스의 적절한 속성을 나타내는 name, category, device status를 정의해 주었다.
이것을 접근하는 방법은 이전 nullable 변수에 대한 내용에서 length 속성에 접근하기 위한 방법과 동일하다.
fun main() {
val smartTvDevice = SmartDevice()
println("Device name is: ${smartTvDevice.name}")
}
속성은 단순 변수보다 조금 더 많은 작업을 진행할 수 있다.
가령, 스마트 기기의 볼륨을 조정할 때에 범위를 특정 값으로 지정하고 싶다면, setter 함수를 본인이 원하는 대로 작성하면 된다.
값을 얻어내는 getter 함수에 대해서도 함께 소개해보도록 하자.
일단 기본적인 getter, setter 함수의 변경 방법은 위와 같다.
아마 기본적으로 get 함수는 속성 값을 그대로 return해주는 함수일 것이고, set 함수는 속성값을 인자 값인 value로 지정해 주는 함수일 것이다.
위와 같이 body와 return 문에 원하는 대로 지정해 주면 우리가 원하는 getter, setter (get(), set()) 함수가 완성될 것이다.
기본적인, 백그라운드에서 컴파일러가 추가해 주는 getter, setter 함수에 대해서 알아보자.
var speakerVolume = 2
get() = field
set(value) {
field = value
}
만약 val 키워드로 작성했으면, set() 함수가 없을 것이다.
get 함수로 속성 값을 읽거나 set 함수로 속성 값을 업데이트하려면 Kotlin에서 제공하는 지원필드를 활용해야 한다. 지원필드는 속성 값을 메모리에 보유하고 있으며, Kotlin 컴파일러에서 자동으로 생성되어 field 식별자로 참조된다. (위의 filed가 지원필드에 있는 속성 값)
자, 이제 우리가 원하는 대로 속성 값을 원하는 범위 내로 조정하도록 해보자.
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
조건문을 통해 업데이트하려는 값이 0과 100 사이일 경우에만 값을 업데이트시켜준다. 하지만 0과 100 사이의 값이 아닐 경우에 아무 변화가 없으므로 그 부분에 대한 예외 처리나 메세지 출력을 해주면 더 좋을 것이다.
이제 클래스의 동작 및 작업을 수행하는 메서드에 대해서 알아보도록 하자.
메서드는 클래스 내부에 있는 함수로, 이전에 배웠던 함수의 정의를 클래스 내부에서 실행시키면 된다.
예를 들어 SmartDevice이기 때문에 기기를 켜고 끄는 것에 대한 함수를 작성해 보자.
class SmartDevice {
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
fun turnOn() {
println("Smart Device is turned on.")
}
fun turnOff() {
println("Smart Device is turned off.")
}
}
함수의 내용은 아직 상세하게 진행한 것이 없으므로 간단히 스마트 기기가 켜짐과 꺼짐을 알리는 메세지를 출력하는 것으로 예시를 들었다.
이제 이렇게 정의한 함수를 객체에서 사용하는 방법을 알아보자.
사용 방법은 간단하다. nullable 변수에서 우리는 length 속성을 확인할 때에 . 연산자를 사용했었는데 메서드를 호출할 때에도 마찬가지로 . 연산자를 활용하면 된다.
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
// Smart device is turned on.
// Smart device is turned off.
생성자
클래스의 생성자에 대해서 알아보자. 생성자란 클래스의 특수한 함수로 객체를 초기화하고 객체를 클래스의 의미에 맞게 사용할 수 있도록 준비하는 역할을 한다. 생성자는 객체가 인스턴스화, 즉 선언될 때 처리된다.
생성자는 매개변수를 포함하거나 포함하지 않을 수 있으며, 기본 생성자의 경우 매개변수가 없는 생성자이다.
class SmartDevice constructor() {
...
}
위와 같이 기본생성자를 표기하며, Kotlin은 간결한 것을 목표로 하기 때문에 특별한 주석 또는 공개 상태 수정자, 다른 언어에서는 접근 제한자라고 불리는 키워드가 없다면 constructor 키워드를 생략하여 다음과 같이 작성할 수도 있다.
class SmartDevice {
...
}
Kotlin은 기본 생성자를 자동으로 생성하며, 이 생성자는 컴파일러가 백그라운드에서 추가하기 때문에 코드에 표시되지 않는다.
매개변수화된 생성자를 정의하는 방법에 대해서 알아보자.
먼저, 기존 SmartDevice 클래스에 존재하는 name, category 속성은 val로 지정하였으므로 수정할 수 없다. 하지만 이 값은 하드코딩 된 값이므로 값을 받아서 초기화시켜줄 필요가 있는 값들이다.
이때 매개변수화된 생성자를 활용하여 클래스 속성들을 초기화시켜 줄 수 있다.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
클래스 속성인 name과 category를 매개변수로 지정해 준 모습이다. 이에 따라서 객체를 인스턴스화 할 때에도 매개변수 값을 인수로 넘겨줘야 한다.
smartTvDevice = SmartDevice("Android Tv", "Entertainment")
지금껏 살펴본 생성자는 매개변수가 없거나 있는 기본생성자로 본문이 없는 생성자였다면, 이제 살펴볼 생성자는 보조 생성자이다.
기본 생성자는 초기화시켜줄 속성이 있다면 매개변수를 추가해 주고 아니라면 추가해주지 않는 방식으로 이루어지며, 본문이 없기 때문에 인자로 받는 값들로 속성을 초기화시켜주는 역할만 진행한다. 또한 클래스 내부에는 기본 생성자가 하나만 존재할 수 있다.
하지만, 보조 생성자의 경우 클래스 내부에 하나 이상 존재할 수 있으며, 클래스를 초기화시키는 동시에 어떤 로직을 포함할 수 있는 본문을 가지는 생성자이다.
보조 생성자의 위치는 클래스 본문이고, 다음과 같은 세 가지로 구성된다.
- 보조 생성자 선언: constructor 키워드로 시작하며 바로 뒤에 괄호 쌍을 입력해서 보조생성자 선언. 필요시 매개변수를 추가한다.
- 기본 생성자 초기화: 초기화는 콜론으로 시작하며, 그 뒤에 this 키워드와 괄호 쌍을 통해 기본 생성자를 초기화한다. (this는 클래스 자신을 지칭할 때 사용하는 키워드이다.)
- 보조 생성자 본문: 기본 생성자 초기화 뒤에 중괄호 쌍으로 본문을 입력한다.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
// Secondary constructor : Initialize primary constructor
constructor(name: String, category: String, statusCode: Int) : this(name, category){
deviceStatus = when (statusCode) {
0 -> "offline"
1 -> "online"
else -> "unknown"
}
}
...
}
위 보조 생성자는 status code에 따라서 device의 status를 조정해 주는 작업을 수행한다.
상속
클래스를 활용할 때, 클래스 간 연관성을 짓게 해 주고 코드의 재사용성을 증대시켜 줄 수 있는 상속에 대해서 살펴보자.
상속이란 클래스 간의 관계를 정의하여 클래스의 작업이 공통적인 부분이 있는 경우 특정 클래스를 상속하여 조금 더 구체적인 기능을 하는 클래스를 만드는 것이다.
가령, 일반적인 스마트 기기 클래스를 정의하고, 조금 더 구체적인 기기인 스마트폰 클래스는 스마트 기기 클래스를 상속받아서 공통적인 부분은 스마트 기기 클래스의 속성 및 메서드를 활용하고 나머지 구체적인 부분만 따로 정의하는 것이다.
프로그래밍 측면에서 볼 때, 스마트폰 클래스는 스마트 기기 클래스를 확장하는 개념으로 이루어지며 이 때, 스마트 기기 클래스는 슈퍼 클래스, 스마트폰 클래스는 서브 클래스라고 한다.
이제 상속을 활용하는 방법에 대해서 알아보자.
우선 일반적인 클래스인 슈퍼클래스의 확장 가능성을 컴파일러에게 알리기 위해 open 키워드를 슈퍼 클래스 선언부 앞에 추가해 준다.
open class SmartDevice(val name: String, val category: String){
...
}
이후 서브 클래스의 선언은 다음과 같이 일반적인 클래스 선언 이후에 콜론을 통해 상속을 진행한다.
스마트 TV를 예시로 코드를 작성해 보면 다음과 같다.
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory){
}
다만, 여기서 기존 클래스와 조금 다른 점이 class 매개변수를 입력하는 클래스 이름 뒤의 괄호 안 변수들을 단순히 슈퍼클래스 생성자에 전달하기만 한다는 점이다.
이 때문에 따로 val 혹은 var 키워드를 입력하지도 않고 클래스 내에서 deviceName 및 deviceCategory에 접근할 수도 없다.
이제 새롭게 정의한 SmartTvDevice 클래스에 구체적인 클래스 속성과 메서드를 다음과 같이 정의해볼 수 있다.
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory){
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker Volume increased to $speakerVolume")
}
}
클래스 간의 관계에 대해서 잠시 짚고 넘어갈 필요가 있다.
우선 위에 소개했던 예시와 같이 상속 관계가 있는 두 클래스 간의 관계를 IS-A 관계라고 한다.
다른 경우는 한 클래스에서 다른 클래스를 포함하거나 사용하는 HAS-A 관계이다.
간단한 다이어그램으로 확인해 보면 다음과 같은 모습이다.
IS-A 관계에 대한 예시는 위에서 확인해 봤으므로, HAS-A 관계에 대한 예시를 살펴보자.
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
SmartTvDevice와 상속관계가 없는 SmartHome 클래스에서 SmartTvDevice 클래스를 사용하는 예제이다.
SmartTvDevice에 turnOn 메서드가 없지만 슈퍼클래스인 SmartDevice 클래스에 turnOn 메서드가 있기 때문에 상속받아서 위와 같이 사용할 수 있다.
하지만 실제로 슈퍼클래스의 동작 세부사항이 서브 클래스마다 다르면 어떻게 할까? 이때에는 메서드를 재정의하는 기능을 활용하면 된다.
우선 클래스의 상속과 유사하게 슈퍼 클래스의 재정의할 메서드 선언부 앞에 open이라는 키워드를 추가해 준다.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
...
open fun turnOn() {
...
}
open fun turnOff() {
...
}
}
이후 서브 클래스에서 동일한 이름의 메서드를 해당 클래스 동작에 맞게 바꾸어주고 선언부 앞에 덮어쓴다는 의미의 override 라는 키워드를 추가한다.
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory){
var speakerVolume = 2
set(value) {
if(value in 0..100) {
field = value
}
}
override fun turnOn() {
deviceStatus = "online"
println("SmartTvDevice is turned on. Speaker Volume is set to $speakerVolume")
}
override fun turnOff() {
deviceStatus = "offline"
println("SmartTvDevice is turned off.")
}
}
위 예시의 경우 매우 간단한데, 만일 서브클래스에서 작동하는 turnOn 메서드가 슈퍼클래스 메서드의 상당 부분을 동작시킨 후 서브 클래스 동작을 수행하면 어떨까? 코드의 중복이 많아서 가독성도 떨어지고 비효율적인 코드 구성이 될 것이다.
이러한 상황에서 서브 클래스에서 슈퍼 클래스 메서드를 사용하면 편리할 것이다.
서브 클래스에서 슈퍼 클래스의 객체는 굳이 선언하지 않더라도 super 키워드로 정의되어 있으며 객체 + . 연산을 하는 기존 방식과 유사하게 super + . 연산을 진행해주면 된다.
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory){
var speakerVolume = 2
set(value) {
if(value in 0..100) {
field = value
}
}
override fun turnOn() {
super.turnOn()
println("SmartTvDevice is turned on. Speaker Volume is set to $speakerVolume")
}
override fun turnOff() {
super.turnOff()
println("SmartTvDevice is turned off.")
}
}
위와 같이 코드를 재사용 할 수 있다. 만약에 슈퍼클래스의 turnOn 메서드 동작이 많았다면 좀 더 효율적인 동작이 되었을 것이다.
속성의 경우도 메서드와 마찬가지로 재정의가 가능하다. 메서드와 동작 원리가 비슷하므로 예시 코드만 남기도록 한다.
class SmartDevice(val name: String, val category: String) {
...
open val deviceType = "unknown"
...
}
class SmartTvDevice(deviceName: String, deviceCategory: String):
SmartDevice(name = deviceName, category = deviceCategory) {
...
override val deviceType = "Samrt Tv"
...
}
공개 상태 수정자 (접근 제한자)
다른 프로그래밍 언어에서는 접근 제한자라고도 불리는 코틀린의 공개 상태 수정자에 대해서 알아보자.
공개 상태 수정자라는 것은 접근하는 범위를 지정함으로써 무단으로 클래스 및 클래스 요소에 접근하는 것을 막을 수 있고 캡슐화를 달성하는데 주요 역할을 한다.
코틀린에서는 다음과 같은 4가지의 공개 상태 수정자를 제공하며, 기본 값은 public이다.
- public: 모든 위치에서 클래스 선언 및 액세스가 가능하다.
- private: 동일한 클래스 및 소스파일에서 선언 및 액세스가 가능하다.
- protected: 서브클래스에서 선언 및 액세스가 가능하다. 클래스 및 서브클래스를 모두 protected 로 표시
- internal: 동일한 모듈에서 선언 및 액세스가 가능하다.
클래스는 패키지에 포함되고 패키지는 모듈에 포함되는 개념이다.
이러한 공개 상태 수정자는 클래스 뿐만 아니라 속성 및 메서드에도 지정해줄 수 있으며, 공개 상태 수정자를 통해 개발자가 클래스 내의 숨기고 싶은 기능을 숨길 수 있다. 또한 특정 프로젝트와 관련 있는 부분만 공개 상태 수정자를 표시하여 구현이 의도치 않게 사용되지 않도록 하여 버그 가능성을 낮출 수 있다.
사용 방법은 다음과 같이 private, protected, internal 수정자 중 하나로 시작하여 그 뒤에 속성 및 메서드를 정의한다.
open class SmartDevice(val name: String, val category: String) {
...
private var deviceStatus = "online"
...
}
getter 및 setter 함수도 공개 상태 수정자를 적용할 수 있다. 다만, getter 함수의 경우 속성의 공개 상태 수정자와 일치하지 않으면 컴파일러가 오류를 보고하게 된다.
위 코드에서는 private로 속성의 공개 상태 수정자를 설정하였지만, 실제 동작 상으로는 SmartDevice 클래스에서 deviceStatus는 클래스와 하위 요소만 값을 업데이트 할 수 있어야 하므로 protected로 지정하는 것이 올바르다.
open class SmartDevice(val name: String, val category: String) {
...
protected var deviceStatus = "online"
...
}
메서드와 클래스도 속성과 마찬가지로 선언부 앞에 수정자 키워드만 추가해주면 된다.
internal class SmartDevice(val name: String, val category: String) {
...
protected fun turnOn() {
...
}
...
}
하물며 생성자에도 공개 상태 수정자를 지정해줄 수 있으며, 이 경우에는 매개변수가 없는 기본 생성자의 경우에도 construct와 괄호를 타이핑 해주어야 한다.
하지만 코틀린에서 조금 더 자세히 확인해보니, package의 top level 요소들에 대해서는 protected는 사용할 수 없고, private는 같은 파일 내에서는 접근이 가능하다고 한다.
이게 무슨 말이냐면 top level 요소들인 클래스나 가장 바깥쪽에 래핑하고 있는 요소들은 protected와 private의 쓰임이 앞서 설명과 다르다는 것이다. class member나 constructor는 앞서 설명한 부분과 동일하게 동작하는 것 같다.
// top level component example
package foo
fun baz() { ... }
class Bar { ... }
공개 상태 수정자에 대한 조금 더 자세한 내용은 아래 링크를 참고하면 되겠다.
https://kotlinlang.org/docs/visibility-modifiers.html
Visibility modifiers | Kotlin
kotlinlang.org
이렇게 코틀린에서 제공하는 공개 상태 수정자를 활용하여 OOP의 목적에 좀 더 부합하도록 코드를 작성할 수 있다.
속성 위임
코틀린에서는 클래스 내부에서 속성을 선언할 때 자동으로 접근자라고 하는 get, set 함수가 추가적으로 정의가 되고 단순한 대입 및 호출에 있어서 접근자가 내부적으로 동작하도록 되어 있는데, 이를 이용하여 코드의 재사용성을 늘릴 수 있다.(그래서 코틀린은 자바와는 다르게 필드의 개념과 속성의 개념이 다르다. 코틀린은 "속성 = 필드 + 접근자" 개념이다) 앞서 우리는 속성 값을 변경할 때 값이 특정 범위 안에 위치하도록 custom set 함수를 정의했었는데, 서로 다른 여러가지 클래스에서 이렇게 set 함수를 지정해줘야 하는 경우 속성을 나타내는 class를 새로 정의하여 속성을 위임해줄 수 있다.
속성 위임을 위한 클래스를 정의해야 하는데, 이는 작업할 목록을 의미하는 인터페이스를 구현함으로써 정의된다. 필자가 인터페이스 구현 없이 새롭게 class를 정의하여 속성 위임을 진행해보았더니 getValue, setValue 함수에 특정 파라미터가 들어가 있지 않아서 컴파일러가 오류를 보고했다. 코틀린에서 이럴 때 그냥 인터페이스를 구현하라고 만들어 놓은 것 같다.
var 키워드를 통해 클래스를 정의할 때에는 ReadWriteProperty 인터페이스를 구현하고 val 키워드를 통해 클래스를 정의할 때에는 ReadOnlyProperty를 구현하면 된다.
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
// getter 함수 역할
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Int {
// setter 함수 역할
if (value in minValue..maxValue) {
fieldData = value
}
}
}
우선 꺾쇠와 관련된 부분은 무시하고 ReadWriteProperty 인터페이스의 getValue, setValue를 구현하는 클래스라고 이해해두면 되겠다. KPropery는 선언된 속성을 나타내는 인터페이스라고 하고 이를 통해서 위임된 속성의 메타 데이터에 접근할 수 있다고 소개한다.
이런식으로 클래스를 정의해주었으면 속성 위임을 해주는 방법은 다음과 같이 by 키워드를 활용하는 것이다.
class SmartTvDevice(deviceName: String, deviceCategory: String):
SmartDevice(name = deviceName, category = deviceCategory) {
...
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
...
}
이런식으로 다른 클래스에서도 속성 위임을 진행해줌으로써 set 함수가 호출될 때, 특정 범위 안에 있는 값으로만 변경될 수 있도록 해준다.
코틀린에서 제공하는 클래스에 대해서 조금 다루어 보았는데, 아직 내용적으로 미흡한 부분도 많고 추가적으로 등장할 개념도 많지만 추후 블로그에서 다루도록 한다.
'프로그래밍 언어 > Kotlin' 카테고리의 다른 글
Kotlin - Generic (1) | 2024.01.30 |
---|---|
Kotlin - 람다 표현식 (0) | 2023.12.18 |
Kotlin - nullable variable (0) | 2023.12.15 |
Kotlin - 조건문 (0) | 2023.12.13 |
Kotlin 기초 문법 (2) | 2023.11.28 |