✏️12.11 슈퍼클래스를 위임으로 바꾸기

리팩터링 전

class List {...}
class Stack extends List {...}

리팩터링 후

class Stack {
  constructor() {
    this._storage = new List();
  }
}
class List {...}

🧷 배경

슈퍼클래스 대신 위임을 사용하는 리팩터링으로 슈퍼클래스의 기능들이 서브클래스에 어울리지 않는다면 그 기능들을 상속을 통해 이용하면 안된다는 신호이다. 이런 경우 상속을 버리고 위임으로 갈아타 객체를 분리하여 혼란과 오류를 피해야 한다.

위임을 이용하면 기능 일부만 빌려오고 서로 별개인 개념이 명확해진다.

🧷 절차

  1. 슈퍼클래스 객체를 참조하는 필드를 서브클래스에 만든다. 위임 참조를 새로운 슈퍼클래스 인스턴스로 초기화한다.

  2. 슈퍼클래스의 동작 각각에 대응하는 전달 함수를 서브클래스에 만든다. 서로 관련된 함수끼리 그룹으로 묶어 진행하며, 그룹을 하나씩 만들 때마다 테스트한다.

  3. 슈퍼클래스의 동작 모두가 전달 함수로 오버라이드되었다면 상속 관계를 끊는다.

🧷 예시

🧷 리팩터링 전

class CatalogItem {
  constructor(id, title, tags) {
    this._id = id;
    this._title = title;
    this._tags = tags;
  }

  get id() {return this._id;}
  get title() {return this._title;}
  hasTag(arg) {return this._tags.includes(arg);}
}

// Scroll 클래스 (CatalogItem을 상속함)
class Scroll extends CatalogItem {
  constructor(id, title, tags, dateLastCleaned) {
    super(id, title, tags);
    this._lastCleaned = dateLastCleaned;
  }

  needsCleaning(targetDate) {
    const threshold = this.hasTag("revered") ? 700 : 1500;
    return this.daysSinceLastCleaning(targetDate) > threshold;
  }

  daysSinceLastCleaning(targetDate) {
    return this._lastCleaned.until(targetDate, ChronoUnit.DAYS);
  }
}

물리적인 스크롤과 논리적인 카탈로그 아이템에는 차이가 있다. 스크롤은 사본이 여러 개 임에도 카탈로그 아이템은 한 개 뿐이다. 사본 중 하나의 내용을 수정해야 한다면 같은 카테고리 항목의 다른 사본들 모두가 올바르게 수정되는지 확인해야한다.

🧷 리팩터링 후

class CatalogItem {
  constructor(id, title, tags) {
    this._id = id;
    this._title = title;
    this._tags = tags;
  }
  // 2. 전달 메서드 만들기
  get id() {return this._catalogItem._id;}
  get title() {return this._catalogItem._title;}
  hasTag(aString) {return this._catalogItem.hasTag(aString);}
}

// 3. 상속 관계 끊기
class Scroll {
  constructor(id, title, tags, dateLastCleaned) {
    // 1. 카탈로그 아이템을 참조하는 속성을 만들고 슈퍼클래스의 인스턴스를 새로 하나 만들어 대입한다.
    this._catalogItem = new CatalogItem(id, title, tags);
    this._lastCleaned = dateLastCleaned;
  }

  needsCleaning(targetDate) {
    const threshold = this.hasTag("revered") ? 700 : 1500;
    return this.daysSinceLastCleaning(targetDate) > threshold;
  }

  daysSinceLastCleaning(targetDate) {
    return this._lastCleaned.until(targetDate, ChronoUnit.DAYS);
  }
}

Last updated