[리펙터링 2판] 12장 - 상속 다루기
도서

[리펙터링 2판] 12장 - 상속 다루기

반응형

 상속은 발등에 불이 떨어져야 비로소 잘못 사용했음을 알아차리는 경우가 많다. 상속은 막강한 도구지만, 잘못된 곳에서 사용되거나 나중에 환경이 변해 문제가 생기기도 한다.

 

 

 

12. 1 매서드 올리기

내용

  • 중복된 메서드를 찾아 제거하여 메서드 올리기를 사용한다.

적용

  • 메서드들의 본문 코드가 똑같을 때 매서드 올리기를 적용하면 된다.
  • 가장 이상하고 복잡한 경우에는 메서드의 본문에서 참조하는 필드들이 서브클래스에만 있는 경우 슈퍼클래스로 올린 후에 메서드를 올려야 한다.

결과

  • 중복 코드를 제거하는 것은 중요하기에 효과를 볼 수 있다.

예시

Before

class Employee {...}

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

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

After

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

class Salesman extends Employee {...}
class Engineer extends Employee {...}

 

 

12. 2 필드 올리기

내용

  • 반대 리팩터링 : 필드 내리기 (12.5절)
  • 서브클래스들이 독립적으로 개발 되었거나, 뒤늦게 하나의 계층구조로 리팩터링된 경우라면 일부 기능이 중복되어 있을 수 있다.

적용

  • 필드들이 비슷한 방식으로 쓰인다고 판단되면 슈퍼 클래스로 끌어 올려야 한다.

결과

  • 필드의 중복 선언 제거

예시

Before

class Employee {...} // Java

class Salesman extends Employee {
  private String name;
}

class Engineer extends Employee {
  private String name;
}

After

class Employee {
  protected String name;
}

class Salesman extends Employee {...}
class Engineer extends Employee {...}

 

 

 

12. 3 생성자 본문 올리기

내용

  • 생성자는 다루기 까다롭고, 할 수 있는 일과 호출 순서에 제약이 있기 때문에 제약을 둔다.

적용

  • 공통된 생성자가 있을 때 슈퍼클래스로 올린다.

결과

  • 생성자의 중복 제거

예시

Before

class Party {...}

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

After

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 메서드 내리기

내용

  • 반대 리펙터링 : 메서드 올리기(12.1절)
  • 소수의 서브클래스와만 관련된 메서드를 슈퍼클래스에서 서브클래스로 내리는 것

적용

  • 하나의 서브클래스에만 관련된 메서드가 있는경우에

결과

  • 슈퍼 클래스에 없어도 되는 기능을 내려 용도가 명확해진다.

예시

Before

class Employee {
  get quota {...}
}

class Engineer extends Employee {...}
class Salesman extends Employee {...}

After

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

 

 

 

12. 5 필드 내리기

내용

  • 반대 리펙터링 : 메서드 올리기(12.1절)
  • 소수의 서브클래스와만 관련된 메서드를 슈퍼클래스에서 서브클래스로 내리는 것

적용

  • 하나의 서브클래스에만 관련된 메서드가 있는경우에

결과

  • 슈퍼 클래스에 없어도 되는 기능을 내려 용도가 명확해진다.

예시

Before

class Employee {        // Java
  private String quota;
}

class Engineer extends Employee {...}
class Salesman extends Employee {...}

After

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

class Salesman extends Employee {
  protected String quota;
}

 

 

12. 6 타입 코드를 서브클래스로 바꾸기

내용

  • 비슷한 대상들을 특정 특성에 따라 구분해야 할 때가 자주 있다. 이런 일을 다루는 수단으로 타입 코드 필드가 있다.
  • 타입 코드는 프로그래밍 언어에 따라 열거형, 심볼, 문자열, 숫자등으로 표현한다.
  • 타입 코드 그 이상이 필요할 때가 있는데 서브클래스를 가리킨다.

적용

  • 서브클래스는 조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다. 동작이 달라져야하는 함수가 여러 개일 때 유용하다.
  • 또한 특정 타입에서만 의미있는 값을 사용하는 필드나 메서드가 있을 때 발현 된다. 

결과

  • 다형성을 제공하고 데이터의 관계를 더 명확히 드러낸다.

예시

Before

function createEmployee(name, type) {
  return new Employee(name, type);
}

After

function createEmployee(name, type) {
  switch (type) {
    case "engineer": return new Engineer(name);
    case "salesman": return new Salesman(name);
    case "manager":  return new Manager (name);
  }

단점은 대상 클래스를 직접 서브클래싱하는게 간단하지만 특정 유형을 다른 용도로 쓰고 싶을 때 그럴 수 없다. 또한 유형이 불변일 때도 직접 서브클래싱 방식은 이용할 수 없다.

 

 

 

12. 7 서브클래스 제거하기

내용

  • 서브 클래싱은 원래 데이터 구조와는 다른 변종을 만들거나 다른 종류에 따라 동작이 달라지게 할 수 있는 유용한 메커니즘이다.

적용

  • 소프트웨어 시스템이 성장함에 따라 서브클래스로 만든 변종이 다른 모듈로 이동하거나 완전하 사라짐으로 가치가 바래지기도 한다.

결과

  • 불필요한 서브클래스를 슈퍼클래스의 필드로 대체해 제거하여 불필요한 이해도를 없앤다.

예시

Before

class Person {
  get genderCode() {return "X";}
}
class Male extends Person {
  get genderCode() {return "M";}
}
class Female extends Person {
  get genderCode() {return "F";}
}

After

class Person {
  get genderCode() {return this._genderCode;}
}

 

 

 

 

12. 8 슈퍼클래스 추출하기

내용

  • 비슷한 일을 수행하는 두 클래스가 보이면 상속 매커니즘을 이용해서 비슷한 부분을 공통의 슈퍼클래스로 옮겨 담을 수 있다.
  • 공통 부분이 데이터라면 필드 올리기(12.2절)
  • 동작이라면 메서드 올리기(12.1절)

적용

  • 상속과 위임으로 중복동작을 해결할지 결정한다.
  • 슈퍼클래스로 끌어올리고 싶은 공통 요소를 찾았을 때 수행

결과

  • 유사한 클래스를 슈퍼클래스로 추출하여 중복을 제거한다.

예시

Before

class Department {
  get totalAnnualCost() {...}
  get name() {...}
  get headCount() {...}
}

class Employee {
  get annualCost() {...}
  get name() {...}
  get id() {...}
}

After

class Party {
  get name() {...}
  get annualCost() {...}
}

class Department extends Party {
  get annualCost() {...}
  get headCount() {...}
}

class Employee extends Party {
  get annualCost() {...}
  get id() {...}
}

 

 

 

 

 

 

12. 9 계층 합치기

내용

  • 클래스 계층 구조를 리팩터링 하다보면 기능들을 위로 올리거나 아래로 하는 일은 다반사

적용

  • 클래스와 그 부모가 비슷해져서 독립적으로 존재할 필요가 없는 경우 합친다.

결과

  • 불필요 클래스 제거

예시

Before

class Employee {...}
class Salesman extends Employee {...}

After

class Employee {...}

 

 

 

12. 10 서브클래스를 위임으로 바꾸기

내용

  • 속한 갈래에 따라 동작이 달라지는 객체는 상속이 자연스럽다.
  • 단점은 한 번만 쓸 수 있는 카드가 된다. ('나이대', '소득수준'에 달리 하고싶으면 서브 클래스는 젊은이와 어르신, 부자와 서민이되며 둘다는 되지 않는다)
  • 또 다른 단점은 클래스들의 관계를 긴밀하게 결합해 부모를 수정하면 자식 기능을 해친다.

적용

  • 처음에는 상속으로 접근을 하고 문제가 생기면 위임으로 상속의 단점을 해결해준다.

결과

  • 위임은 상속을 과용하지 않게 하고, 단점 부분을 해결해준다.

예시

Before

class Order {
  get daysToShip() {
    return this._warehouse.daysToShip;
  }
}

class PriorityOrder extends Order {
  get daysToShip() {
    return this._priorityPlan.daysToShip;
  }
}

After

class Order {
  get daysToShip() {
    return (this._priorityDelegate)
      ? this._priorityDelegate.daysToShip
      : this._warehouse.daysToShip;
  }
}

class PriorityOrderDelegate {
  get daysToShip() {
    return this._priorityPlan.daysToShip
  }
}

 

 

 

12. 11 슈퍼클래스를 위임으로 바꾸기

내용

  • 객체 지향 프로그래밍에서 상속은 기존 기능을 재활용하는 강력하고 손쉬운 수단이지만, 혼란과 복잡도를 키우는 방식으로 이뤄지기도 한다.

적용

  • 기능을 온전히 사용할 수 없고 어울리지 않은 클래스를 상속 받은 경우
  • 상속을 먼저 적용하고 문제가 생기면 이 리팩터링을 진행한다.

결과

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

예시

Before

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

After

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

 

반응형