캡슐화
- 7.1 레코드 캡슐화하기
- 7.2 컬렉션 캡슐화하기
- 7.3 기본형을 객체로 바꾸기
- 7.4 임시 변수를 질의 함수로 바꾸기
- 7.5 클래스 추출하기
- 7.6 클래스 인라인하기
- 7.7 위임 숨기기
- 7.8 중개자 제거하기
- 7.9 알고리즘 교체하기
좋은 설계의 핵심이 뭐냐고 물으면, 파울러는 **"모듈이 자신의 내부를 얼마나 잘 감추고 있는가"**라고 답해. 7장은 캡슐화를 강화하거나 조정하는 기법들을 모아놓은 챕터야.
**레코드 캡슐화하기(Encapsulate Record)**는 단순한 레코드(해시, JSON 객체 등)를 클래스로 감싸는 거야. 레코드는 직관적이지만 문제가 있거든. "어떤 데이터가 들어 있는지 API만 보고는 모른다." 게다가 레코드를 직접 노출하면 누가 어떤 필드를 읽고 쓰는지 추적하기 어려워. 클래스로 감싸면 내부 구조를 숨길 수 있어. 필드 이름이 바뀌어도, 내부 표현이 바뀌어도 외부는 영향받지 않지. 중첩 레코드(JSON처럼 깊이가 있는 경우)를 캡슐화할 때는 깊은 복사로 불변성을 보장할 건지, 래퍼 객체로 수정을 통제할 건지 판단이 필요해.
**컬렉션 캡슐화하기(Encapsulate Collection)**도 비슷한 맥락인데, 클래스가 컬렉션을 멤버로 가지고 있을 때 그 컬렉션을 직접 반환하면 안 돼. "getter가 컬렉션 자체를 반환하면, 외부에서 그 컬렉션을 마음대로 수정할 수 있다." 클래스가 모르는 사이에 내부 상태가 바뀌는 거지. add(), remove() 같은 변경 메서드를 제공하고, getter에서는 복사본이나 읽기 전용 프록시를 반환해야 해. 한 코드베이스 안에서 컬렉션 접근 방식이 통일되어야 한다는 것도 중요해.
**기본형을 객체로 바꾸기(Replace Primitive with Object)**는 처음에는 단순한 문자열이나 숫자로 충분했던 데이터가 시간이 지나면서 특별한 동작이 필요해지는 경우야. 전화번호를 문자열로 다루다가 포맷팅, 지역 코드 추출, 유효성 검증이 필요해지면 객체로 바꿔야 해. "경험 많은 프로그래머라면 이런 데이터를 발견할 때마다 바로 전용 클래스로 만든다." 시작은 효과가 미미해 보이지만, 프로그램이 커지면서 그 가치가 드러나거든.
**임시 변수를 질의 함수로 바꾸기(Replace Temp with Query)**는 함수 안에서 계산 결과를 임시 변수에 담아놓고 여러 번 쓰는 코드가 있을 때, 그 계산을 함수로 추출하는 거야.
// Before
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000) return basePrice * 0.95;
else return basePrice * 0.98;
// After
get basePrice() { return this._quantity * this._itemPrice; }
// ...
if (this.basePrice > 1000) return this.basePrice * 0.95;
else return this.basePrice * 0.98;
임시 변수를 없애면 함수 추출하기가 쉬워져. 변수가 있으면 추출할 때 매개변수로 넘겨야 하는데, 질의 함수로 바꾸면 그럴 필요가 없거든. 클래스 안에서 적용할 때 가장 효과적이야.
**클래스 추출하기(Extract Class)**는 하나의 클래스가 너무 많은 책임을 지고 있을 때 일부를 별도 클래스로 분리하는 거야. "클래스가 점점 비대해지는 건 흔한 일이다." 처음에는 적절했던 클래스에 기능이 하나둘 추가되면서 책임이 불어나거든. 함께 변하는 데이터와 메서드를 묶어서 새 클래스로 빼내. 반대로 **클래스 인라인하기(Inline Class)**는 클래스가 하는 일이 거의 없어서 존재 가치가 없을 때 다른 클래스에 합치는 거야. 두 클래스의 책임을 재분배하고 싶을 때 일단 합친 다음 다시 올바르게 추출하는 전략도 써.
**위임 숨기기(Hide Delegate)**는 클라이언트가 서버 객체의 위임 객체를 직접 호출하는 경우, 서버에 위임 메서드를 만들어서 위임 객체를 숨기는 거야.
// Before — 클라이언트가 내부 구조를 알아야 한다
manager = aPerson.department.manager;
// After — 캡슐화
manager = aPerson.manager;
"캡슐화란 모듈이 시스템의 다른 부분을 얼마나 적게 알아야 하는지와 관련된다." 위임 객체가 바뀌어도 클라이언트는 영향받지 않아. 반대로 **중개자 제거하기(Remove Middle Man)**는 서버 클래스가 위임 메서드로 가득 차서 실제로 하는 일 없이 전달만 하고 있을 때 적용해. 이 둘의 균형은 상황에 따라 달라. 시스템이 변하면서 "적절한 은닉의 정도"도 바뀌거든. 리팩터링은 양방향으로 자유롭게 오갈 수 있어야 해.
**알고리즘 교체하기(Substitute Algorithm)**는 더 간결하고 명확한 알고리즘이 있으면 바꾸는 거야. 수십 줄의 탐색 코드를 표준 라이브러리의 find() 한 줄로 바꾸는 것처럼. 이 기법을 적용하려면 먼저 함수를 가능한 한 잘게 쪼개놔야 해. 크고 복잡한 알고리즘은 교체하기 어렵거든.
정리
7장 읽고 기억할 거 세 가지:
- "레코드보다 클래스가 낫다." 클래스로 감싸면 내부를 숨기고 변경에 유연해져
- "컬렉션을 직접 반환하지 마라." 외부에서 내부 상태를 몰래 바꿀 수 있어
- "위임 숨기기와 중개자 제거하기는 양방향이다." 상황에 따라 적절한 균형을 찾아