이 글은 리팩터링 2판(마틴 파울러) 책을 참고하였습니다.
순서
I. 배경
II. 예시
I. 배경
앞선 조건부로직을 다형성으로 바꾸기1 에서는
저명하게 알려진 팩토리 패턴으로, 또 Override방식을 이용하여
상속을 사용하였다.
하지만 상속을 사용하는 위의 목적 이외에 꽤나 빈번하게 사용되는 목적이 있다.
"거의 똑같은 객체지만 다른 부분도 있음을 표현"할 때이다.
II. 예시
코드 원본
코드 중 실제 동작 알고리즘(Test코드)을 보면
"HistoryItem", "Voyage" 두 클래스를 생성해
각 메소드들을 실행시키는 구조이다.
#include <algorithm>
#include <list>
#include <string>
#include "gtest/gtest.h"
class Voyage {
public:
std::string zone_;
int length_;
};
class HistoryItem {
public:
std::string zone_;
int profit_;
};
using History = std::list<HistoryItem>;
/* 여기서부터 methods */
bool HasChina(History* history) {
auto it = history->begin();
while (it != history->end()) {
if (it->zone_ == "china") {
return true;
}
++it;
}
return false;
}
int VoyageProfitFactor(Voyage* voyage, History* history) {
int result = 2;
if (voyage->zone_ == "china") result += 1;
if (voyage->zone_ == "east-indies") result += 1;
if (voyage->zone_ == "china" && HasChina(history)) {
result += 3;
if (history->size() > 10) result += 1;
if (voyage->length_ > 12) result += 1;
if (voyage->length_ > 18) result -= 1;
} else {
if (history->size() > 8) result += 1;
if (voyage->length_ > 14) result -= 1;
}
return result;
}
int VoyageRisk(Voyage* voyage) {
int result = 1;
if (voyage->length_ > 4) result += 2;
if (voyage->length_ > 8) result += voyage->length_ - 8;
if (voyage->zone_ == "china" || voyage->zone_ == "east-indies") result += 4;
return std::max(result, 0);
}
int CaptainHistoryRisk(Voyage* voyage, History* history) {
int result = 1;
if (history->size() < 5) result += 4;
auto it = history->begin();
while (it != history->end()) {
if (it->profit_ < 0) {
result += history->size();
}
++it;
}
if (voyage->zone_ == "china" && HasChina(history)) result -= 2;
return std::max(result, 0);
}
std::string GetRatingValue(Voyage* voyage, History* history) {
int vpf = VoyageProfitFactor(voyage, history);
int vr = VoyageRisk(voyage);
int chr = CaptainHistoryRisk(voyage, history);
if (vpf * 3 > (vr + chr * 2)) {
return "A";
}
return "B";
}
TEST(ReplaceConditionalWithPolymorphism, VoyageProfitFactor_WestIndies) {
Voyage voyage = {"west-indies", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", -2}, {"west-africa", 7}};
ASSERT_EQ(2, VoyageProfitFactor(&voyage, &history));
}
TEST(ReplaceConditionalWithPolymorphism, VoyageProfitFactor_China) {
Voyage voyage = {"china", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", -2}, {"west-africa", 7}};
ASSERT_EQ(6, VoyageProfitFactor(&voyage, &history));
}
TEST(ReplaceConditionalWithPolymorphism, CaptainHistoryRisk_WestIndies) {
Voyage voyage = {"west-indies", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", -2}, {"west-africa", 7}};
ASSERT_EQ(9, CaptainHistoryRisk(&voyage, &history));
}
TEST(ReplaceConditionalWithPolymorphism, CaptainHistoryRisk_China) {
Voyage voyage = {"china", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", -2}, {"west-africa", 7}};
ASSERT_EQ(7, CaptainHistoryRisk(&voyage, &history));
}
TEST(ReplaceConditionalWithPolymorphism, Rating_WestIndies) {
Voyage voyage = {"west-indies", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", -2}, {"west-africa", 7}};
ASSERT_EQ("B", GetRatingValue(&voyage, &history));
}
TEST(ReplaceConditionalWithPolymorphism, Rating_China) {
Voyage voyage = {"china", 10};
History history = {{"east-indies", 5}, {"west-indies", 15}, {"china", 0}, {"west-africa", 7}};
ASSERT_EQ("A", GetRatingValue(&voyage, &history));
}
위의 기본 동작을 모두 메소드로 가진
"Rating"이라는 클래스를 새로 만들자.
1. 새로운 동작 클래스 생성하기
#include <algorithm>
#include <list>
#include <string>
#include "gtest/gtest.h"
class Voyage {
public:
std::string zone_;
int length_;
};
class HistoryItem {
public:
std::string zone_;
int profit_;
};
using History = std::list<HistoryItem>;
/* 1. New Class */
/* 사실상 클래스 안에 다 집어 넣기만 함 */
class CRating {
public:
bool HasChina(History* history) {
auto it = history->begin();
while (it != history->end()) {
if (it->zone_ == "china") {
return true;
}
++it;
}
return false;
}
int VoyageProfitFactor(Voyage* voyage, History* history) {
int result = 2;
if (voyage->zone_ == "china") result += 1;
if (voyage->zone_ == "east-indies") result += 1;
if (voyage->zone_ == "china" && HasChina(history)) {
result += 3;
if (history->size() > 10) result += 1;
if (voyage->length_ > 12) result += 1;
if (voyage->length_ > 18) result -= 1;
} else {
if (history->size() > 8) result += 1;
if (voyage->length_ > 14) result -= 1;
}
return result;
}
int VoyageRisk(Voyage* voyage) {
int result = 1;
if (voyage->length_ > 4) result += 2;
if (voyage->length_ > 8) result += voyage->length_ - 8;
if (voyage->zone_ == "china" || voyage->zone_ == "east-indies") result += 4;
return std::max(result, 0);
}
int CaptainHistoryRisk(Voyage* voyage, History* history) {
int result = 1;
if (history->size() < 5) result += 4;
auto it = history->begin();
while (it != history->end()) {
if (it->profit_ < 0) {
result += history->size();
}
++it;
}
if (voyage->zone_ == "china" && HasChina(history)) result -= 2;
return std::max(result, 0);
}
std::string GetRatingValue(Voyage* voyage, History* history) {
int vpf = VoyageProfitFactor(voyage, history);
int vr = VoyageRisk(voyage);
int chr = CaptainHistoryRisk(voyage, history);
if (vpf * 3 > (vr + chr * 2)) {
return "A";
} else {
return "B";
}
}
}; // 클래스 끝
그냥 클래스안에 함수를 넣기만 했다.
코드를 가만보면 China지역과, China History가 있을 때 예외가 발생한다.
이 예외처리를 따로 처리해주는 Class를 만들자.
이번 챕터의 주제인 "거의 똑같은 객체지만 다른 부분도 있음"을 구현.
2. 특정 동작을 위한 서브클래스 생성하기
#include <algorithm>
#include <list>
#include <string>
#include "gtest/gtest.h"
class Voyage {
public:
std::string zone_;
int length_;
};
class HistoryItem {
public:
std::string zone_;
int profit_;
};
using History = std::list<HistoryItem>;
/* 1. New Class */
/* 2. 멤버 변수를 통해 함수형태 바꿈 */
class CRating {
public:
/* 2. 멤버변수를 사용하도록 한다. */
CRating(Voyage* voyage, History* history)
: voyage_(voyage), history_(history) {}
History* history_;
Voyage* voyage_;
bool HasChina() {
auto it = history_->begin();
while (it != history_->end()) {
if (it->zone_ == "china") {
return true;
}
++it;
}
return false;
}
int VoyageProfitFactor(Voyage* voyage, History* history) {
int result = 2;
if (voyage->zone_ == "china") result += 1;
if (voyage->zone_ == "east-indies") result += 1;
if (history->size() > 8) result += 1;
if (voyage->length_ > 14) result -= 1;
return result;
}
int VoyageRisk(Voyage* voyage) {
int result = 1;
if (voyage->length_ > 4) result += 2;
if (voyage->length_ > 8) result += voyage->length_ - 8;
if (voyage->zone_ == "china" || voyage->zone_ == "east-indies") result += 4;
return std::max(result, 0);
}
int CaptainHistoryRisk() {
int result = 1;
if (history_->size() < 5) result += 4;
auto it = history_->begin();
while (it != history_->end()) {
if (it->profit_ < 0) {
result += history_->size();
}
++it;
}
return std::max(result, 0);
}
std::string GetRatingValue() {
int vpf = VoyageProfitFactor();
int vr = VoyageRisk();
int chr = CaptainHistoryRisk();
if (vpf * 3 > (vr + chr * 2)) {
return "A";
} else {
return "B";
}
}
}; // 클래스 끝
/* 2. New Class for china */
class CChinaRating : public CRating {
public:
using CRating:CRating;
int VoyageProfitFactor() {
int result = 1;
result += 3;
if (history->size() > 10) result += 1;
if (voyage->length_ > 12) result += 1;
if (voyage->length_ > 18) result -= 1;
}
int CaptainHistoryRisk() {
int result = 1;
if (history_->size() < 5) result += 4;
auto it = history_->begin();
while (it != history_->end()) {
if (it->profit_ < 0) {
result += history_->size();
}
++it;
}
result -= 2;
return std::max(result, 0);
}
}; // End China Class
이렇게 새로운 China의 경우를 위한 서브클래스를 따로 생성하였다.
기타 변경된 부분은 CRating에서 멤버변수를 사용한 것이다.
그러면 이제 두 클래스 중 어떤 Instance를 가져갈지 Factory를 만드는 것이다.
"createRating"을 새로 만들어보자.
또한 CRating에 "VoyageProfitFactor()"과 "VoyageRisk()"는
동인도도 포함되어 있어 일방적으로 떼어내기 힘들다.
이럴때는 조건을 통째로 함수로 추출해준다.
3. Factory 만들기, 기타 리팩토링
#include <algorithm>
#include <list>
#include <string>
#include "gtest/gtest.h"
class Voyage {
public:
std::string zone_;
int length_;
};
class HistoryItem {
public:
std::string zone_;
int profit_;
};
using History = std::list<HistoryItem>;
class CRating {
public:
CRating(Voyage* voyage, History* history)
: voyage_(voyage), history_(history) {}
History* history_;
Voyage* voyage_;
/* 3. hasChina 밖으로 추출 */
/* result를 얻는 조건문 만들어줌 */
int getVoyageHistoryLengthFactor() {
result = 0;
if (history_->size() > 8) result += 1;
if (voyage_->length_ >14) result -= 1;
return result;
}
int VoyageProfitFactor() {
int result = 2;
if (voyage_->zone_ == "china") result += 1;
if (voyage_->zone_ == "east-indies") result += 1;
result += getVoyageHistoryLengthFactor();
return result;
}
int VoyageRisk() {
int result = 1;
if (voyage_->length_ > 4) result += 2;
if (voyage_->length_ > 8) result += voyage_->length_ - 8;
if (voyage_->zone_ == "china" || voyage_->zone_ == "east-indies") result += 4;
return std::max(result, 0);
}
int CaptainHistoryRisk() {
int result = 1;
if (history_->size() < 5) result += 4;
auto it = history_->begin();
while (it != history_->end()) {
if (it->profit_ < 0) {
result += history_->size();
}
++it;
}
return std::max(result, 0);
}
std::string GetRatingValue() {
int vpf = VoyageProfitFactor();
int vr = VoyageRisk();
int chr = CaptainHistoryRisk();
if (vpf * 3 > (vr + chr * 2)) {
return "A";
}
return "B";
}
}; // Class 끝
/* 2. New Class for china */
class CChinaRating : public CRating {
public:
using CRating:CRating;
int getVoyageHistoryLengthFactor() {
result = 0;
if (history_->size() > 10) result += 1;
if (voyage_->length_ > 12) result += 1;
if (voyage_->length_ > 18) result -= 1;
return result;
}
int VoyageProfitFactor() {
int result = 3;
result += getVoyageHistoryLengthFactor() + 3;
}
int CaptainHistoryRisk() {
int result = 1;
if (history_->size() < 5) result += 4;
auto it = history_->begin();
while (it != history_->end()) {
if (it->profit_ < 0) {
result += history_->size();
}
++it;
}
result -= 2;
return std::max(result, 0);
}
}; // China Class 끝
/* 3. Method for Factory */
bool HasChina(History* history) {
auto it = history->begin();
while (it != history->end()) {
if (it->zone == "china") {
return true;
}
++it;
}
return false;
}
CRating* createRating(Voyage* voyage, History* history) {
if (voyage->zone_ == "china" && HasChina(history))
return new CChinaRating(vayage, history);
else
return new CRating(voyage, history);
}
미완..
'Programming > Refactoring(C++)' 카테고리의 다른 글
[Refactoring] 14. 조건부로직을 다형성으로 바꾸기 (For Repeated Switches) (0) | 2022.10.31 |
---|---|
[Refactoring] 13. 타입코드를 서브클래스로 바꾸기 (For Large Class) (0) | 2022.10.27 |
[Refactoring] 12. 슈퍼클래스 추출하기 (For Large Class) (0) | 2022.10.23 |
[Refactoring] 11. 클래스 추출하기 (For Large Class) (0) | 2022.10.21 |