0

考虑以下类。(来自游戏,但大大简化了。)

战斗.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;
};

战斗.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;
}

怪物.h:

class Monster: public Combat {
public:
    Monster(int health, int offense, int defense);
    virtual ~Monster();
}

怪物.cc:

Monster::Monster(int health, int offense, int defense)
    : Combat(health, offense, defense) {}

Monster::~Monster()=default;

播放器.h:

class Player : public Combat {
public:
    Player();
    virtual ~Player();

private:
    struct PlayerImpl;
    static PlayerImpl _impl;
};

播放器.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());
    }
}

如果你运行这个程序,你会发现它不起作用。怪物的生命值应有的降低,但玩家的生命值仍停留在其初始值。我认为它发生的原因是这样的;Player 只有静态数据(封装在 PlayerImpl _impl 中) 这样我就可以拥有一个全局 Player 对象,我可以从我的代码中的不同函数调用它。(单态模式。)但它的基类 Combat 是动态的。所以发生的事情是每次我创建 Player 播放器;在 Fight() 中,我实际上得到了一个新的 Combat,其中 Combat::_health 是默认值。当玩家超出范围时,对 _health 的任何更改都会丢失。在 Monster 中,这不是问题,因为 Monster 对象也有动态数据。理想情况下我可以说

 class Player : public static Combat {

意味着只使这个特定的 Combat 成为静态,但这是一个语法错误。还有另一种方法吗?还是我把自己画在一个角落里?

4

1 回答 1

3

听起来您还没有真正考虑过您的封装层次结构。来自战斗的玩家没有多大意义,而您的实现混乱(和这个问题)支持了这一点。您已经违反了 C++ 为我们提供多重继承而不是接口这一事实,因为我相信您要描述的是 Player 具有 Combat 接口。

解决此类问题的一种常用方法是使用转发器/桥接器/委托/特征/访问器类,在这种情况下可能是“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()”?

于 2013-06-01T22:08:25.720 回答