考虑以下课程。 (从游戏中可以大大简化。)
combat.h:
class Combat {
public:
Combat();
Combat(int health, int offense, int defense);
virtual ~Combat();
int attack();
int defend();
int health() const;
void setHealth(int health);
private:
struct CombatImpl;
std::unique_ptr<CombatImpl> _impl;
};
combat.cc:
struct Combat::CombatImpl {
CombatImpl();
CombatImpl(int health, int offense, int defense);
~CombatImpl()=default;
int _health;
int _offense;
int _defense;
};
Combat::Combat(int health, int offense, int defense) :
_impl { new Combat::CombatImpl(health, offense, defense) } {
}
Combat::~Combat()=default;
int Combat::attack() {
int hits = 0;
for(int i = 0; i < _impl->_offense; i++ ) {
if (rand() % 6 == 5) {
hits++;
}
}
return hits;
}
int Combat::defend() {
int parries = 0;
for(int i = 0; i < _impl->_defense; i++ ) {
if (rand() % 6 == 5) {
parries++;
}
}
return parries;
}
int Combat::health() const {
return _impl->_health;
}
void Combat::setHealth(int health) {
_impl->_health += health;
}
Combat::CombatImpl::CombatImpl(int health, int offense, int defense) {
_health = health;
_offense = offense;
_defense = defense;
}
monster.h:
class Monster: public Combat {
public:
Monster(int health, int offense, int defense);
virtual ~Monster();
}
monster.cc:
Monster::Monster(int health, int offense, int defense)
: Combat(health, offense, defense) {}
Monster::~Monster()=default;
player.h:
class Player : public Combat {
public:
Player();
virtual ~Player();
private:
struct PlayerImpl;
static PlayerImpl _impl;
};
player.cc:
struct Player::PlayerImpl {
PlayerImpl()=default;
~PlayerImpl()=default;
} Player::_impl;
Player::Player() : Combat(17, 1, 1) {
}
Player::~Player()=default;
...最后,一个使用它们的测试程序:
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <memory>
using namespace std;
#include "monster.h"
#include "player.h"
static Monster monster(3, 1, 1);
void fight() {
Player player;
int damage = monster.attack();
damage -= player.defend();
if ( damage > 0 ) {
player.setHealth(-damage);
}
if ( player.health() < 1 ) {
return;
}
damage = player.attack();
damage -= monster.defend();
if ( damage > 0 ) {
monster.setHealth(-damage);
}
if ( monster.health() < 1 ) {
return;
}
}
int main() {
Player player;
srand(time(NULL));
while (player.health() > 0 && monster.health() > 0) {
fight();
printf("player health = %d monster health = %d\n", player.health(),
monster.health());
}
}
如果您运行此程序,您将看到它不起作用。怪物的健康状况应该会降低,但是玩家的健康状况会停留在初始值。我认为它发生的原因是这个;播放器只有静态数据(封装在PlayerImpl _impl中)这样我就可以拥有一个全局播放器对象,我可以在代码中使用不同的函数调用它。 (monostate模式。)但它的基类Combat是动态的。那么我每次创建播放器播放器时都会发生什么。在fight()中,我实际上得到了一个新的Combat,其中Combat :: _ health是默认值。当玩家超出范围时,对_health的任何更改都将丢失。在怪物中,这不是问题,因为怪物对象也有动态数据。理想情况下,我可以说
class Player : public static Combat {
表示make static这个特殊的Combat但这是一个语法错误。还有另一种方法吗?或者我把自己画成了角落?
答案 0 :(得分:3)
听起来你还没有真正考虑过你的封装层次结构。从战斗中获得的玩家没有多大意义,你的实施混乱(和这个问题)支持了这一点。你已经认识到C ++为我们提供了多重继承而不是接口,因为我相信你想要描述的是Player有一个Combat接口。
清除此类问题的常用方法是使用forwarder / bridge / delegate / trait / accessor类,在这种情况下可能是“Combatant”或“CombatHandler”或“CombatEntity” - 所有这些都取决于您希望继承的方式阅读 - 其唯一目的是帮助您遍历封装图;在这种情况下,从实体到该类实体的斗争功能的封装。
这些中间类不是简单的,仅限于互连逻辑。不要在其中加入任何实际功能;尽量让所有成员保持不变。
class Combatant {
public:
Combatant() {}
virtual const Combat* Combat() const = 0; // so combat is technically our impl
virtual Combat* Combat() = 0;
// keep this interface light, it's primarily an accessor interface.
virtual bool CanFight() const { return (Combat() != nullptr); }
virtual bool CanFight(Combatant* opponent_) const {
return (opponent_ != nullptr && CanFight() && opponent_->CanFight());
}
};
class PassiveEntity() : Combatant {
...
const Combat* Combat() const { return nullptr; }
Combat* Comat() { return nullptr; }
}
class Player : public Combatant {
public:
virtual const Combat* Combat() const override {
// if you HAVE to use a static, something like this.
return &s_playerCombatImpl;
}
virtual Combat* Combat() override {
// but really it should be a member so it can be stateful.
return &m_combat;
}
...
};
class Monster : public Combatant {
...
};
class Corpse : public PassiveEntity {
...
};
你应该重构的第二件事是导致你在没有参数的情况下调用全局函数而不是调用
monster.fight(player);
//or
player.fight(monster);
我怀疑这是因为你正在尝试实现框架并且还没有封装它,所以框架不知道参与者是谁,而你通过使用全局变形来强迫它。
再看一下你的原文并回顾一下静态的使用是如何迫使你的手更进一步:将战斗细节和意识提升到Player类中,进一步打破你的封装。
这并不是说必须不惜一切代价避免单身人士或全球人,只要确保你自己检查一下 - 你是否真的想说这些信息是可见的并且可以由任何班级修改,包括“PrawnShriveller”和“ MP3Player“,以及全局函数”WhenIdleFormatHardDriveCatchFireOrDoOtherThings()“?