class Organization {
constructor(data) {
this._name = data.name;
this._name = data.country:
}
get name() {return this._name;}
set name(arg) {this._name = arg;}
get country() {return this._country;}
set country(arg) {this._country = arg;}
}
입력 데이터 레코드와의 연결을 끊어준다는 이점이 생긴다. 이 레코드를 참조하여 캡슐화를 깰 우려가 있는 코드가 많을 때가 좋다.
🧷 배경
📍레코드: 연관된 여러 데이터를 직관적인 방식으로 묶을 수 있어서 각각을 따로 취급할 때보다 훨씬 의미 있는 단위로 전달할 수 있게 해준다.
📍레코드의 단점: 계산해서 얻을 수 있는 값과 그렇지 않은 값을 명확히 구분해 저장해야 하는 점이 번거롭다.
{start: 1, end: 5} {start: 1, length: 5}
와 같이 어떤 식으로 저장하든 시작과 끝과 길이를 알 수 있어야 한다.
→ 가변 데이터를 저장하는 용도로는 레코드보다 객체를 선호한다.
레코드 구조
필드 이름을 노출하는 형태
(필드를 외부로 숨겨서) 내가 원하는 이름을 쓸 수 있는 형태
🧷 절차
레코드를 담은 변수를 캡슐화한다.
레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다. 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고, 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
테스트한다.
원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
레코드를 반환하는 예전 함수를 사용하는 코드를 4에서 만든 새 함수를 사용하도록 바꾼다. 필드에 접근할 때는 객체의 접근자를 사용한다. 적절한 접근자가 없다면 추가한다. 한 부분을 바꿀 때마다 테스트한다.
클래스에서 원본 데이터를 반환하는 접근자와 원본 레코드를 반환하는 함수들을 제거한다.
테스트한다.
레코드의 필드도 데이터 구조인 중첩 구조라면 레코드 캡슐화하기와 컬렉션 캡슐화하기를 재귀적으로 적용한다.
🧷 리팩터링 전
중첩된 레코드 캡슐화하기
"1920":{
name: "마틴 파울러",
id: "1920",
usages: {
"2016": {
"1": 50,
"2": 55,
// 나머지 달은 생략.
},
"2015":{
"1": 70,
"2": 63,
// 나머지 달은 생략.
}
}
},
"38673":{
name: "닐 포드",
id: "38673",
// 다른 고객 정보도 같은 형식으로 저장된다.
}
중첩 정도가 심할수록 읽거나 쓸 때 데이터 구조 안으로 더 깊숙히 들어가야 한다.
// 쓰기 예...
customerData[customerID].usages[year][month] = amount;
// 읽기 예...
function compareUsage (customerID, laterYear, month) {
const later = customerData[customerID].usages[laterYear][month];
const earlier = customerData[customerID].usages[laterYear-1][month];
return {laterAmount: later, change: later - earlier};
}
변수캡슐화부터 시작한다.
function getRawDataOfCustomer() {return customerData;}
function setRawDataOfCustomer(arg) {customerData = arg;}
// 쓰기 예...
getRawDataOfCustomers()[customerID].usages[year][month] = amount;
// 읽기 예...
function compareUsage(customerID, laterYear, month) {
const later = getRawDataCustomers()[customerID].usages[laterYear][month];
const earlier = getRawDataOfCustomers()[customerID].usages[laterYear-1][month];
return {laterAmount: later, change: later - earlier};
}
전체 데이터 구조를 표현하는 클래스를 CustomerData와 같이 정의하고 반환하는 함수로 새로 만든다.
class CustomerData{
constructor(data) {
this._data = data;
}
}
// 최상위...
function getCustomerData() {return customerData;}
function getRawDataOfCustomers() {return customerData._data;}
function setRawDataOfCustomers(arg) {customerData = new CustomerData(arg);}
여기서 중요한 부분은 데이터를 쓰는 코드다. getRawDataOfCustomers()를 호출한 후에 데이터를 변경할 때도 주의를 해야한다.
3. 고객 객체에는 세터가 없으니 데이터 구조 깊이 까지 들어가서 값을 바꾸는데 이를 보완하기 위해 아래와 같이 데이터 구조 안으로 들어가는 코드를 세터로 뽑아내는 작업을 해준다.
// 쓰기 예....
setUsage(customerID, year, month, amount);
// 최상위....
function setUsage(customerID, year, month, amonth){
getRawDataOfCustomer()[customerID].usges[year][month] = amount;
}
캡슐화에서는 값을 수정하는 부분을 명확하게 드러내고 한 곳에 모아두는 일이 굉장히 중요하다.
우선 getRawDataOfCustomers()에서 데이터를 깊은 복사해서 반환하여 확인하는 방법이 있다.
// 최상위...
function getCustomerData() {return customerData;}
function getRawDataOfCustomer() {return customerData.rawData;}
function setRawDataOfCustomer(arg) {customerData = new CustomerData(arg);}
// CustomerData 클래스...
get rawData() {
return _.cloneDeep(this._data)
}
깊은 복사는 lodash라이브러리의 cloneDeep()로 처리한다.
읽기는 어떻게 처리해야 할까?
첫번째로 세터 때와 같은 방법을 적용할 수 있다. 읽는 코드를 모두 독립함수로 추출한 다음 고객 데이터 클래스로 옮기는 것이다.