class Person {
get courses() {return this._courses;}
set courses(aList) {this._courses = aList;}
리팩터링 후
class Person {
get courses() {return this._courses.slice();}
addCourse(aCourse) {...}
removeCourse(aCourse) {...}
🧷 배경
저자는 가변 데이터를 모두 캡슐화한다. 데이터 구조가 언제 어떻게 수정되는지 파악하기 쉬워서 필요한 시점에 데이터 구조를 변경하기도 쉬어지기 때문이다.
컬렉션 변수로의 접근을 캡슐화하면서 게터가 컬렉션 자체를 반환하도록 한다면, 그 컬렉션을 감싼 클래스가 눈치채지 못하는 상태에서 컬렉션의 원소들이 바뀌어버릴 수 있다.
add()와 remove()라는 이름의 컬렉션 변경자 메서드를 만든다.
원본 모듈 밖에서는 컬렉션을 수정하지 않는 습관을 갖고 있다면 이런 메서드를 제공하는 것만으로도 충분할 수 있다. 컬렉션 게터가 원본 컬렉션을 반환하지 않게 만들어서 클라이언트가 실수로 컬렉션을 바꿀 가능성을 차단하는 것이 낫다.
🧷 절차
아직 컬렉션을 캡슐화하지 않았다면 변수 캡슐화하기부터 한다.
컬렉션에 원소를 추가/제거하는 함수를 추가한다.
➡️ 컬렉션 자체를 통째로 바꾸는 세터는 제거한다. 세터를 제거할 수 없다면 인수로 받은 컬렉션을 복제해 저장하도록 한다.
정적 검사를 수행한다.
컬렉션을 참조하는 부분을 모두 찾는다. 컬렉션의 변경자를 호출하는 코드가 모두 앞에서 추가한 추가/제거 함수를 호출하도록 수정한다. 하나씩 수정할 때마다 테스트한다.
컬렉션 게터를 수정해서 원본 내용을 수정할 수 없는 읽기전용 프락시나 복제본을 반환하게 한다.
테스트한다.
🧷 리팩터링 전
// Person 클래스...
constructor(name) {
this._name = name;
this._course = [];
}
get name() {return this._name;}
get courses() {return this._courses;}
set courses(aList) {this._courses = aList;}
// Course 클래스...
constructor(name, isAdvanced){
this._name = name;
this._isAdvanced = isAdvanced;
}
get name() {return this._name;}
get isAdvanced() {return this._isAdvanced;}
//클라이언트는 Person클래스에서 제공하는 수업 컬렉션에서 수업 정보를 얻는다.
numAdvancedCourses = aPerson.courses
.filter(c => c.isAdvanced)
.length
;
🧷 리팩터링 후
// 클라이언트
const basicCourseNames = readBasicCourseNames(filename);
aPerson.courses = basicCourseNames.map(name => new Course(name, false));
//클라이언트 입장에서는 아래의 코드처럼 수업 목록을 직접 수정하는게 훨씬 편할 수 있다.
// 클라이언트
for(const name of readBasicCourseNames(filename)){
aPerson.courses.push(new Course(name, false));
}
// Person 클래스...
addCourse(aCourse){
this._courses.push(aCourse);
}
removeCourse(aCourse, fnIfAbsent = () => {throw new RangeError();}) {
const index = this._course.indexOf(aCourse)
if (index === -1) fnIfAbsent();
else this._courses.splice(index, 1);
}
방금 추가한 메서드를 호출하도록 아래와 같이 코드를 수정한다.
// 클라이언트...
for(const name of readBasicCourseName(filename)) {
aPerson.addCourse(new Course(name, false));
}
더이상 setCourses()를 사용할 일이 없으니 제거해준다. 세터를 제공해야할 특별한 이유가 있다면 아래의 코드와 같이 복제본을 필드에 저장하도록 한다.
// Person 클래스...
set courses(aList) {this._courses = aList.slice();}
// 메서드들을 사용하지 않고 아무도 목록을 변경할 수 없게하고 싶다면 아래와 같이 복제본을 제공하도록 한다.
get courses() {return this._courses.slice();}