관리 메뉴

너와 나의 스토리

[리팩터링] 슈퍼클래스 추출하기 Extract Superclass 본문

개발/Refactoring

[리팩터링] 슈퍼클래스 추출하기 Extract Superclass

노는게제일좋아! 2022. 2. 16. 08:07
반응형

배경

  • 비슷한 일을 수행하는 두 클래스가 보이면 상속 메커니즘을 이용해서 비슷한 부분을 공통의 슈퍼클래스로 옮겨 담을 수 있다.
  • 슈퍼클래스 추출하기의 대안으로는 클래스 추출하기가 있다.
  • 어느 것을 선택하느냐는 중복 동작을 상속으로 해결하느냐 위임으로 해결하느냐에 달렸다.
  • 슈퍼클래스 추출하기 방법이 더 간단할 경우가 많으니 이 리팩터링을 먼저 시도해보길 권한다.

 

절차

  1. 빈 슈퍼클래스를 만든다. 원래의 클래스들이 새 클래스를 상속하도록 한다.
  2. 테스트한다.
  3. 생성자 본문 올리기, 메서드 올리기, 필드 올리기를 차례로 적용하여 공통 원소를 슈퍼클래스로 옮긴다.
  4. 서브클래스에 남은 메서드들을 검토한다. 공통되는 부분이 있다면 함수로 추출한 다음 메서드 올리기를 적용한다.
  5. 원래 클래스들을 사용하는 코드를 검토하여 슈퍼클래스의 인터페이스를 사용하게 할지 고민해본다.

 

예시

  • 다음 두 클래스를 사용하고 있는데, 공통된 기능이 눈에 띈다.
    • class Employee {
          constructor(name, id, monthlyCost) {
              this._id = id;
              this._name = name;
              this._monthlyCost = monthlyCost;
          }
          get monthlyCost() { return this._monthlyCost; } // 월간 비용
          get name() { return this._name; } // 이름
          get id() { return this._id; }
      
          get annualCost() {
              return this.monthlyCost * 12;
          }
      }
      
      class Department {
          constructor(name, staff) {
              this._name = name;
              this._staff = staff;
          }
          get staff() { return this._staff.slice(); }
          get name() { return this._name; } // 이름
      
          get totalMonthlyCost() {
              return this.staff.map(e => e.monthlyCost)
                  .reduce((sum, cost) => sum + cost);
          }
          get headCount() {
              return this.staff.length;
          }
          get totalAnnualCost() {
              return this.totalMonthlyCost * 12;
          }
      }
  • 두 클래스로부터 슈퍼클래스를 추출해보자.
  • [Step 1] 빈 슈퍼클래스를 만들고, 두 클래스가 이를 확장하도록 한다.
    • class Party { }
      
      class Employee extends Party {
          constructor(name, id, monthlyCost) {
              super();
              this._id = id;
              this._name = name;
              this._monthlyCost = monthlyCost;
          }
          // ...
      }
      
      class Department extends Party {
          constructor(name, staff) {
              super();
              this._name = name;
              this._staff = staff;
          }
          // ...
      }
  • [Step 3] 데이터부터 슈퍼클래스(Party)로 올려주자.
    • 먼저 이름 속성을 위로 올려보자.
    • class Party {
          constructor(name) {
              this._name = name;
          }
      }
      
      class Employee extends Party {
          constructor(name, id, monthlyCost) {
              super(name);
              this._id = id;
              this._monthlyCost = monthlyCost;
          }
          // ...
      }
      
      class Department extends Party {
          constructor(name, staff) {
              super(name);
              this._staff = staff;
          }
          // ...
      }
    • 다음은 데이터와 관련된 메서드들을 옮겨보자.
    • class Party {
          constructor(name) {
              this._name = name;
          }
          get name() { return this._name; }
      }
    • // Employee 클래스
      get name() {return this._name; }

      // Department 클래스
      get name() {return this._name; }
    • 다음으로, 구현 로직이 비슷한 메서드가 두 개 보인다.
    • // Employee 클래스
      get monthlyCost() { return this._monthlyCost; }
      
      get annualCost() {
          return this.monthlyCost * 12;
      }
      
      // Department 클래스
      get totalMonthlyCost() {
              return this.staff.map(e => e.monthlyCost)
                  .reduce((sum, cost) => sum + cost);
          }
          
      get totalAnnualCost() {
          return this.totalMonthlyCost * 12;
      }
    • annualCost()와 totalAnnualCost() 이 두 메서드에서 호출하는 메서드는 이름도 다르고 본문 코드도 다르다. 
    • 하지만 의도는 같다. 그렇다면 함수 선언 바꾸기로 이름을 통일한다.
    • // Department 클래스
      get annualCost() { // Before: totatlAnnualCost()
          return this.monthlyCost * 12;
      }
      
      get monthlyCost() {...} // Before: totalMonthlyCost()
    • 이제 두 연간 비용 산출 메서드에 메서드 올리기를 적용할 수 있다.
  • Before After
    class Department {
         get totalAnnualCost() { }
         get name() { }
         get headCount() { }
    }

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

    class Department extends Party {
         get headCount() { }
         get monthlyCost() { }
    }

    class Employee extends Party {
         get id() { }
         get monthlyCost() { }
    }

 

 

 

출처:

- [리팩터링 2판]

반응형
Comments