[Refactoring] # 컬렉션 캡슐화하기

앞서 레코드를 캡슐화하는 것은 가변 객체를 클래스로 변경하는 방법을 활용했다. 컬렉션에 대해서는 이미 클래스의 field에 컬렉션이 있는 상태에서 이를 캡슐화해야 한다.

컬렉션을 getter로 내보내면, 사실 객체의 참조값을 내보냈기 때문에 클라이언트에서는 클래스를 통하지 않아도 컬렉션을 마음대로 수정할 수가 있다. 여기서 캡슐화가 깨지게 된다.

컬렉션을 다루는 행위는 클래스를 통해서 동작해야지 다루기가 편하다. 따라서 컬렉션을 수정하는 행위들을 메서드로 만들어서 클라이언트에서는 이 메서드를 통해서만 컬렉션을 다루게 끔 해야 한다.

Code

class Person {
  constructor(name) {
    this._name = name;
    this._courses = [];
  }
  get name() {
    return this._name;
  }
  get courses() {
    this._courses;
  }
  set courses(aList) {
    this._courses = aList;
  }
}

class Course {
  constructor(name, isAdvanced) {
    this._name = name;
    this._isAdvanced = isAdvanced;
  }
  get name() {
    this._name;
  }
  get isAdvanced() {
    return this._isAdvanced;
  }
}

/**
 * usecase in client
 */
// getter를 이용해서 캡슐화를 유지한다.
const numAdvancedCourses = aPerson.courses.filter(c => c.isAdvanced).length;

// 하지만 setter에 의해 캡슐화가 깨지게 된다.
// 클래스가 컬렉션을 제어할 수가 없다.
aPerson.courses = basicCourseNames.map(name => new Course(name, false));

// 캡슐화를 위해서 컬렉션을 추가하고 제거하는 메서드를 추가하자.
/**
 * Person class
 */
addCourse(aCourse){
    this._courses.push(aCourse);
}

removeCourse(aCourse, fnIfAbsent = () => { throw new RangeError()}) {
    const index = this._courses.indexOf(aCourse);
    if (index === -1) fnIfAbsent();
    else this._courses.splice(index, 1)
}

// 그런 다음 컬렉션을 직접 호출해서 변경하던 코드를 수정한다.
for (const name of basicCourseNames()) {
    aPerson.addCourse(new Course(name, false))
}

module.exports = {};

Reference

Refactoring: Improving the Design of Existing Code (2nd Edition) p246-250.

좋은 웹페이지 즐겨찾기