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;

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());
    }
}

このプログラムを実行すると、機能しないことがわかります。モンスターの体力は当然減少しますが、プレイヤーの体力は初期値のままです。それが起こっていると私が思う理由はこれです。Player には静的データしかありません (PlayerImpl _impl にカプセル化されています)。これは、コード内のさまざまな関数から呼び出すことができる 1 つのグローバル Player オブジェクトを持つことができるようにするためです。(モノステート パターン。) しかし、その基本クラスの Combat は動的です。つまり、Player プレーヤーを作成するたびに、何が起こっているのかということです。Fight() では、実際に Combat::_health がデフォルト値である新しい Combat を取得しています。プレイヤーが範囲外になると、_health への変更はすべて失われます。Monster では、Monster オブジェクトにも動的データがあるため、これは問題になりません。と言えるのが理想です。

 class Player : public static Combat {

この特定の戦闘のみを静的にすることを意味しますが、それは構文エラーです。それを行う別の方法はありますか?それとも、私は自分自身を隅に追いやったのですか?

4

1 に答える 1

3

カプセル化の階層についてあまり考えていないようです。戦闘から派生したプレイヤーはあまり意味がなく、実装の混乱 (およびこの問題) がそれを裏付けています。あなたが説明しようとしているのは、Player が Combat インターフェースを持っているということだと私は信じているので、C++ がインターフェースの代わりに多重継承を提供するという事実に違反しました。

この種の問題を解決するための一般的なアプローチは、フォワーダー/ブリッジ/デリゲート/トレイト/アクセサー クラスを使用することです。この場合は、おそらく「Combatant」または「CombatHandler」または「CombatEntity」です。継承をどのように読み取るかによって異なります。その唯一の目的は、カプセル化グラフをトラバースするのを支援することです。この場合、エンティティから、そのクラスのエンティティの戦闘機能のカプセル化まで。

これらの中間クラスは単純であることを意図しておらず、相互接続ロジックに限定されています。実際の機能を入れないでください。すべてのメンバーを const に保つようにしてください。

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 {
    ...
};

リファクタリングする必要がある 2 番目のことは、呼び出しの代わりにパラメーターなしでグローバル関数を呼び出す原因となっているものです。

monster.fight(player);
//or
player.fight(monster);

これは、フレームを実装しようとしていて、まだカプセル化していないためだと思われます。そのため、フレームは参加者が誰であるかを認識せず、グローバルを使用して強制しています。

オリジナルをもう一度見て、スタティックの使用がどのように手を下に追いやったかを確認してください: 戦闘の詳細と認識を Player クラスに引き上げ、カプセル化をさらに壊します。

シングルトンやグローバルを絶対に避ける必要があると言っているわけではありません。自分で確認してください。この情報は、「PrawnShriveller」や「MP3Player」など、どのクラスでも表示可能であり、変更可能であると本当に言いたかったのですか?グローバル関数「WhenIdleFormatHardDriveCatchFireOrDoOtherThings()」と同様に?

于 2013-06-01T22:08:25.720 に答える