😀 책에서 기억하고 싶은 내용을 써보세요.
- 모듈을 분리하는 가장 중요한 기준은 아마도 시스템에서 각 모듈이 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기느냐에 있을 것이다.
- 이러한 비밀 중 대표적인 데이터 구조는 레코드 캡슐화하기와 컬렉션 캡슐화하기로 숨길 수 있다.
- 심지어 기본형 데이터도 기본형을 객체로 바꾸기로 캡슐화할 수 있다.
- 클래스는 본래 정보를 숨기는 용도로 설계되었다.
organization = {name: "애크미 구스베리", country: "GB"};
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() {return this._name;}
get name(arg) {this._name = arg;}
get country() {return this._country;}
get country(arg) {this._country = arg;}
}
배경
대부분의 프로그래밍 언어는 데이터 레코드를 표현하는 구조를 제공한다.
- 레코드는 연관된 여러 데이터를 직관적인 방식으로 묶을 수 있어서 각각을 따로 취급할 때보다 훨씬 의미 있는 단위로 전달할 수 있게 해준다.
하지만 단순한 레코드에는 단점이 있다.
- 계산해서 얻을 수 있는 값과 그렇지 않은 값을 명확히 구분해 저장해야 하는 번거로움
바로 이 때문에 저자는 가변 데이터를 저장하는 용도로는 레코드보다 객체를 선호하는 편이다.
- 객체를 사용하면 어떻게 저장했는지를 숨긴 채 세 가지 값을 각각의 메서드로 제공할 수 있다.
- 사용자는 무엇이 저장된 값이고 무엇이 계산된 값인지 알 필요가 없다.
- 캡슐화하면 이름을 바꿀 때도 좋다.
- 필드 이름을 바꿔도 기존 이름과 새 이름 모두를 각각의 메서드로 제공할 수 있어서 사용자 모두가 새로운 메서드로 옮겨갈 때까지 점진적으로 수정할 수 있다.
‘가변’ 데이터 일 때 객체를 선호
- 값이 불변이면 단순히 ‘시작'과 ‘끝'과 ‘길이'를 모두 구해서 레코드에 저장한다.
- 이름을 바꿀 때는 그저 필드를 복제한다. 그러면 앞서 객체를 활용해 수정 전후의 두 메서드를 동시에 제공한 방식과 비슷하게 점진적으로 수정할 수 있다.
절차
- 레코드를 담은 변수를 캡슐화한다.
- 레코드를 캡슐화하는 함수의 이름은 검색하기 쉽게 지어준다.
- 레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다. 이 클래스에 원본 레코드를 반환하는 접근자도 정의하고, 변수를 캡슐화하는 함수들이 이 접근자를 사용하도록 수정한다.
- 테스트한다.
- 원본 레코드 대신 새로 정의한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
- 레코드를 반환하는 예전 함수를 사용하는 코드를 4에서 만든 새 함수를 사용하도록 바꾼다. 필드에 접근할 때는 객체의 접근자를 사용한다. 적절한 접근자가 없다면 추가한다. 한 부분을 바꿀 때마다 테스트한다.
- 중첩된 구조처럼 복잡한 레코드라면, 먼저 데이터를 갱신하는 클라이언트들에 주의해서 살펴본다. 클라이언트가 데이터를 읽기만 한다면 데이터의 복제본이나 읽기전용 프락시를 반환할지 고려해보자.
- 클래스에서 원본 데이터를 반환하는 접근자와 (1에서 검색하기 쉬운 이름을 붙여둔) 원본 레코드를 반환하는 함수들을 제거한다.
- 테스트한다.
- 레코드의 필드도 데이터 구조인 중첩 구조라면 레코드 캡슐화하기와 컬렉션 캡슐화하기를 재귀적으로 적용.
예시: 간단한 레코드 캡슐화하기
프로그램 전체에서 널리 사용되는 상수를 예로 살펴보자.
const organization = {name: "애크미 구스베리", country: "GB"};
위 상수는 프로그램 곳곳에서 레코드 구조로 사용하는 자바스크립트 객체로, 아래와 같이 읽고 쓴다.
result += `<h1>${organization.name}</h1>`; // 읽기 예
organization.name = newName; // 쓰기 예
1️⃣가장 먼저 이 상수를 캡슐화한다(변수 캡슐화하기)
function getRawDataOfOrganization() {return organization;}
그러면 읽고 쓰는 코드는 다음처럼 바뀐다.
result += `<h1>${getRawDataOfOrganization().name}<h1>`; // 읽기 예
getRawDataOfOrganization().name = newName; // 쓰기 예
- 레코드를 캡슐화하는 목적은 변수 자체는 물론 그 내용을 조작하는 방식도 통제하기 위해서다.
- 2️⃣이렇게 하려면 레코드를 클래스로 바꾸고, 4️⃣새 클래스의 인스턴스를 반환하는 함수를 새로 만든다.
// Organization 클래스
class Organization {
constructor(data) {
this._data = data;
}
}
// 최상위...
const organization = new Organization({name: "애크미 구스베리", country: "GB"});
function getRawDataOfOrganization() {return organization._data;}
function getOrganization() {return organization;}
객체로 만드는 작업이 끝났으니 5️⃣레코드를 사용하던 코드를 살펴보자.
레코드를 갱신하던 코드는 모두 세터를 이용하도록 고친다.
// Organization 클래스...
set name(aString) {this._data.name = aString;}
// 클라이언트
getOrganization().name = newName;
마찬가지로, 레코드를 읽는 코드는 모두 게터를 사용하게 바꾼다.
// Organization 클래스
get name() {return this._data.name;}
// 클라이언트
result += `<h1>${getOrganization().name}</h1>`;
6️⃣다 바꿨다면 앞에서 이상한 이름으로 지었던 임시 함수를 제거한다.
function getRawDataOfOrganization() {return organization._data;} // <--- 제거
function getOrganization() {return organization;}
마지막으로 _data의 필드들을 객체 안에 바로 펼쳐놓으면 더 깔끔할 것 같다.
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() {return this._name;}
get name(aString) {this._name = aString;}
get country() {return this._country;}
get country(aCountryCode) {this._country = aCountryCode;}
}
- 이렇게 하면 입력 데이터 레코드와의 연결을 끊어준다는 이점이 생긴다.
- 특히 이 레코드를 참조하여 캡슐화를 깰 우려가 있는 코드가 많을 때 좋다.
- 게터는 데이터 구조를 깊이 탐색하게 만들되 원본 데이터를 그대로 반환하지 말고 객체로 감싸서 반환하는게 효과적일 수 있다.
- [Refactoring Code to Load Document] 참고 ⇒ 링크
🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.
- 이 책에서 레코드라고 불리는 데이터의 형식은 JSON과 상당히 비슷하다고 느꼈습니다.
- 레코드는 좀 더 직관적인 방식이고 객체는 개발자에게 더 친화적이라고 생각했습니다.
- 자바, 스프링을 사용하면서 기본적으로 객체를 통해 데이터를 주고 받는 코딩을 하고 있었습니다. 이런 방식이 저절로 캡슐화를 지켜서 코딩을 하게 도와주고 있었다는 것을 알게 되었습니다.
- 유명한 프로그래머(ex. 마틴파울러)의 코드와 생각이 정리된 글을 보고 배우자!
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- 레코드 캡슐화를 재귀적으로 하는 방법은?
반응형