이 글은 리팩터링 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를 가상함수로 만드는... 그 부분이다.
일반적으로는 소멸자를 가상함수로 하여 상속받는데,
각 객체마다 처리해야할 소멸자가 다를 수 있기 때문이다.
'Programming > Refactoring(C++)' 카테고리의 다른 글
[Refactoring] 14. 조건부로직을 다형성으로 바꾸기 (For Repeated Switches) (0) | 2022.10.31 |
---|---|
[Refactoring] 13. 타입코드를 서브클래스로 바꾸기 (For Large Class) (0) | 2022.10.27 |
[Refactoring] 11. 클래스 추출하기 (For Large Class) (0) | 2022.10.21 |
[Refactoring] 10. 반복문을 파이프라인으로 바꾸기 (For Loop Smell) (0) | 2022.10.20 |