[코어자바스크립트] 정보은닉(information hiding)
📚 What is TIL?
접근 권한 제어(정보은닉)
아래는 외부 함수의 변수를 참조하는 내부함수 대해 다룬 코드이다
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2);
outer 함수가 반환한 inner 함수는 outer 함수 내부의 a 변수에 직접적이 아닌 간접적으로 접근할 수 있는데
outer 함수 내부에서 선언된 a 변수는 outer 함수의 스코프 안에서만 유효한 지역 변수이기 때문에,
outer 함수가 return 한 정보에만 접근이 가능하고 outer 함수 외부에서는 직접적으로 접근할 수 없다
💡 이를 통해 알 수 있는 것은 return을 활용해서 일부 변수에 대한 접근 권한을 부여할 수 있다는 것이다
접근 제한자 public / private
자바, C++, C# 과 같은 언어에서는 접근 제한자라는 키워드가 존재하는데,
public, private, protected 라는 접근 제한자를 통해 접근을 제어할 수 있다
여기서 public은 외부에서 접근가능한 것을 의미하고
private는 외부에 노출되지 않고 내부에서만 접근가능한 것을 의미한다
🔎 반면, 자바스크립트에서는 이 public과 private라는 접근 제한자를 지원하진 않지만
이러한 접근 제한자 없이도 클로저로 public한 값과 private한 값을 구분해서 접근권한을 제어할 수 있다
var outer = function () {
var a = 1;
var inner = function () {
return ++a;
};
return inner();
};
var outer2 = outer();
console.log(outer2);
다시 코드를 살펴보자면,
a 변수는 outer 함수 내부에서만 사용되는 private한 값이 된다
하지만 outer 함수에서 반환하고 있는 inner 함수는
스코프 외부에서도 접근이 가능하기 때문에 public한 값이 된다
💡 그래서 정리하자면,
외부에 제공하고자하는 정보들은 return하고,
내부에서만 사용할 정보들은 return하지 않는 것으로 접근 권한 제어가 가능하다
접근 권한에 더 익숙해지기
아래는 간단하게 가위바위보 게임을 구현해본 코드이다
var 가위바위보게임 = {
options : ['가위', '바위', '보'],
moved : 0,
choice : 1,
run: function () {
var computerChoice = this.options[Math.floor(Math.random() * 3)];
var playerChoice = this.options[this.choice];
if (computerChoice === playerChoice) {
console.log('비겼습니다');
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
} else if (
(computerChoice === '가위' && playerChoice === '보') ||
(computerChoice === '바위' && playerChoice === '가위') ||
(computerChoice === '보' && playerChoice === '바위')
) {
console.log('인간패배');
this.moved -= 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${this.moved}번째 계단)`)
} else {
console.log('인간승리');
this.moved += 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${this.moved}번째 계단)`)
}
}
}
가위바위보게임 이라는 변수에 객체를 직접 할당하고
run() 이라는 메서드를 실행할 때 사용할 options, moved라는 프로퍼티에 값을 부여했다그래서 외부에서 호출이 이루어져서 run() 메서드가 실행되면
Math.random 함수를 이용해서 0부터 3까지
랜덤한 숫자의 인덱스에 해당하는 options 의 값을
컴퓨터가 낼 선택지인 computerChoice라는 변수에 할당하고
playerChoice라는 변수에는 무조건 주먹이 들어가도록 할당했다그 다음 if문으로 비교해서
각각의 값이 같을경우||
엔드연산자를 이용해서 내가 컴퓨터에게 졌을 경우||
내가 이겼을경우
각각의 경우에 따라 해당 메세지를 출력하고
이기면 위로 한칸 지면 아래로 한칸 내려가도록 만들었다
가위바위보게임.run()
// 인간승리
// 플레이어:바위 | 컴퓨터:가위
// 계단 한칸 내려가기 (현재 1번째 계단)
💡 run() 메서드를 통해 실행해보면 잘 작동되지만 컴퓨터에게 계속 가위바위보를 지는바람에 분한나머지 코드를 수정해봤다 😎
가위바위보게임.options = ['가위', '바위', '가위']
가위바위보게임.run()
가위바위보게임.run()
가위바위보게임.run()
// 인간승리
// 플레이어:바위 | 컴퓨터:가위
// 계단 한칸 내려가기 (현재 1번째 계단)
// 비겼습니다
// 플레이어:바위 | 컴퓨터:바위
// 인간승리
// 플레이어:바위 | 컴퓨터:가위
// 계단 한칸 내려가기 (현재 2번째 계단)
options를 [가위, 바위, 가위]로 수정해서
컴퓨터는 3분에 2확률로 가위를 내고 3분에 1확률로 바위를 내서
내가 지는 일 없이 이기거나 비기도록 만들었다
💡 하지만 이런식으로 마음대로 객체안의 값을 수정해버린다면 이기기만하는 일방적인 게임이 될 수 있다
💡 여기서 바로 클로저를 활용하면 값을 변경하지못하도록 할 수 있다
클로저 활용하기
이번에는 객체로 이루어진 기존의 코드에서 함수로 만들어 보았다
그리고 외부에 제공할 정보들만 return 해서 변수를 보호하도록 했다
var 가위바위보게임 = function () {
var options = ['가위', '바위', '보'];
var moved = 0;
var choice = 1;
return {
get moved () {
return moved
},
get choice () {
return choice
},
set choice (value) {
if (value === 0 || value === 1 || value === 2) {
choice = value;
} else if (value === 'random') {
choice = Math.floor(Math.random() * 3);
}
},
run: function () {
var computerChoice = options[Math.floor(Math.random() * 3)];
var playerChoice = options[choice];
if (computerChoice === playerChoice) {
console.log('비겼습니다');
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
} else if (
(computerChoice === '가위' && playerChoice === '보') ||
(computerChoice === '바위' && playerChoice === '가위') ||
(computerChoice === '보' && playerChoice === '바위')
) {
console.log('인간패배');
moved -= 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${moved}번째 계단)`)
} else {
console.log('인간승리');
moved += 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${moved}번째 계단)`)
}
}
}
}
이번에는 가위바위보라는 함수를 실행함으로써
options, choice, moved 라는 변수들은 private로 구분해서 외부에서의 접근을 제한했고,
return 문 안에서 객체를 생성하고 반환해줌으로써 run() 메서드를 외부에서 접근 가능하도록 했다
여기 있는 get 과 set 은 getter 와 setter를 의미한다
Getter는 객체의 속성 값을 반환하는 메서드이고
Setter는 객체의 속성 값을 설정하거나 변경하는 메서드이다
이 getter 와 setter 를 이용해서
변수 값을 외부에서 접근하지 못하도록 보호(캡슐화)할 수 있는데요
함수 앞에 get 또는 set 키워드를 붙여서 정의하고
return 키워드를 사용해서 읽어오거나 수정할 값을 설정할 수 있다
moved는 getter로 값을 읽어올 수 있도록 했고
choice는 값을 수정하거나 불러올 수 있도록
getter 와 setter 로 두가지 모두 부여했다
choice의 값을 수정했을 때 0 또는1 또는 2를 입력하면 입력한 값을 choice에 재할당해주고
만약 random이란 문자열을 입력하면 0부터 3까지의 랜덤한 숫자가 choice에 재할당되도록 했다
💡 따라서 이제 외부에서는 run() 메서드를 실행하거나,
moved 값을 확인하거나 choice 값을 변경하거나 확인하는 동작만이 가능하게 되었다
var 플레이 = 가위바위보게임();
플레이.run();
// 비겼습니다
// 플레이어:바위 | 컴퓨터:바위
console.log(플레이.moved);
플레이.choice = 0;
플레이.run();
// 0
// 비겼습니다
// 플레이어:가위 | 컴퓨터:가위
플레이.choice = 'random';
플레이.run();
// 인간패배
// 플레이어:가위 | 컴퓨터:바위
// 계단 한칸 내려가기 (현재 -1번째 계단)
console.log(플레이.options);
// undefined
플레이.options = ['바위', '바위', '보'];
console.log(플레이.options);
// [ '바위', '바위', '보' ]
이제 가위바위보게임 함수를 호출해서 생성된 객체를
play 변수에 할당해서 run() 메서드로 호출해보면
마찬가지로 잘 작동하고 setter 를 통해 값을 변경해줘도 잘 작동된다
⚠️ 그런데, 플레이.options 으로 외부에서 접근하면
내부 변수를 접근하는 것이기 때문에 undefined가 되지만
플레이.options = ['바위', '바위', '보'];
이 부분의 코드가 실행되면 options 의 값이 변경되어버린다
🔎 이것은 함수 내부에 정의 되어있는 options 변수와는 별개로
가위바위보게임 함수의 전역변수로 들어가는게 아니라 새로운 options 라는 프로퍼티가
run() 메서드 내부에서생성되어 그 값을 할당하면서 options값이 덮어씌워진 것이다
✔️ 따라서 이것을 해결하기 위해서는,
Object.freeze() 메서드를 사용하면 되는데,
Object.freeze() 메서드는 객체를 “동결”하여 수정할 수 없도록 만들 수 있다
Object.freeze()
var 가위바위보게임 = function () {
var options = ['가위', '바위', '보'];
var moved = 0;
var choice = 1;
var publicMembers = {
get moved() {
return moved;
},
get choice() {
return choice;
},
set choice(value) {
if (value === 0 || value === 1 || value === 2) {
choice = value;
} else if (value === 'random') {
choice = Math.floor(Math.random() * 3);
}
},
run: function () {
var computerChoice = options[Math.floor(Math.random() * 3)];
var playerChoice = options[choice];
if (computerChoice === playerChoice) {
console.log('비겼습니다');
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
} else if (
(computerChoice === '가위' && playerChoice === '보') ||
(computerChoice === '바위' && playerChoice === '가위') ||
(computerChoice === '보' && playerChoice === '바위')
) {
console.log('인간패배');
moved -= 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${moved}번째 계단)`);
} else {
console.log('인간승리');
moved += 1;
console.log(`플레이어:${playerChoice} | 컴퓨터:${computerChoice}`);
console.log(`계단 한칸 내려가기 (현재 ${moved}번째 계단)`);
}
},
};
Object.freeze(publicMembers);
return publicMembers;
};
위와 같이, 객체의 모든 속성들이 불변 상태로 유지되도록 할 수 있다
따라서 publicMebers라는 객체를 따로 만들어서
return 안에 있던 내용들을 그 안으로 옮겨주고Object.freeze() 라는 메서드를 통해 객체의 속성들을 변경할 수 없도록 만들어 주게되고
publicMembers 객체 내부의 options 변수는 Object.freeze로 불변화되어 있으므로,
publicMembers 객체 외부에서는 fuel 변수의 값을 변경할 수 없다
💡 이러한 정보은닉은 객체 내부의 상태나 동작을 외부에서 직접 접근하는 것을 제한할 수 있다
💡 정보은닉은 객체지향 프로그래밍에서 중요한 개념이라 더 공부할 필요성이 있다고 느꼈다
댓글남기기