페이지

2015년 1월 4일 일요일

가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자.

통상적인 가상 함수를 이용한 방법은 다음과 같다.

class GameCharecter {
public:
  virtual int getHP() const;
  ...
};




1. 비가상 함수 인터페이스 (NVI : non-virtual interface) 관용구
   이 관용구에 쓰인 가상함수를 포함하는 비가상 함수를 가상 함수의 랩퍼(wrapper)라고 부른다.
   가상 함수가 호출되기 전에 어떤 상태를 구성하고, 호출된 후에 어떤 상태를 없애는 작업을 랩퍼를 통해 공간적으로 보장된다.
   ex) Mutex Lock/Unlock, Log 정보, 각종 유효성 Check

class GameCharecter {
public:
  virtual int getHP() const {
    ... // 가상함수 호출전 작업
    int retVal = calcHP();
    ... // 가상함수 호출후 작업
    return retVal;
  }
private:
  virtual int calcHP() const { ... }
};




2. 함수 포인터로 구현한 전략(Strategy) 패턴
   단, 계산 함수는 이제 클래스의 멤버 함수가 아니라서 public 멤버가 아닌 부분은 건드릴 수가 없다.

class GameCharacter;
int defaultCalcHP(const GameCharacter& gc);

class GameCharacter {
public:
  typedef int (*calcHP)(const GameCharacter&);
  explicit GameCharacter(calcHP chp = defaultCalcHP)
    : pCalcHP(chp) {}
  int getHP() const { return pCalcHp(*this); }
private:
  calcHP pCalcHP;
};

class EvilGuy : public GameCharacter { ... };
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);

EvilGuy e1(loseHealthQuickly);
EvilGuy e2(loseHealthSlowly);

3. tr1::function으로 구현한 전략 패턴 
   tr1::function 계열의 객체는 함수호출성 개체(callable entity)를 가질 수 있고, 이들 개체는 주어진 시점에서 예상되는 시그너처와 호환되는 시그너처를 갖고 있다.

class GameCharacter;
int defaultCalcHP(const GameCharacter& gc);

class GameCharacter {
public:
  typedef std::tr1::function<int (const GameCharacter&)> calcHP;
  explicit GameCharacter(calcHP chp = defaultCalcHP)
    : pCalcHP(chp) {}
  int getHP() const { return pCalcHp(*this); }
private:
  calcHP pCalcHP;
};

short calcHealth(const GameCharacter&);  // return이 short ?

struct HealthCalc {
  int operator() (const GameCharacter&) const { ... }
};

class GameLevel {
public:
  float health(const GameCharacter&) const; // 어라 ? 이건 float ?
};

class EvilGuy : public GameCharacter { ... };

class EvilCandyGuy : public GameCharacter { ... };

EvilGuy e1(calcHealth);
EvilCandyGuy ec(HealthCalc());

GameLevel cl;
EvilGuy e2(std::tr1::bind(&GameLevel::health, cl, _1));

 4. 고전적인 전략 패턴
     GameCharacter 와 HealthCalcFunc 의 2개의 Class를 두고 이 Class들을 상속받아서 하위 Class들만 만든다. 그리고 GameCharacter 타입의 모든 객체는 HealthCalcFunc 타입의 객체에 대한 포인터를 포함하도록 설계한다.

class GameCharacter;

class calcHP {
public:
  virtual int calc(const GameCharacter& gc) const { ... }
};

calcHP defaultCalcHP;

class GameCharacter {
public:
  explicit GameCharacter(calcHP chp = defaultCalcHP)
    : pCalcHP(chp) {}
  int getHP() const { return pCalcHp->calc(*this); }
private:
  calcHP* pCalcHP;
};



* 가상 함수 대신에 쓸 수 있는 다른 방법으로 NVI 관용구 및 전략 패턴을 들 수 있습니다. 이 중 NVI 관용구는 그 자체가 템플릿 메서드 패턴의 한 예입니다.

* 객체에 필요한 기능을 멤버 함수로부터 클래스 외부 비멤버 함수로 옮기면, 그 비멤버 함수는 그 클래스의 public 멤버가 아닌 것들을 접근할 수 없다는 단점이 생깁니다.




* tr1::function 객체는 일반화된 함수 포인터처럼 동작합니다. 이 객체는 주어진 대상 시그너처와 호환되는 모든 함수호출성 개체를 지원합니다. 

댓글 없음:

댓글 쓰기