왜 getter, setter를 사용해야 할까?

Gio·2025년 3월 7일
0

서론

getter, setter를 두지 않고 그냥 필드를 public으로 선언하면 되지 않나요? 왜 그렇게 하지 않을까요?

위 질문에 대한 답을 하기 위해 알아보았다.

게터, 세터란?

자바에서 필드의 값을 얻거나 필드를 초기화하기 위한 함수이다.

Kotlin에서는 자동으로 생성된다.

class Person {
    var name: String? = null
        get() = field // 기본 게터
        set(value) {
            field = value // 기본 세터
        }

    fun getName(): String { // 우연히 게터와 이름이 같을 뿐, 로직이 다름
        return name ?: "Unknown"
    }

    fun fetchName(): String? { // 우연히 게터와 로직이 같을 뿐, 이름이 다름
        return name
    }
}

본론

getter, setter를 사용하라는 주장을 확장하면 필드에 직접 접근하지 말고 메서드를 쓰라는 말과 같다. 그 이유는 필드에 접근하는 것이 캡슐화를 위반하기 때문이다.

💡

캡슐화: 상태와 행동을 하나의 객체 안에 모으는 것

객체를 사용하면 나중에 변경될 가능성이 높은 내부 구현(특히 상태)을 외부로부터 감출 수 있다. 이를 통해 변경의 여파를 통제할 수 있다.

getter, setter를 통해 얻고 세팅하는 과정을 추상화하여 견고한 부분만을 공개하고, 내부적으로 어떻게 하는지에 대한 세부사항은 감출 수 있다.

그럼 getter, setter를 사용하면 캡슐화가 지켜지는 것인가?

질문은 원점으로 돌아온다.

*getter, setter도 결국 필드를 노출하는 것인데, 어떻게 캡슐화가 지켜진다고 할 수 있을까?*

이 질문은 매우 날카로운 질문이다. 다음 name, agegetter, setter는 필드를 그대로 드러낸다.

class Person(
    val name: String,
    val age: Int,
)

fun main() {
    val gio = Person("지오", 20)
    println("안녕하세요. 저는 ${gio.name}이고, ${gio.age}살입니다.")
}

getter, setter를 통해 Person의 상태를 얻어와 main에서 자기소개를 하고 있다. 하지만 다음과 같이 제약사항이 발생한다면 어떨까?

저희 커뮤니티에서 자기소개를 할 때에는 앞으로 나이를 밝히지 않겠습니다.

이는 즉 자기소개에 대한 요구사항의 변경이다. 자기소개를 한다는 사실 자체는 변하지 않지만 자기소개에 관한 세부사항의 변경이다.
하지만 이는 Person 뿐 아니라 사용처인 main의 수정 또한 필요로 한다.

class Person(
    val name: String,
    val age: Int,
)

fun main() {
    val gio = Person("지오", 20)
    println("안녕하세요. 저는 ${gio.name}입니다.")
}

간단한 요구사항인데도 만약 Person의 사용처가 다양했다면 지옥이 펼쳐졌을 것이다.

하지만 캡슐화를 잘 사용했다면 다음과 같은 코드를 작성했을 것이다.

class Person(
    private val name: String,
    private val age: Int,
) {
    fun introduce() {
        println("안녕하세요. 저는 ${name}이고, ${age}살입니다.")
    }
}

fun main() {
    val gio = Person("지오", 20)
    gio.introduce()
}

getter, setter를 아예 사용하지 않고 견고한 부분인 메서드만을 사용해 코드를 수행했다. 이 때 같은 제약사항이 변하면 다음과 같이 코드를 수정하면 된다.

class Person(
    private val name: String,
    private val age: Int,
) {
    fun introduce() {
        println("안녕하세요. 저는 ${name}입니다.")
    }
}

fun main() {
    val gio = Person("지오", 20)
    gio.introduce()
}

이렇게만 보면 큰 차이가 없어 보이지만 다음에 주목해야 한다.

코드의 수정이 Person 내부에서만 일어났다.

객체의 사용처는 1곳보다 많을 가능성이 높다. 위 코드는 캡슐화의 원칙을 지켜 변경의 여파를 제한했고, 코드 수정을 최소화하는 효과를 얻는다.

결론

  1. 캡슐화를 위반하면 변경의 여파를 제한할 수 없다.
  2. getter, setter를 사용하지 않으면 캡슐화를 위반하기 쉽다.
  3. getter, setter를 사용했더라도 상태를 드러내면 캡슐화에 위반된다.
  4. 객체가 가져야 할 상태(데이터)에 초점을 두지 말고, 객체가 수행해야 할 행동(동작)에 초점을 두어 개발하자.

→ 객체 지향 생활 체조 원칙 9. getter/setter/프로퍼티를 쓰지 않는다.


REF

조영호, 오브젝트: 코드로 이해하는 객체지향 설계

Properties | Kotlin

profile
틀린 부분을 지적받기 위해 업로드합니다.

0개의 댓글