003. 코틀린의 클래스와 프로퍼티

코틀린의 클래스와 프로퍼티

You probably aren’t new to object-oriented programming and are familiar with the abstraction of a class.

이번 포스팅에서는 클래스의 선언을 위한 간단한 문법에 대해서 알아보도록 하자.

먼저 Java로 name 이라는 이름을 가진 프로퍼티와 이를 포함한 Person이라는 객체를 선언해보자.

1
2
3
4
5
6
7
8
9
10
11
12
 public class Person {

private final String name;

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

일반적으로 Java에서는 생성자가 필드를 초기화하기 위한 값을 파라미터로 받는 경우가 많다.

이는 클래스가 많아질수록 반복적으로 작업될 수 밖에 없는 상황인데, 이러한 반복적인 작업을 프로그래밍에선 보일러 플레이트 라고 한다.

참고 Boilerplate란?

위의 코드를 코틀린으로 바꾸면 아래와 같이 바뀐다.

1
class Person(val name: String)

특정 동작없이 값만 가지고 있는 클래스들을 Value Object, VO 라고 한다.

프로퍼티

보통 하나의 클래스를 만든다는 건 어떤 데이터를 가지고 있끼 그 데이터에 대한 동작을 하나의 엔티티에 캡슐화하는 것을 말한다.

이를 위해 Java는 대부분의 필드값들을 외부에서 참조할 수 없는 private로 선언하고, 이를 접근하기 위한 Getter/Setter를 작성한다.

Java의 필드값과 해당 필드값에 접근하는 동작을 묶어 Property라고 부른다.

수많은 프레임워크가 프로퍼티 개념을 사용하고 있으며, 코틀린도 마찬가지이다.

1
2
3
4
class Person(
val name: String,
var isMarried: Boolean
)

코틀린에서 선언할 수 있는 프로퍼티는 두 종류로 최초 초기화 이후 값을 변경할 수 없는 val, 계속해서 값을 변경할 수 있는 var로 구분된다.

Java의 경우 클래스를 작성할 때 Getter/Setter를 선언하거나 값의 변경 등의 동작을 수행할 수 있는 메서드를 작성하곤 한다.

코틀린은 이에 대한 작성은 필요없으나 Java에서의 호출을 위해 보일러 플레이트를 제공한다.

만약 위의 Person 객체를 Java에서 사용하고자 한다면 아래와 같이 출력할 수 있을 것이다.

1
2
3
4
5
6
/* Java */
>>> Person person = new Person("Bob", true);
>>> System.out.println(person.getName());
Bob
>>> System.out.println(person.isMarried());
true

코틀린의 보일러 플레이트를 사용해 출력한다면 아래와 같다.

1
2
3
4
5
>>> val person = Person("Bob", true)
>>> println(person.name)
Bob
>>> println(person.isMarried)
true

이제 Getter 대신에 직접 해당 속성에 접근하여 참조할 수 있게되어, 똑같은 동작이지만 Getter를 작성하고 호출하는 것에 비해 간결한 코드를 얻을 수 있다.

접근제어자 (Custom accessors)

이번엔 Getter/Setter가 아닌 프로퍼티와 관련되어 동작하는 메서드를 작성해보자.

어떤 사각형을 의미하는 클래스가 있을 때, 이 사각형이 정사각형인지 직사각형인지 판별하는 isSquare라는 프로퍼티를 선언해보자.

1
2
3
4
5
6
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}

isSquare 프로퍼티는 호출되었을 시점에 이미 저장된 가로와 세로 길이를 이용해 값을 계산할 수 있으모로 별도로 값을 저장할 필요가 없다.

{}까지 써가며 전체적인 코드를 작성할 필요도 없고, get() = height == width 로만 작성해도 된다.

1
2
3
>>> val rectangle = Rectangle(41, 43)
>>> println(rectangle.isSquare)
false

소스 코드 구조

코틀린의 디렉토리 구조 그리고 패키지 구조에 대해 알아보자.

Java의 경우엔 모든 클래스가 패키지 아래 소속되어 구성된다.

코틀린와 유사하게 모든 코틀린 관련 파일이 패키지에 소속될 수 있다.

해당 파일에 클래스, 함수, 프로퍼티등 모든 선언이 소속된 패키지에 저장되게 된다.

참고 Kotlin in Action 원서에는 Kotlin file can have a package statement … 라고 되어있으나 임의로 의역한다.

만약 다른 파일에 선언된 코드라도 동일한 패키지에 있는 경우 별다른 조치 없이 직접 참조해서 가져올 수 있으며, 다른 패키지에 있는 경우 import를 통해 가져오게 된다.

아래 예제 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
package geometry.shapes // 패키지 선언부

import java.util.Random // Java의 표준 라이브러리를 import 한다.

class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}

코틀린은 import를 할때 클래스나 함수 등을 별도로 구분하지 않고 참조한다.

최상위의 속해있는 함수도 이름을 명시하여 import 할 수 있다.

1
2
3
4
5
6
7
package geometry.example

import geometry.shapes.createRandomRectangle // 함수의 이름을 통한 참조

fun main(args: Array<String>) {
println(createRandomRectangle().isSquare)
}

Java와 마찬가지로 패키지명 뒤에 PACKAGE_NAME.*와 같이 .*을 붙여서 최상위를 포함한 모든 클래스와 함수를 비롯한 선언부를 참조할 수 있다.