❗ 캡슐화의 정의
객체의 속성(data fields)과 행위(methods)를 하나로 묶고,
실제 구현 내용 일부를 외부에 감추어 은닉한다.
캡슐화와 은닉화?
캡슐화의 정의 : 메서드 안에서 어떠한 일이 일어나고 있는지 모르게 해야한다. (메서드의 내부 정보 제한)
은닉화의 정의 : 외부에서 객체의 속성을 함부로 접근하지 못하도록 한다. (속성의 접근 제한)
왜 캡슐화를 할까?
객체의 속성과 행위를 묶으면 응집도가 올라가므로 자율적인 객체가 된다는 장점이 있습니다.
자율적인 객체가 된다면 단순히 데이터 전달자 역할이 아니라, 자신의 상태를 스스로 처리할 수 있습니다.
“ 그런데, 이상황에서 은닉화가 이루어지지 않는다면? “
객체가 스스로 자신의 할일을 처리하지 못하고 외부에서 속성을 꺼내와 상태를 수정하게됩니다.
이러한 상황에서는 높은결합도와 함께 낮은 응집도를 가진 수동적인 상태가 됩니다.
또한, 돈과 같이 보안이 중요시되는 부분을 외부의 사람이 알게된다면?
사용자에 따라 돈이 털리거나 자신의 돈을 조작할 수 도 있겠죠. 즉, 보안적으로 치명적인 상태가 됩니다.
즉, 유연한 프로그램을 만듦과 동시에 보안적인 프로그램을 구축하기위해 캡슐화를 사용합니다.
캡슐화를 지키는 방법
// 애완 뉴트리아의 몸을 구성하는 class
class Nutria(capacity: Int) {
val stomach: Stomach = Stomach(capacity)
}
// 애완 뉴트리아의 위를 담당하는 class
class Stomach(val capacity: Int)
// 애완 뉴트리아의 배고픔을 정해주는 main 문
fun main(){
// 사용자는 생각보다 많은것을 알 수 있다.
// 1. 뉴트리아의 배고픔에 관련된 수치 기준
// 2. 해당 코드가 어떤 구조로 작성이 되었는지
// 이러한 문제점은 외부에서 해당구조를 맘대로 수정하고 사용할 수 있게된다.
val nutria = Nutria(0)
if (nutria.stomach.capacity == 0) {
println("(애완)뉴트리아는 배가 고프다.")
} else if (nutria.stomach.capacity <= 5) {
println("(애완)뉴트리아는 약간 배가 고프다")
} else {
println("(애완)뉴트리아는 배부르다.")
}
}
// 즉, 뉴트리아의 몸을 거쳐 뉴트리아의 위에 배고픔 수치를 저장하고, 그 배고픔에 따라 로그가 나옴.
위의 코드는 우선 , Nutria 의 속성인 Stomach를 가져 오고, 또 Stomach의 속성인 capacity를 가져와서 행위를 정의해 주었습니다.
여기서 문제점은 해당 속성들이 모두 public 이기 때문에 사용자가 알 필요도없는 구조를 알아 버린다는것입니다.
즉, 내부구현이 노출되고 이에따라 외부에서 이들을 맘대로 사용하고 수정할 수 있게 됩니다.
또한 만약 Class 가 추가되거나 기존 Class 의 이름을 바꾼다면 전체 코드에서 모든 이름을 바꿔주어야합니다.
이렇게 수동적인 객체는 변화에 취약할 수 밖에 없습니다.
사실 사용자에게 구조 자체를 보여주지 않아도 되고, 무엇을 수행할지만 알면 됩니다.
우리가 우리의 뉴트리아에게 배고프니? 라고 물어보지, 뉴트리아의 배를 갈라 안의 구조를 확인해 보지 않으니까요 ㅎㅎ
// 애완 뉴트리아의 몸을 구성하는 class
class Nutria(capacity: Int) {
private val stomach: Stomach = Stomach(capacity)
// 애완 뉴트리아의 배고픔 수치를 위에서 판단하에 정해주게 하기위해 capacity 변수를 연결해주는 함수
fun showHungryStatus() {
stomach.showHungryStatus()
}
}
// 애완 뉴트리아의 위를 담당하는 class
class Stomach(private val capacity: Int) {
// 애완 뉴트리아의 실질적인 배고픔 수치를 판단하는 함수
fun showHungryStatus() {
if (capacity == 0) {
println("(애완)뉴트리아는 배가 고프다.")
} else if (capacity <= 5) {
println("(애완)뉴트리아는 약간 배가 고프다")
} else {
println("(애완)뉴트리아는 배부르다.")
}
}
}
fun main(){
// 유저는 여기서 뉴트리아의 배고픔 수치에 관한 판단 기준을 알수 없을 뿐더러
// showHungryStatus 가 정확히 어떤 역할을 하는지도 알 수 없다.
// 해당 구조는 어느 한곳의 구조를 바꾸어도 유동적으로 사용할 수 있게 된다.
val nutria : Nutria = Nutria(0)
nutria.showHungryStatus()
}
이제, 다음과 같이 캡슐화를 적용시켜봅시다.
메시지를 뜻하는 showHungryStatus() 메소드만 정의를 하고,
해당 책임을 다시 Stomach에게 위임을 한다음
그 객체 안에서 showHungryStatus() 메소드를 정의합니다.
다음과 같이 구현하면 유저는 여기서 뉴트리아의 배고픔 수치에 관한 판단 기준을 알수 없을 뿐더러
showHungryStatus 가 정확히 어떤 역할을 하는지도 알 수도 없습니다.
또한 만약 Class 이름을 바꾼다해도 기존의 showHungryStatus 라는 메인 코드는 건드리지도 않습니다.
이제 훨씬 변화에 유연한 코드가 되었습니다. 그리고 외부에서는 객체의 내부 구현을 알 필요 없이 원하는 결과를 가져올 수도 있으며,
악의적인 사용자가 객체의 속성을 임의로 바꿀 수도 없습니다.