이 글은 리팩터링 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);
}

미완..

+ Recent posts