7장. 클래스와 프로퍼티
지금까지 우리는 값 하나하나를 따로 다뤄 왔습니다.
이름은 이름대로, 나이는 나이대로
변수에 담아 두었습니다.
그런데 프로그램이 커지면
“서로 관련된 값들“을 하나로 묶고 싶어집니다.
예를 들어 회원 한 명은
이름, 나이, 이메일을 함께 가집니다.
이렇게 관련된 값들을 하나로 묶는 도구가
바로 클래스(class)입니다.
이 장에서는 클래스를 만드는 방법과,
클래스가 값을 담아 두는 그릇인 프로퍼티(property)를 배웁니다.
여기서부터가 이 책의 4부,
“객체를 설계하는 코틀린“의 시작입니다.
자바를 조금 아는 분이라면
클래스라는 단어가 익숙할 수 있습니다.
하지만 코틀린의 클래스는
자바보다 훨씬 짧고 깔끔합니다.
그 차이를 하나씩 확인해 봅시다.
7.1 클래스 만들기
클래스란 무엇인가
클래스를 배우기 전에
“객체“라는 말부터 짚고 넘어가겠습니다.
우리가 다루는 회원, 상품, 주문처럼
“현실에 있는 무언가“를 프로그램 안에서 표현한 것을
객체(object)라고 부릅니다.
그리고 이 객체를 만들기 위한
“설계도“가 바로 클래스입니다.
비유하자면 이렇습니다.
클래스는 붕어빵 “틀“이고,
객체는 그 틀로 찍어낸 “붕어빵“입니다.
틀은 하나지만,
그 틀로 붕어빵을 여러 개 만들 수 있습니다.
마찬가지로 클래스는 하나지만,
그 클래스로 객체를 여러 개 만들 수 있습니다.
클래스 선언하기
코틀린에서 클래스는class라는 키워드로 만듭니다.
가장 단순한 클래스를 하나 만들어 보겠습니다.
class Member
fun main() {
println("Member 클래스가 만들어졌습니다")
}
class Member,
이 한 줄이 전부입니다.
중괄호({ })조차 없어도 됩니다.
내용이 없는 클래스라면 이렇게 선언만 해도 됩니다.
자바에서는 같은 클래스를 만들 때
최소한 중괄호가 필요했습니다.
// 자바
public class Member {
}
코틀린은 필요 없는 부분을
과감히 생략할 수 있습니다.
객체 생성하기
클래스는 설계도일 뿐입니다.
실제로 쓰려면 객체를 만들어야 합니다.
객체를 만드는 일을
생성(instantiation)이라고 부릅니다.
만들어진 객체 하나하나를
인스턴스(instance)라고도 부릅니다.
코틀린에서 객체를 만드는 방법은 간단합니다.
class Member
fun main() {
val member = Member()
println(member)
}
Member(),
클래스 이름 뒤에 괄호를 붙이면 객체가 만들어집니다.
여기서 자바를 아는 분이라면
빠진 단어를 눈치챘을 것입니다.
바로 new입니다.
// 자바
Member member = new Member();
자바는 객체를 만들 때new 키워드를 꼭 붙여야 했습니다.
코틀린에는 new가 없습니다.
그냥 Member()라고만 쓰면 됩니다.
이렇게 만든 객체는
변수에 담아 두고 쓸 수 있습니다.
여기서는 val member에 담았습니다.
프로퍼티 추가하기
빈 클래스는 아직 아무 정보도 담지 못합니다.
이제 회원에게
이름과 나이를 넣어 보겠습니다.
클래스 안에 값을 담는 자리를
프로퍼티(property)라고 부릅니다.
우리말로는 “속성“이라고 옮기기도 합니다.
클래스가 가지는 값이라고 생각하면 쉽습니다.
class Member {
var name: String = "이름 없음"
var age: Int = 0
}
프로퍼티는 우리가 2장에서 배운 변수와
생김새가 똑같습니다.val이나 var로 선언하고, 타입을 적습니다.
다른 점은 위치입니다.
클래스 안에 들어가 있으면 프로퍼티,
함수 안에 있으면 그냥 지역 변수입니다.
프로퍼티 사용하기
이제 객체를 만들고
프로퍼티에 값을 넣고 읽어 보겠습니다.
프로퍼티에 접근할 때는
객체 이름 뒤에 점(.)을 찍습니다.
class Member {
var name: String = "이름 없음"
var age: Int = 0
}
fun main() {
val member = Member()
member.name = "홍길동"
member.age = 20
println("이름: ${member.name}")
println("나이: ${member.age}")
}
실행하면 이렇게 출력됩니다.
이름: 홍길동
나이: 20
member.name = "홍길동": 프로퍼티에 값을 넣기member.name: 프로퍼티의 값을 읽기
점 하나로 값을 넣고 읽을 수 있어서
직관적입니다.
한 가지 짚어 둘 점이 있습니다.
여기서 member는 val로 선언했는데도member.name은 바꿀 수 있었습니다.
val member는
“member라는 변수에 다른 객체를 다시 담을 수 없다“는 뜻이지,
“객체 안의 값을 못 바꾼다“는 뜻이 아닙니다.
객체 안의 값을 바꾸지 못하게 하는 방법은
7.5에서 자세히 다룹니다.
7.2 생성자
생성자가 필요한 이유
앞의 코드에는 조금 불편한 점이 있었습니다.
객체를 먼저 만들고,
그다음에 값을 하나씩 채워 넣었습니다.
val member = Member()
member.name = "홍길동"
member.age = 20
이렇게 하면
값을 빠뜨릴 위험이 있습니다.
이름만 넣고 나이를 깜빡한다면,age는 처음에 넣어 둔 0인 채로 남습니다.
그래서 더 좋은 방법이 있습니다.
객체를 만드는 바로 그 순간에
필요한 값을 함께 받는 것입니다.
이때 쓰는 것이 생성자(constructor)입니다.
생성자란
“객체를 만들 때 실행되는 특별한 코드“입니다.
주로 프로퍼티에 초기값을 채우는 일을 합니다.
Primary Constructor
코틀린에서 가장 많이 쓰는 생성자는
클래스 이름 바로 옆에 적는 방식입니다.
이것을 주 생성자(primary constructor)라고 부릅니다.
class Member(val name: String, val age: Int)
fun main() {
val member = Member("홍길동", 20)
println("이름: ${member.name}")
println("나이: ${member.age}")
}
클래스 이름 옆 괄호 안에val name: String, val age: Int를 적었습니다.
이 짧은 한 줄이 두 가지 일을 동시에 합니다.
- 객체를 만들 때 이름과 나이를 받는다
- 받은 값으로 프로퍼티를 만든다
즉, 앞에서 여러 줄로 하던 일을
한 줄로 끝낸 것입니다.
객체를 만들 때는Member("홍길동", 20)처럼
괄호 안에 값을 순서대로 넣어 줍니다.
여기서 val을 var로 바꾸면
나중에 값을 바꿀 수 있는 프로퍼티가 됩니다.
class Member(var name: String, var age: Int)
자바로 같은 클래스를 만들면
훨씬 길어집니다.
// 자바
public class Member {
private final String name;
private final int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
}
같은 일을 하는데도
자바는 여러 줄이 필요합니다.
코틀린은 이 모든 것을class Member(val name: String, val age: Int) 한 줄로 표현합니다.
init 블록
생성자에서
값을 채우는 것 말고
다른 일도 하고 싶을 때가 있습니다.
예를 들어 “나이가 음수면 안 된다” 같은
검사를 하고 싶을 수 있습니다.
이럴 때 init 블록을 씁니다.
init은 initialize(초기화)의 줄임말입니다.
객체가 만들어질 때
자동으로 한 번 실행되는 코드 묶음입니다.
class Member(val name: String, val age: Int) {
init {
println("$name 회원이 생성되었습니다")
}
}
fun main() {
val member = Member("홍길동", 20)
}
실행하면 이렇게 출력됩니다.
홍길동 회원이 생성되었습니다
객체를 만들자마자init 블록이 스스로 실행된 것입니다.
init 블록은 값 검사에 특히 자주 쓰입니다.
class Member(val name: String, val age: Int) {
init {
if (age < 0) {
throw IllegalArgumentException("나이는 음수일 수 없습니다")
}
}
}
이렇게 하면
잘못된 나이로 객체를 만드는 순간
바로 오류가 발생합니다.
덕분에 “잘못된 상태의 객체“가
프로그램 안을 돌아다니는 일을 막을 수 있습니다.
(예외를 던지는 throw는 8부에서 자세히 다룹니다.)
이렇게 “만들 때부터 올바른 객체“를 보장하는 생각은
백엔드 도메인 설계에서 아주 중요합니다.
(11부 34장에서 다시 깊게 다룹니다.)
Secondary Constructor
대부분은 주 생성자 하나로 충분합니다.
하지만 가끔
“객체를 만드는 방법을 여러 가지로 열어 두고” 싶을 때가 있습니다.
이럴 때 쓰는 것이
보조 생성자(secondary constructor)입니다.
보조 생성자는 클래스 안에서constructor라는 키워드로 만듭니다.
class Member(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) {
println("나이를 모른 채 회원을 만들었습니다")
}
}
fun main() {
val a = Member("홍길동", 20) // 주 생성자 사용
val b = Member("이순신") // 보조 생성자 사용
println("${b.name}, ${b.age}세")
}
실행하면 이렇게 출력됩니다.
나이를 모른 채 회원을 만들었습니다
이순신, 0세
여기서 눈여겨볼 부분은: this(name, 0)입니다.
이것은
“주 생성자를 대신 불러 달라“는 뜻입니다.
이름만 받아서, 나이는 0으로 채워
주 생성자에게 넘기는 것입니다.
즉, 보조 생성자는
언제나 주 생성자를 거쳐 갑니다.
정리하면 이렇습니다.
| 구분 | 위치 | 개수 |
|---|---|---|
| 주 생성자 | 클래스 이름 옆 | 하나 |
| 보조 생성자 | 클래스 안 constructor | 여러 개 가능 |
한 가지 조언을 덧붙이자면,
코틀린에서는 보조 생성자를 쓸 일이 생각보다 적습니다.
값을 채우는 목적이라면
“기본값(default parameter)“으로 해결되는 경우가 많기 때문입니다.
(기본 인자는 4장에서 배웠습니다.)
// 보조 생성자 대신 기본값으로 해결
class Member(val name: String, val age: Int = 0)
이렇게 하면Member("이순신")처럼 나이를 생략해도
자동으로 0이 들어갑니다.
보조 생성자 없이
같은 편리함을 얻은 것입니다.
7.3 Getter와 Setter
Kotlin Property의 정체
여기서 코틀린 클래스의
숨은 동작 하나를 밝히겠습니다.
우리가 이렇게 값을 읽고 쓸 때,
member.name = "홍길동" // 값 쓰기
println(member.name) // 값 읽기
겉보기에는 프로퍼티에
직접 접근하는 것처럼 보입니다.
하지만 실제로는
숨겨진 함수 두 개가 대신 일하고 있습니다.
- 값을 읽을 때 : 게터(getter)라는 함수가 호출됨
- 값을 쓸 때 : 세터(setter)라는 함수가 호출됨
게터는 “값을 꺼내 주는 함수”,
세터는 “값을 넣어 주는 함수“라고 생각하면 됩니다.
코틀린은 프로퍼티를 만들 때
이 게터와 세터를 자동으로 함께 만들어 줍니다.
그래서 우리는
게터와 세터를 직접 쓰지 않고도
점(.) 하나로 편하게 값을 다룰 수 있었던 것입니다.
겉모습은 값에 직접 접근하는 것 같지만,
속에서는 게터와 세터가 대신 움직입니다.
val로 만든 프로퍼티는
값을 바꿀 수 없으므로 게터만 만들어집니다.var로 만든 프로퍼티는
게터와 세터가 모두 만들어집니다.
Custom Getter
게터가 함수라면,
그 안의 동작을 우리가 직접 정할 수도 있습니다.
이렇게 직접 만든 게터를
커스텀 게터(custom getter)라고 부릅니다.
예를 들어
성과 이름을 따로 받아서,
“전체 이름“을 만들어 주는 프로퍼티를 생각해 봅시다.
class Member(val firstName: String, val lastName: String) {
val fullName: String
get() = "$lastName$firstName"
}
fun main() {
val member = Member("길동", "홍")
println(member.fullName)
}
실행하면 이렇게 출력됩니다.
홍길동
fullName은
따로 값을 저장하지 않습니다.
대신 누군가 member.fullName을 읽을 때마다get() 안의 코드가 실행되어
그 자리에서 값을 만들어 냅니다.
이렇게 “저장하지 않고 계산해서 돌려주는 프로퍼티“는
서로 관련된 값을 조합해 보여 줄 때 아주 유용합니다.
Custom Setter
세터도 직접 만들 수 있습니다.
이것을 커스텀 세터(custom setter)라고 부릅니다.
값을 넣을 때
특별한 처리를 하고 싶을 때 씁니다.
여기서 새 단어 하나가 필요합니다.
바로 field입니다.
field는
프로퍼티가 실제 값을 저장해 두는 자리를 가리킵니다.
“뒷받침하는 저장 공간“이라는 뜻에서
배킹 필드(backing field)라고 부릅니다.
말이 어렵지만 쓰임은 간단합니다.
커스텀 세터 안에서
“진짜 값을 넣을 곳“이 바로 field입니다.
class Member {
var name: String = ""
set(value) {
field = value.trim()
}
}
fun main() {
val member = Member()
member.name = " 홍길동 " // 앞뒤에 공백이 있음
println("[${member.name}]")
}
실행하면 이렇게 출력됩니다.
[홍길동]
세터 안에서value.trim()으로 앞뒤 공백을 지운 뒤field에 넣었습니다.
그래서 공백이 섞인 이름을 넣어도
저장될 때는 깔끔하게 정리됩니다.
여기서 field 대신
실수로 name이라고 쓰면 안 됩니다.
name = value라고 쓰면
세터가 자기 자신을 다시 부르게 되어
끝없이 반복되는 문제가 생깁니다.
그래서 반드시 field를 써야 합니다.
Java Bean과의 차이
자바를 배운 분이라면
게터와 세터라는 말이 익숙할 것입니다.
자바에서는 값을 안전하게 다루기 위해
프로퍼티를 숨기고,getName(), setName() 같은 함수를
일일이 손으로 만들어 왔습니다.
이런 방식으로 만든 객체를
자바 빈(Java Bean)이라고 부릅니다.
자바로 쓰면 이렇습니다.
// 자바
public class Member {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
프로퍼티 하나에
게터와 세터를 직접 써야 해서
코드가 길어집니다.
코틀린은 이 일을 대신 해 줍니다.
class Member {
var name: String = ""
}
이 한 줄이
자바의 게터, 세터를 모두 포함합니다.
정리하면 이렇습니다.
| 구분 | 자바 | 코틀린 |
|---|---|---|
| 게터/세터 작성 | 손으로 직접 | 자동 생성 |
| 값 읽기 | member.getName() | member.name |
| 값 쓰기 | member.setName("홍") | member.name = "홍" |
코드는 짧아졌지만
동작은 자바와 같습니다.
속에서는 여전히 게터와 세터가 움직이고 있으니까요.
이 덕분에 코틀린으로 만든 클래스를
자바에서 불러 쓸 때도 자연스럽게 연결됩니다.
(자바와 코틀린을 함께 쓰는 이야기는 9부에서 다룹니다.)
7.4 접근 제어
접근 제어가 필요한 이유
클래스를 만들다 보면
“이건 밖에서 건드리지 않았으면” 하는 값이 생깁니다.
예를 들어 회원의 비밀번호는
아무나 밖에서 읽거나 바꾸면 곤란합니다.
이렇게 “어디까지 접근을 허용할지“를 정하는 것을
접근 제어(access control)라고 부릅니다.
접근 범위를 정해 주는 키워드를
가시성 변경자(visibility modifier)라고 합니다.
말이 어렵지만
“어디까지 보이게 할까“를 정하는 표시라고
생각하면 쉽습니다.
코틀린에는 네 가지가 있습니다.
| 키워드 | 접근 범위 |
|---|---|
public | 어디서든 접근 가능 (기본값) |
private | 선언한 클래스 안에서만 |
protected | 클래스 자신과 자식 클래스에서만 |
internal | 같은 모듈 안에서만 |
하나씩 살펴보겠습니다.
public
public은
“어디서든 접근할 수 있다“는 뜻입니다.
아무 표시도 하지 않으면
자동으로 public이 됩니다.
class Member {
var name: String = "" // public (기본값)
}
여기서 자바와의 차이가 하나 있습니다.
자바는 아무 표시도 없으면
“같은 패키지에서만 접근” 가능한 상태가 됩니다.
하지만 코틀린은 아무 표시가 없으면public,
즉 어디서든 접근 가능한 상태가 됩니다.
코틀린의 기본값은
public입니다.
private
private은
“선언한 클래스 안에서만 쓸 수 있다“는 뜻입니다.
밖에서는 접근할 수 없습니다.
class Member(val name: String) {
private val password: String = "1234"
fun checkPassword(input: String): Boolean {
return password == input
}
}
fun main() {
val member = Member("홍길동")
println(member.checkPassword("1234")) // 가능
// println(member.password) // 오류: 밖에서 접근 불가
}
password는 private이라
클래스 밖에서는 읽을 수 없습니다.
대신 클래스 안의 checkPassword 함수를 통해서만
간접적으로 확인할 수 있습니다.
이렇게 하면
비밀번호가 밖으로 새어 나가는 것을
막을 수 있습니다.
protected
protected는
“클래스 자신과, 그 클래스를 물려받은 자식 클래스“에서만
접근할 수 있다는 뜻입니다.
여기서 “물려받는다“는 말이 나오는데,
이것을 상속(inheritance)이라고 합니다.
상속은 다음 장인 8장에서 자세히 배웁니다.
지금은 이렇게만 기억해 두면 됩니다.
private은 자기 클래스 안에서만,protected는 자식 클래스까지 허용한다.
open class User {
protected val secret: String = "비밀"
}
private보다는 조금 넓고,public보다는 좁은 범위라고
이해하면 됩니다.
internal
internal은 코틀린에만 있는 개념입니다.
자바에는 없습니다.
“같은 모듈 안에서만 접근 가능“하다는 뜻입니다.
여기서 모듈(module)이란
함께 컴파일되는
“하나의 코드 묶음“을 말합니다.
예를 들어
하나의 프로젝트, 하나의 라이브러리가
각각 하나의 모듈이 됩니다.
internal class InternalHelper {
fun help() {
println("같은 모듈에서만 쓸 수 있어요")
}
}
internal은
“우리 프로젝트 안에서는 자유롭게 쓰되,
남에게 라이브러리로 공개할 때는 감추고 싶은 코드“에
유용합니다.
지금 단계에서는public과 private만 확실히 알아 두어도 충분합니다.
- 밖에서 써야 하면
public(또는 그냥 생략) - 안에서만 써야 하면
private
이 두 가지가
실무에서 가장 많이 쓰입니다.
7.5 불변 객체 만들기
다시 만나는 불변성
2장에서 우리는
“되도록 val을 쓰자“는 생각을 배웠습니다.
바뀌지 않는 성질,
즉 불변성(immutability)이었습니다.
이 생각은 변수 하나에만 해당하는 것이 아닙니다.
객체 전체에도 그대로 적용됩니다.
한 번 만들어지면
내용이 절대 바뀌지 않는 객체를
불변 객체(immutable object)라고 부릅니다.
val 중심의 객체
불변 객체를 만드는 방법은 간단합니다.
모든 프로퍼티를 val로 선언하면 됩니다.
var로 만든 객체와 비교해 보겠습니다.
// 바꿀 수 있는 객체 (var)
class MutableMember(var name: String, var age: Int)
// 바꿀 수 없는 객체 (val)
class Member(val name: String, val age: Int)
fun main() {
val a = MutableMember("홍길동", 20)
a.age = 21 // 가능: var이므로 변경됨
val b = Member("이순신", 30)
// b.age = 31 // 오류: val이라 바꿀 수 없음
}
MutableMember는
만든 뒤에도 나이를 바꿀 수 있습니다.
하지만 Member는
한 번 만들어지면 나이를 바꿀 수 없습니다.
바로 이 Member가 불변 객체입니다.
왜 불변 객체가 좋을까
값을 못 바꾸게 만드는 것이
불편해 보일 수 있습니다.
하지만 백엔드에서는
이 불편함이 오히려 큰 장점이 됩니다.
서버는 수많은 요청을
동시에 처리합니다.
이때 여러 곳에서 같은 객체를
동시에 바꾸려 하면,
값이 뒤엉켜 예측할 수 없는 오류가 생깁니다.
하지만 객체가 불변이라면
아무도 값을 바꿀 수 없으므로
이런 뒤엉킴 자체가 일어나지 않습니다.
아무도 바꿀 수 없는 값은,
누가 언제 읽어도 항상 똑같습니다.
또 다른 장점도 있습니다.
값이 바뀌지 않으니
“이 값이 어디서 바뀌었지?” 하고
추적할 필요가 없습니다.
버그를 찾기가 훨씬 쉬워집니다.
상태를 외부에 노출하지 않기
여기서 한 걸음 더 나아가 봅시다.
객체가 가진 값을
그 객체의 상태(state)라고 부릅니다.
불변 객체를 만들 때는
“밖에서 상태를 함부로 바꾸지 못하게” 하는 것이 핵심입니다.
특히 컬렉션(여러 값을 담는 목록)을 프로퍼티로 가질 때
주의가 필요합니다.
(컬렉션은 5부에서 자세히 배웁니다.)
간단한 예로 살펴보겠습니다.
class Team(names: List<String>) {
private val members: MutableList<String> = names.toMutableList()
val memberNames: List<String>
get() = members.toList()
fun addMember(name: String) {
members.add(name)
}
}
여기서 설계 의도를 하나씩 보겠습니다.
- 실제 목록
members는private으로 감춥니다 - 밖에서 목록을 볼 때는
memberNames로 복사본만 내줍니다 - 값을 추가할 때는 반드시
addMember함수를 거칩니다
이렇게 하면
밖에서는 목록을 마음대로 헤집을 수 없습니다.
오직 addMember라는
정해진 통로로만 값을 넣을 수 있습니다.
만약 목록을 그대로 밖에 공개했다면,
밖에서 몰래 값을 지우거나 바꿔
객체의 상태가 엉망이 될 수 있습니다.
내부의 상태는 감추고,
정해진 통로로만 다루게 한다.
이것이 객체를 안전하게 지키는 기본 원칙입니다.
백엔드 도메인 객체의 상태 관리
이 원칙이 가장 빛나는 곳이
바로 백엔드의 도메인 객체(domain object)입니다.
도메인 객체란
회원, 주문, 상품처럼
그 서비스의 핵심 개념을 담은 객체입니다.
이런 객체는
언제나 “올바른 상태“를 유지해야 합니다.
예를 들어 주문 금액이
갑자기 음수가 되면 안 됩니다.
그래서 좋은 도메인 객체는
세 가지를 함께 지킵니다.
- 만들 때부터 올바른 값만 받는다 (
init검사) - 만든 뒤에는 상태를 함부로 못 바꾼다 (
val중심) - 꼭 바꿔야 하는 변화는 정해진 함수로만 한다
이 세 가지를 담은
간단한 주문 객체를 만들어 보겠습니다.
class Order(
val orderId: String,
val amount: Int,
) {
init {
if (amount <= 0) {
throw IllegalArgumentException("주문 금액은 0보다 커야 합니다")
}
}
}
fun main() {
val order = Order("ORD-001", 15000)
println("주문 ${order.orderId}: ${order.amount}원")
// val wrong = Order("ORD-002", -100) // 만드는 순간 오류
}
이 Order는
잘못된 금액으로는 아예 만들어지지 않습니다.
또 한 번 만들어지면
금액을 바꿀 수도 없습니다.
즉, 세상에 나온 순간부터
항상 올바른 상태를 유지합니다.
이렇게 “믿을 수 있는 객체“를 쌓아 올리는 것이
탄탄한 백엔드 코드의 출발점입니다.
이 주제는 이 책의 마지막을 향해 가며
11부 34장에서 본격적으로 다룹니다.
지금은 “불변 객체가 왜 좋은지“를
느끼는 것만으로 충분합니다.
7장을 마치며
이 장에서 우리는 다음을 배웠습니다.
- 클래스는 객체를 만드는 설계도이고,
프로퍼티는 클래스가 값을 담는 자리라는 점 new없이클래스이름()으로
객체를 만든다는 점- 주 생성자로 객체 생성과 프로퍼티 선언을
한 줄에 끝낼 수 있다는 점 init블록으로
만들 때부터 올바른 객체를 보장하는 방법- 코틀린의 프로퍼티는
게터와 세터를 자동으로 만들어 준다는 점 public,private,protected,internal로
접근 범위를 조절하는 방법val중심으로 불변 객체를 만들고,
상태를 감춰 안전하게 지키는 생각
특히 두 가지를 기억해 두면 좋습니다.
“생성자로 한 줄에 담기”, 그리고 “불변 객체로 상태 지키기“입니다.
이 두 가지는
앞으로 객체를 설계하는 내내 함께합니다.
다음 장에서는
클래스끼리 관계를 맺는 방법,
즉 상속과 인터페이스를 배워 보겠습니다.