티스토리 뷰
6. 상속, 그리고 객체 지향 설계 (2)
항목 35 : 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자
가상 함수 대신 쓸 수 있는 매서드 패턴 네 가지를 알아보자.
class GameCharacter
{
public:
// 캐릭터의 체력치를 반환하는 함수.
// 파생 클래스를 이 함수를 재정의할 수 있습니다.
virtual int healthValue() const;
};
1. 비가상 인터페이스 관용구(NVI 관용구)를 통한 템플릿 메서드 패턴
공개되지 않은 가상 함수를 비가상 public 멤버 함수로 감써서 호출하는, 템플릿 메서드 패턴의 한 형태입니다.
class GameCharacter
{
public:
// 파생 클래스는 이제 이 함수를 재정의 불가
int healthValue() const
{
// "사전" 동작 수행
...
// 실제 동작 수행
int retVal = doHealthValue();
// "사후" 동작 수행
...
return retVal;
}
...
private:
// 파생 클래스는 이 함수를 재정의 가능
virtual int healthValue() const
{
// 캐릭터의 체력치 계산을 위한 기본 알고리즘 구현
...
}
};
2. 함수 포인터로 구현한 전략(Strategy) 패턴
가상 함수를 함수 포인터 데이터 멤버로 대체합니다.
// 전방 선언
class GameCharacter;
// 체력치 계산에 대한 기본 알고리즘을 구현한 함수
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const;
{
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};
2.1. 장점
2.1.1. 같은 캐릭터 타입으로부터 만들어진 객체(인스턴스)들도 체력치 계산 함수를 각각 다르게 가질 수 있습니다.
class EvilBadGuy : public GameCharacter
{
public:
explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf)
{ ... }
...
};
// 다른 동작 원리로 구현된 체력치 계산 함수들
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);
// 같은 타입인데도 체력치 변화가 다게 나오는 케릭터들
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);
2.1.2. 게임이 실행되는 도중에 특정 캐릭터에 대한 체력치 계산 함수를 바꿀 수 있습니다.
2.2. 단점
2.2.1. 체력치가 계산되는 대상 객체의 비공개 데이터는 이 함수로 접근할 수 없습니다.
3. tr1::function으로 구현한 전략 패턴
가상 함수를 tr1::function 데이터 멤버로 대체하여, 호환되는 시그니처를 가진 함수호출성 개체를 사용할 수 있도록 만듭니다.
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
// HealthCalcFunc는 함수호출성 개체로서, GameCharacter와 호환되는
// 어떤 것이든 넘겨받아서 호출될 수 있으며 int와 호환되는 모든 타입의 객체를 반환
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const
{
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};
// 체력치 계산 함수
short calcHealth(const GameCharacter&);
struct HealthCalculator
{
// 체력치 계산용 함수 객체를 만들기 위한 클래스
int operator() (const GameCharacter&) const
{ ... }
};
class GameLevel
{
public:
// 체력치 계산에 쓰일 멤버 함수
float health(const GameCharacter&) const;
...
};
class EvilBadGuy : public GameCharacter
{ ... };
// 또 하나의 캐릭터 타입. 생성자는 EvilBadGuy와 동일
class EyeCandyCharacter : public GameCharacter
{ ... };
// 체력치 계산을 위한 함수를 사용하는 캐릭터
EvilBadGuy ebg1(calcHealth);
// 체력치 계산을 위한 함수 객체를 사용하는 캐릭터
EyeCandyCharacter ecc1(HealthCalculator());
GameLevel currentLevel;
...
// ebg2의 체력치 계산 함수는 항상 currentLevel만을 GameLevel 객체로 쓴다고 지정
EvilBadGuy ebg2(
std::tr1::bind(&GameLevel::health, currentLevel, _1)
);
[참고] Boost.Bind http://www.boost.org/doc/libs/1_58_0/libs/bind/doc/html/bind.html
4. "고전적인" 전략 패턴
한쪽 클래스 계통에 속해 있는 가상 함수를 다른 계통에 속해 있는 가상 함수로 대체합니다.
class GameCharacter;
class HealthCalcFunc
{
public:
...
virtual int calc(const GameCharacter& gc) const
{ ... }
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter
{
public:
explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc) : pHealthCalc(phcj)
{ }
int healthValue() const
{ return pHealthCalc->calc(*this); }
...
private:
HealthCalcFunc *pHealthCalc;
};
- 가상 함수 대신에 쓸 수 있는 다른 방법으로 NVI 관용구 및 전략 패턴을 들 수 있습니다. 이 중 NVI 관용구는 그 자체가 템플릿 메서드 패턴의 한 예입니다.
- 객체에 필요한 기능을 멤버 함수로부터 클래스 외부의 비멤버 함수로 옮기면, 그 멤버 함수는 그 클래스의 public 멤버가 아닌 것들을 접근할 수 없다는 단점이 생깁니다.
- tr1:;function 객체는 일반화된 함수 포인터처럼 동작합니다. 이 객체는 주어진 대상 시그너처와 호환되는 모든 함수호출성 개체를 지원합니다.
항목 36 : 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!
class B
{
public:
void mf();
...
};
class D : public B { ... };
D x;
B *pB = &x;
pB->mf(); // D::mf 호출되어야 합니다.
D *pD = &x;
pD->mf(); // D::mf 호출되어야 합니다.
- B 객체에 해당되는 모든 것들이 D 객체에 그대로 적용됩니다. 왜냐하면 모든 D 객체는 B 객체의 일종이기 때문입니다.
- B에서 파생된 클래스는 mf 함수의 인터페이스와 구현을 모두 물려받게 됩니다. mf는 B 클래스에서 비가상 멤버 함수이기 때문입니다.
비가상 함수를 파생 클래스에서 재정의하면 비가상 함수는 정적 바인딩을 하기 때문에 public 상속 설계에 모순이 생깁니다.
class D : public B
{
public:
// B::mf를 가림.(항목 33 참조)
void mf();
...
};
pB->mf(); // B::mf 호출
pD->mf(); // D::mf 호출
- 상속받은 비가상 함수를 재정의하는 일은 절대로 하지 맙시다.
항목 37 : 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지 말자
1. 문제 - 상속 받은 기본 매개변수 재정의
class Shape
{
public:
enum ShapeColor {Red, Green, Blue};
// 모든 도형은 자기 자신을 그리는 함수를 제공해야 합니다.
virtual void draw(ShapeColor color = Red) const = 0;
...
};
class Rectangle : public Shape
{
public:
virtual void draw(ShapeColor color = Green) const = 0; ...
};
class Circle : public Shape
{
public:
virtual void draw(ShapeColor color) const;
...
};
파생 클래스에 정의된 가상 함수를 호출하면서 기본 클래스에 정의된 기본 매개변수 값을 사용해 버릴 수 있습니다. 가상 함수는 동적으로 바인딩되어 있지만 기본 매개변수는 정적으로 바인딩되어 있기 때문입니다.
// 모두 정적 타입 = Shape*
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
// Circle::draw(Shape::Red)를 호출합니다.
pc->draw(Shape::Red);
// Rectangle::draw(Shape::Red)를 호출합니다.
pr->draw(Shape::Red);
// Rectangle::draw(Shape::Red)를 호출합니다!
pr->draw();
2. 해결 - 비가상 인터페이스(non-virtual interface) 관용구
class Shape
{
public:
enum ShapeColor {Red, Green, Blue};
// 이제는 비가상 함수입니다.
void draw(ShapeColor color = Red) const
{
// 가상 함수를 호출합니다.
doDraw(color);
}
...
private:
// 진짜 작업은 이 함수에서 이루어집니다.
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle : public Shape
{
public:
...
private:
// 기본 매개변수 값이 없습니다.
virtual void doDraw(ShapeColor color) const;
...
};
- 상속받은 기본 매개변수 값은 절대로 재정의해서는 안 됩니다. 왜냐하면 기본 매개변수 값은 정적으로 바인딩되는 반면, 가상 함수(오버라이드할 수 있는 유일한 함수)는 동적으로 바인딩되기 때문입니다.
- James Song
'Books_tech' 카테고리의 다른 글
다시 읽는 <Effective C++> Chapter 7 요약 (1) (0) | 2015.12.09 |
---|---|
다시 읽는 <Effective C++> Chapter 6 요약 (3) (0) | 2015.12.01 |
다시 읽는 <Effective C++> Chapter 6 요약 (1) (0) | 2015.11.20 |
다시 읽는 <Effective C++> Chapter 5 요약 (2) (0) | 2015.11.12 |
다시 읽는 <Effective C++> Chapter 5 요약 (1) (1) | 2015.11.09 |
- #로버트마틴
- #임백준
- #scottmeyers
- #cplusplus
- Scott Meyers
- #제럴드와인버그
- #세미나
- #ndc
- #레거시코드
- #ModernCPP
- #프로그래밍심리학
- #build2016
- 책
- #EffectiveModernCpp
- #스콧마이어스
- 상속
- #알고리즘
- #마이클페더스
- #techdays2015
- 객체 지향 설계
- #팀개발
- #csharp
- Effective Modern C++
- #cpp
- #코드최적화
- #mva
- #uwp
- Effective C++
- #자녀교육
- #클린코드
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |