이 글은 리팩터링 2판(마틴 파울러) 책을 참고하였습니다.

순서

I. 배경
II. 절차
III. 예시
IV. 기타


I. 배경

슈퍼클래스
: 부모클래스를 이야기하고 상속 개념을 설명하는 챕터이다.

마틴파울러는 이 활용도는 프로그램이 성장하면서 깨우치게되고
상속은 공통 요소를 찾을 때 수행하는 사례가 잦았다고 한다.

따라서 비슷한 일을 수행하는 클래스에 대한 리팩토링으로 보면 된다.

또한 이의 대안으로는 바로 전 챕터인 "클래스 추출하기"인데
이는 중복 동작을 상속으로 해결하느냐, 위임으로 해결하느냐에 달려있다.

상속 (is a 관계)
: 클래스 간의 관계로 부모의 속성을 자식이 물려받는다
-> 결합력이 높음 -> 변경가능성 적음
위임 (has a 관계)
인스턴스 간의 관계로 객체에 넘겨서 정보를 처리함
-> 다형성을 활용하여 변화나 유지보수에 탁월함.

 


II. 절차

1. 빈 슈퍼클래스를 만든다. 원래의 클래스들이 새 클래스를 상속하도록 한다.

2. 테스트한다.

3. 생성자 본문 올리기, 메서드 및 필드 올리기 적용해 공통 원소를 슈퍼클래스로 옮긴다.

4. 서브클래스에 남은 메서드들을 검토한다.

5. 원래 클래스들을 사용하는 코드 검토해 슈퍼클래스의 인터페이스 사용성 고민해본다.

 


III. 예시


코드 원본

#include <string>
#include "gtest/gtest.h"

/* Sub Class 1 */
class Employee {
 public:
  std::string name_;
  std::string id_;
  int monthly_cost_;

  Employee(std::string name, std::string id, int monthly_cost)
    : name_(name), id_(id), monthly_cost_(monthly_cost) {}

  std::string GetName() { return name_; }
  std::string GetId() { return id_; }
  int GetMonthlyCost() { return monthly_cost_; }

  int GetAnnualCost() {
    return GetMonthlyCost() * 12;
  }
};

/* Sub Class 2 */
class Department {
 public:
  std::string name_;
  std::vector<Employee> staff_;

  Department(std::string name, std::vector<Employee> staff)
    : name_(name), staff_(staff) {}

  std::string GetName() { return name_; }
  std::vector<Employee> GetStaff() { return staff_; }

  int GetTotalMonthlyCost() {
    int sum = 0;
    for (auto employee : staff_) {
      sum += employee.GetMonthlyCost();
    }
    return sum;
  }

  int GetHeadCount() {
    return staff_.size();
  }

  int GetTotalAnnualCost() {
    return GetTotalMonthlyCost() * 12;
  }
};

TEST(ExtractSuperclass, GetAnnualCost_Employee) {
  Employee employee {"Martin Fowler", "100", 10000};
  ASSERT_EQ(120000, employee.GetAnnualCost());
}

TEST(ExtractSuperclass, GetTotalMonthlyCost) {
  Employee employee1 {"Martin Fowler", "100", 12000};
  Employee employee2 {"Kent Beck", "200", 10000};
  Employee employee3 {"Erich Gamma", "300", 8000};
  Department department {"Refactoring", {employee1, employee2, employee3}};
  ASSERT_EQ(30000, department.GetTotalMonthlyCost());
}

TEST(ExtractSuperclass, GetHeadCount) {
  Employee employee1 {"Martin Fowler", "100", 12000};
  Employee employee2 {"Kent Beck", "200", 10000};
  Employee employee3 {"Erich Gamma", "300", 8000};
  Department department {"Refactoring", {employee1, employee2, employee3}};
  ASSERT_EQ(3, department.GetHeadCount());
}

TEST(ExtractSuperclass, GetTotalAnnualCost) {
  Employee employee1 {"Martin Fowler", "100", 12000};
  Employee employee2 {"Kent Beck", "200", 10000};
  Employee employee3 {"Erich Gamma", "300", 8000};
  Department department {"Refactoring", {employee1, employee2, employee3}};
  ASSERT_EQ(360000, department.GetTotalAnnualCost());
}

 


1. 빈 슈퍼클래스 만들기

테스트 코드 생략
#include <string>
#include "gtest/gtest.h"

class Party { /* 1. New Class */
public :
  Party(name) :
  m_sName(name){}
  
private :
  std::string m_sName;
};

/* Sub Class 1 */
class Employee {
 public:
  std::string name_;
  std::string id_;
  int monthly_cost_;

  Employee(std::string name, std::string id, int monthly_cost)
    : name_(name), id_(id), monthly_cost_(monthly_cost) {}

  std::string GetName() { return name_; }
  std::string GetId() { return id_; }
  int GetMonthlyCost() { return monthly_cost_; }

  int GetAnnualCost() {
    return GetMonthlyCost() * 12;
  }
};

/* Sub Class 2 */
class Department {
 public:
  std::string name_;
  std::vector<Employee> staff_;

  Department(std::string name, std::vector<Employee> staff)
    : name_(name), staff_(staff) {}

  std::string GetName() { return name_; }
  std::vector<Employee> GetStaff() { return staff_; }

  int GetTotalMonthlyCost() {
    int sum = 0;
    for (auto employee : staff_) {
      sum += employee.GetMonthlyCost();
    }
    return sum;
  }

  int GetHeadCount() {
    return staff_.size();
  }

  int GetTotalAnnualCost() {
    return GetTotalMonthlyCost() * 12;
  }
};

 


Party Class를 만들었다.
공통인 Employee, Department의 name_을 삭제해주고
생성자를 Party Class에 넣어주자.

2. 클래스의 확장

테스트 코드 생략
#include <string>
#include "gtest/gtest.h"

class Party { /* 1. New Class */
public :
  Party(name) :
  m_sName(name){}
  
private :
  std::string m_sName;
};

/* Sub Class 1 */
class Employee {
 public:
  std::string id_;
  int monthly_cost_;

/* 2. name을 Party의 생성자로 전달 */
  Employee(std::string name, std::string id, int monthly_cost)
    : Party(name), id_(id), monthly_cost_(monthly_cost) {}

  std::string GetName() { return name_; }
  std::string GetId() { return id_; }
  int GetMonthlyCost() { return monthly_cost_; }

  int GetAnnualCost() {
    return GetMonthlyCost() * 12;
  }
};

/* Sub Class 2 */
class Department {
 public:
  std::vector<Employee> staff_;

/* 2. name을 Party의 생성자로 전달 */
  Department(std::string name, std::vector<Employee> staff)
    : Party(name), staff_(staff) {}

  std::string GetName() { return name_; }
  std::vector<Employee> GetStaff() { return staff_; }

  int GetTotalMonthlyCost() {
    int sum = 0;
    for (auto employee : staff_) {
      sum += employee.GetMonthlyCost();
    }
    return sum;
  }

  int GetHeadCount() {
    return staff_.size();
  }

  int GetTotalAnnualCost() {
    return GetTotalMonthlyCost() * 12;
  }
};

 


 

3. 공통 메서드 올리기

테스트 코드 생략
#include <string>
#include "gtest/gtest.h"

class Party { /* 1. New Class */
public :
  Party(name) : m_sName(name){};
  
  /* 3. make super method */
  std::string getName() {return m_sName};
  
  virtual int getMonthlyCost() =0;
  int getAnnualCost() {
    return getMonthlyCost() * 12;
  }
  
private :
  std::string m_sName;
};

/* Sub Class 1 */
class Employee {
 public:
  std::string id_;
  int monthly_cost_;

/* 2. name을 Party의 생성자로 전달 */
  Employee(std::string name, std::string id, int monthly_cost)
    : Party(name), id_(id), monthly_cost_(monthly_cost) {}
    
  std::string GetId() { return id_; }

  virtual int getMonthlyCost() { return monthly_cost_; }
};

/* Sub Class 2 */
class Department {
 public:
  std::vector<Employee> staff_;

/* 2. name을 Party의 생성자로 전달 */
  Department(std::string name, std::vector<Employee> staff)
    : Party(name), staff_(staff) {}

  std::vector<Employee> GetStaff() { return staff_; }

  virtual int getMonthlyCost() {
    int sum = 0;
    for (auto employee : staff_) {
      sum += employee.GetMonthlyCost();
    }
    return sum;
  }

  int GetHeadCount() {
    return staff_.size();
  }
};

구현이 동일한 getAnnualCost는 공통 메서드로,
함수의 용도는 같지만 구현이 다른 getMonthlyCost는
Pure Virtual함수로 정의하여 부모 클래스로 넘겨주었다.

 


IV. 기타

슈퍼클래스라는 말을 많이 사용하였는데,
책이 Java 기준이고 C++에서는 부모클래스로 표현한다.

대부분 함수의 구현의도는 같고 구현부는 다를 때
가상함수로 오버라이드 하는데 getMonthlyCost같은 경우이다.

또한 상속을 공부할때 자주 나오는 예시인데,
동물이라는 부모클래스에 개, 고양이가 소리가 모두 다르지만
"소리를 내다"의 method는 동일할 때 해당 method를 가상함수로 만드는... 그 부분이다.

일반적으로는 소멸자를 가상함수로 하여 상속받는데,
각 객체마다 처리해야할 소멸자가 다를 수 있기 때문이다.

+ Recent posts