핌팩토링

12. 상속 다루기

AsCE_hyunseung 2022. 6. 19. 01:07

12.1 메서드 올리기

배경

  • 중복된 두 메서드가 미래에는 냄새나는 쓰레기가 될 수 있다.
  • 무언가 중복되었다는 것은 한쪽의 변경이 다른 쪽에는 반영되지 않을 수 있다는 위험을 항상 수반한다.
  • 메서드 올리기를 적용하기 가장 쉬운 상황은 메서드들의 본문 코드가 똑같을 때이다.

절차

  1. 똑같이 동작하는 메서드인지 면밀히 살펴본다
  2. 메서드 안에서 호출하는 다른 메서드와 참조하는 필드들을 슈퍼클래스에서도 호출하고 참조할 수 있는지 확인한다.
  3. 메서드 시그니처가 다르다면 함수 선언 바꾸기로 슈퍼클래스에서 사용하고 싶은 형태로 통일한다.
  4. 슈퍼클래스에 새로운 메서드를 생성하고, 대상 메서드의 코드를 복사해넣는다.
  5. 서브클래스 중 하나의 메서드를 제거한다.
  6. 모든 서브 클래스의 메서드가 없어질 때까지 다른 서브클래스의 메서드를 하나씩 제거한다.

예시

class Employee {
  ...
}

class SalesPerson extends Employee {
  get name() {
    ...
  }
}

class Engineer extends Employee {
  get name() {
    ...
  }
}
------------------------------------
class Employee {
  get name() {
    ...
  }
}

class SalesPerson extends Employee {
  ...
}

class Engineer extends Employee {
  ...
}

12.2 필드 올리기

배경

  • 어떤 일이 벌어지는지를 알아내려면 필드들이 어떻게 이용되는지 분석해봐야 한다.
    • 분석 결과 필드들이 비슷한 방식으로 쓰인다고 판단되면 슈퍼클래스로 끌어올리자
  • 필드 올리기를 하면 두가지의 장점이 있다.
    • 데이터 중복 선언을 없앨 수 있다.
    • 해당 필드를 사용하는 동작을 서브클래스에서 슈퍼클래스로 옮길 수 있다.

절차

  1. 후보 필드들을 사용하는 곳 모두가 그 필드를 똑같은 방시긍로 사용하는지 살핀다.
  2. 필드들의 이름이 각각 다르다면 똑같은 이름으로 바꾼다.
  3. 슈퍼클래스에 새로운 필드를 생성한다.
  4. 서브클래스의 필드들을 제거한다.

예시

class Employee {
  ...
}

class SalesPerson extends Employee {
  name=""
}

class Engineer extends Employee {
  name=""
}
------------------------------------
class Employee {
  name=""
}

class SalesPerson extends Employee {
  ...
}

class Engineer extends Employee {
  ...
}

12.3 생성자 본문 올리기

배경

  • 생성자는 다루기 까다로워서 생성자에서 하는 일에 제약을 두자
    • 할 수 있는 일과 호출 순서에 제약이 있다.

절차

  1. 슈퍼클래스에 생성자가 없다면 하나 정의한다.
  2. 문장 슬라이드하기로 공통 문장 모두를 super() 호출 직후로 옮긴다.
  3. 공통 코드를 슈퍼클래스에 추가하고 서브클래스들에서는 제거한다. 생성자 매개변수 중 공통 코드에서 참조하는 값들을 모두 super()로 건넨다.
  4. 생성자 시작 부분으로 옮길 수 없는 공통 코드에는 함수 추출하기와 메서드 올리기를 차례로 적용한다.

예시

class Party {
	...
}

class Employee extends Party {
  constructor(name, id, monthlyCost) {
    super();
    this._id = id;
    this._name = name;
    this._monthlyCost = monthlyCost
  }
}
--------------------------
class Party {
  constructor(name) {
    this._name = name
  }
}

class Employee extends Party {
  constructor(name, id, monthlyCost) {
    super(name);
    this._id = id;
    this._monthlyCost = monthlyCost
  }
}

12.4 메서드 내리기

배경

  • 특정 서브클래스 하나만 관련된 메서드는 슈퍼클래스에서 제거하고 해당 서브클래스에 추가하는 편이 깔끔하다.
  • 이 리팩터링은 해당 기능을 제공하는 서브클래스가 정확히 무엇인지를 호출자가 알고 있을 때만 적용할 수 있다.

절차

  1. 대상 메서드를 모든 서브 클래스에 복사한다.
  2. 슈퍼 클래스에서 그 메서드를 제거한다.
  3. 이 메서드를 사용하지 않는 모든 서브클래스에서 제거한다.

예시

class Employee {
  get quota {
    ...
  }
}

class SalesPerson extends Employee {
  ...
}

class Engineer extends Employee {
  ...
}
------------------------------------
class Employee {
  ...
}

class SalesPerson extends Employee {
  ...
}

class Engineer extends Employee {
  get quota {
    ...
  }
}

12.5 필드 내리기

배경

  • 서브클래스 하나에서만 사용하는 필드는 해당 서브클래스로 옮긴다.

절차

  1. 대상 필드를 모든 서브클래스에 정의한다
  2. 서브클래스에서 그 필드를 제거한다.
  3. 이 필드를 사용하지 않는 모든 서브클래스에서 제거한다.

예시

class Employee {
  quota=""
}

class SalesPerson extends Employee {
  ...
}

class Engineer extends Employee {
  ...
}
------------------------------------
class Employee {
  ...
}

class SalesPerson extends Employee {
  quota=""
}

class Engineer extends Employee {
  ...
}

12.6 타입 코드를 서브클래스로 바꾸기

배경

  • 타입 코드만으로도 특별히 불편한 상황은 별로 없지만, 그 이상의 무언가가 필요할 떄가 있다.
  • 그 이상은 서브클래스를 가리킨다. 서브클래스는 두 가지 면에서 특히 매력적이다
    1. 조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다.
    2. 특정 타입에서만 의미가 있는 값을 사용하는 필드나 메서드가 있을 때 발현된다.
  • 이번 리팩터링은 대상 클래스에 직접 적용할지, 아니면 타입 코드 자체에 적용할지를 고민해야한다.

절차

  1. 타입 코드 필드를 자가 캡슐화한다.
  2. 타입 코드 값 하나를 선택하여 그 값에 해당하는 서브클래스를 만든다. 타입 코드 게터 메서드를 오버라이드하여 해당 타입 코드의 리터럴 값을 반환하게 한다.
  3. 매개변수로 받은 타입 코드와 방금 만든 서브클래스를 매핑하는 전체 로직을 만든다.
  4. 타입 코드 값 각각에 대해 서브클래스 생성과 선택 로직 추가를 반복한다.
  5. 타입 코드 필드를 제거한다.
  6. 타입 코드 접근자를 이용하는 메서드 모두에 메서드 내리기와 조건부 로직을 다형성으로 바꾸기를 적용한다.

예시