Encapsulate Variable, 변수 캡슐화하기를 알아보자.
요약

코드
let defaultOwner = {firstName: "마틴", lastName: "파울러"};let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {
return defaultOwnerData;
}
export function setDefaultOwner(arg) {
defaultOwnerData = arg;
}배경
- 함수는 데이터보다 다루기가 수월하다.
- 반면에 데이터는 다루기가 까다롭다. 유효범위가 넓어 고쳐야할 곳이 많아진다.
- 접근할 수 있는 범위가 넓은 데이터를 옮길 때는, 그 데이터로의 접근을 독점하는 함수로 캡슐화하는 것이 좋다.
- 해당 함수를 통해야만 변경하고 사용할 수 있다면, 변경 전 검증이나 추가 로직을 쉽게 끼워넣을 수 있다.
- 객체 지향에서 객체의 데이터를 항상
private으로 유지해야 한다고 강조하는 이유가 여기에 있다. - 범위를 제한해야 한다.
- 이보다 좋은 것은 불변 데이터를 사용하는 것이다.
절차
- 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
- 정적 검사를 수행한다.
- 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트한다.
- 변수의 접근 범위를 제한한다.
- 변수로의 직접 접근을 막을 수 없을 때도 있다. (컴파일러 오류?)
- 이런 경우 이름자체를 바꿔버리면 에러가 뜨니 쉽게 찾을 수 있다.
- 테스트한다.
- 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려한다.
따라하기
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
spaceship.owner = defaultOwner;
defaultOwner = {firstName: "레베카", lastName: "파슨스"};-
전역 변수에 데이터가 담겨있는 경우다.
-
참조하는 코드가 있다.
-
이번에는 갱신하는 코드도 있다.
1. 캡슐화를 위해 읽고 쓰는 함수를 정의하자.
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
function defaultOwner() {
return defaultOwner;
}
function setDefaultOwner(arg) {
defaultOwner = arg;
}
spaceship.owner = defaultOwner;
defaultOwner = {firstName: "레베카", lastName: "파슨스"};2. 참조하는 곳을 찾아 바꾼다.
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
function defaultOwner() {
return defaultOwner;
}
function setDefaultOwner(arg) {
defaultOwner = arg;
}
spaceship.owner = defaultOwner();
setDefaultOwner({firstName: "레베카", lastName: "파슨스"});- 하나씩 바꿀때마다 테스트하자.
3. 모든 참조를 수정했다면 변수의 범위를 제한하자.
// defaultOwner.js
let defaultOwner = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {
return defaultOwner;
}
export function setDefaultOwner(arg) {
defaultOwner = arg;
}값 캡슐화하기
- 위의 방법대로 하면 변수 자체에 접근해서 값을 바꾸거나 대입하는 것은 불가능하다.
- 하지만 레퍼런스 타입의 값을 바꾸는 것이기 때문에 다음과 같은 행위는 제어할 수 없다.
const owner1 = defaultOwner();
assert.equal("파울러", owner1.lastName, "처음 값 확인");
const owner2 = defaultOwner();
owner2.lastName = "파슨스";
assert.equal("파슨스", owner1.lastName, "owner2를 변경한 후");- 이런 경우를 방지하기 위해 레퍼런스 타입의 값을 반환할 때는 항상 복제본을 반환하도록 하자.
// defaultOwner.js
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {
return Object.assign({}, defaultOwnerData);
}
export function setDefaultOwner(arg) {
defaultOwnerData = arg;
}- 특히 리스트에 이 기법을 많이 사용한다.
- 하지만 만약 누군가가 원본을 변경하기를 원한다면 어떻게 해야할까?
- 즉,
defaultOwnerData의 내부 값을 변경하고 싶다. (firstName) - 이런 경우 레코드 캡슐화하기를 사용하자.
// defaultOwner.js
let defaultOwnerData = {firstName: "마틴", lastName: "파울러"};
export function defaultOwner() {
return new Person(defaultOwnerData);
}
export function setDefaultOwner(arg) {
defaultOwnerData = arg;
}
class Person {
constructor(data) {
this._lastName = data.lastName;
this._firstName = data.firstName;
}
get lastName() {
return this._lastName;
}
get firstName() {
return this._firstName;
}
}- 지금까지 게터에서 데이터를 복제하는 방법을 살펴보았다.
- 그런데 세터에서도 복제본을 만든느 것이 좋다.
- 복제가 주는 성능이 걱정될 수 있으나, 영향은 미미하다.
- 오히려 원본을 사용할 경우에 생기는 문제가 더 많다.
- 다만 지금 에시에서는 복제본을 만들 떄 depth가 1인 경우만 파악했다. 더 깊을 경우 복잡해진다.