在 C++ 中删除 类 之间的循环依赖
Removing circular dependencies between classes in C++
假设我们正在制作一款两人纸牌游戏,我们有 class 名称为游戏、玩家和纸牌。游戏包含指向两个玩家的指针,并为玩家提供一个界面。 Player 由玩家的健康状况和他们的魔法以及他们的手牌矢量组成。 Card 是一个抽象 class。每张牌都需要魔法来玩,并且可以玩。
问题是每张牌在打出时都可以通过多种方式改变游戏状态。例如,我们可以有一张使玩家的生命值翻倍的卡片,一张使敌方玩家中毒两回合的卡片,一张摧毁棋盘上所有随从的卡片,一张为棋盘上已有的每个随从创建副本的卡片,等等. 可能性真的是无穷无尽的。我能看到的唯一解决方案是在每张卡片中有一个指向 Game 的指针,但这似乎相当不雅。
还有其他解决方案吗?
你的基本想法是正确的,但你应该进一步增强它以保持高水平的抽象和封装。
因此,在您的示例中,假设您有一个 Card
class,它是现有 class.
的一个实例
你可以做的第一件事就是将卡的效果从卡本身中分离出来,例如:
class CardEffect {
// TODO ...
};
class Card {
private:
const CardEffect* effect;
};
所以现在卡片本身不需要知道任何关于游戏的信息。但让我们更深入地了解一下:CardEffect
不需要知道 Game
class 的每个细节,它需要能够做的是将效果应用到游戏中。这可以通过为效果提供一个单独的界面来完成,该界面只公开应用效果所需的内容,所以像这样。
class GameInterface {
public:
virtual const std::vector<Player*>& getPlayers() = 0;
virtual damagePlayer(Player* player, int amount) = 0;
virtual applyPeriodicDamage(Player* player, int turns, int amount) = 0
..
};
class CardEffect {
virtual applyEffect(GameInterface* interface) = 0;
};
class Card {
private:
const CardEffect* effect;
};
现在这只是一个例子,对于你的问题没有确定的解决方案,因为每个特定场景都不同并且有不同的要求,它只是给你一个基本的想法,让你知道如何在保持代码优雅和封装的同时使其具有足够的描述性以便于管理。
每当你有像这样的交叉依赖时:
class Card
{
Game* game;
void f() {
game->method1();
}
};
class Game
{
std::vector<Card> cards;
public:
void method1();
};
其中一个依赖项应实现如下接口:
class IGame
{
public:
virtual void method1()=0;
};
class Card
{
IGame* game;
void f() {
game->method1();
}
};
class Game : public IGame
{
std::vector<Card> cards;
public:
virtual void method1();
};
正如您所看到的,不再有交叉依赖。
假设我们正在制作一款两人纸牌游戏,我们有 class 名称为游戏、玩家和纸牌。游戏包含指向两个玩家的指针,并为玩家提供一个界面。 Player 由玩家的健康状况和他们的魔法以及他们的手牌矢量组成。 Card 是一个抽象 class。每张牌都需要魔法来玩,并且可以玩。
问题是每张牌在打出时都可以通过多种方式改变游戏状态。例如,我们可以有一张使玩家的生命值翻倍的卡片,一张使敌方玩家中毒两回合的卡片,一张摧毁棋盘上所有随从的卡片,一张为棋盘上已有的每个随从创建副本的卡片,等等. 可能性真的是无穷无尽的。我能看到的唯一解决方案是在每张卡片中有一个指向 Game 的指针,但这似乎相当不雅。
还有其他解决方案吗?
你的基本想法是正确的,但你应该进一步增强它以保持高水平的抽象和封装。
因此,在您的示例中,假设您有一个 Card
class,它是现有 class.
你可以做的第一件事就是将卡的效果从卡本身中分离出来,例如:
class CardEffect {
// TODO ...
};
class Card {
private:
const CardEffect* effect;
};
所以现在卡片本身不需要知道任何关于游戏的信息。但让我们更深入地了解一下:CardEffect
不需要知道 Game
class 的每个细节,它需要能够做的是将效果应用到游戏中。这可以通过为效果提供一个单独的界面来完成,该界面只公开应用效果所需的内容,所以像这样。
class GameInterface {
public:
virtual const std::vector<Player*>& getPlayers() = 0;
virtual damagePlayer(Player* player, int amount) = 0;
virtual applyPeriodicDamage(Player* player, int turns, int amount) = 0
..
};
class CardEffect {
virtual applyEffect(GameInterface* interface) = 0;
};
class Card {
private:
const CardEffect* effect;
};
现在这只是一个例子,对于你的问题没有确定的解决方案,因为每个特定场景都不同并且有不同的要求,它只是给你一个基本的想法,让你知道如何在保持代码优雅和封装的同时使其具有足够的描述性以便于管理。
每当你有像这样的交叉依赖时:
class Card
{
Game* game;
void f() {
game->method1();
}
};
class Game
{
std::vector<Card> cards;
public:
void method1();
};
其中一个依赖项应实现如下接口:
class IGame
{
public:
virtual void method1()=0;
};
class Card
{
IGame* game;
void f() {
game->method1();
}
};
class Game : public IGame
{
std::vector<Card> cards;
public:
virtual void method1();
};
正如您所看到的,不再有交叉依赖。