您通常不会像那样公开数据成员,而是提供对封装它们的对象的一些访问,并使用允许读取(并且可能写入)信息的函数。
您会在面向对象编程的上下文中遇到“getter”和“setter”,但这些访问器不需要“瘦”:它们应该反映类的逻辑属性,这些属性可能会或可能不会直接映射到个人数据成员。
所以:
class Fruit
{
public:
Flavour GetFlavour() const;
void SetFlavour(const Flavour newFlavour);
private:
Flavour m_flavour;
};
int main()
{
Fruit fruit;
fruit.GetFlavour();
}
当然,现在我们仍然有同样的问题:如果我添加另一个类,它如何“看到”果实以使用其闪亮的新访问器函数?
class Fruit
{
public:
Flavour GetFlavour() const;
void SetFlavour(const Flavour newFlavour);
private:
Flavour m_flavour;
};
class Juicer
{
public:
void UseFoodItem(const Fruit&);
};
int main()
{
Fruit fruit;
fruit.GetFlavour();
Juicer juicer;
juicer.useFoodItem(fruit);
}
看看我是如何在需要时将水果的引用传递到榨汁机的?这很干净。这并不混乱,因为您不必担心谁拥有什么以及拥有多长时间。但你只能使用里面的水果UseFoodItem
。如果您需要一个更持久的参考,以便其他功能也可以使用它怎么办?
事实证明,参考仍然是一个很好的方法。让我们让示例更接近您的用例:
// Let's imagine you've defined this somewhere, and it contains
// both X and Y coordinates on a shared grid
class Location;
class Fruit
{
public:
Location GetLocation() const;
void MoveTo(const Location newLocation);
private:
Location m_location;
};
class Snake
{
public:
Snake(const Fruit& fruit) : m_fruit(fruit) {}
void DoAThing();
private:
const Fruit& m_fruit;
};
int main()
{
Fruit fruit;
fruit.MoveTo(Location{20, 15});
Snake snake(fruit);
snake.DoAThing();
}
(您是否注意到位置设置器不仅仅是“SetLocation”?逻辑上,对水果执行的操作是移动它。通过更改其“位置”来完成这一事实是一个实现细节,所以我们隐藏它远离类的接口。)
在这里,DoAThing
你的蛇在移动,或者你的游戏中发生了什么。关键是您可以fruit
从它内部访问,也可以从您添加的任何其他功能访问。只要fruit
inmain
仍在范围内,这是安全的。
使用智能指针可以使其更安全。Ashared_ptr<Fruit>
可以在许多其他对象之间共享,而不会造成太多混乱:您会知道水果是正确存在的,直到您的游戏中不再需要它为止。
说了这么多,我不会让蛇知道水果的任何事情。我会把这个逻辑放在其他地方,在更高的层次上。我会让你的蛇由某个函数/类控制,你的水果由该类拥有,并且在每一步它都应该检查蛇并检查水果,然后决定如果检测到碰撞该怎么办。现在这变得有点复杂了,但它可能看起来大致是这样的:
// Let's imagine you've defined this somewhere, and it contains
// both X and Y coordinates on a shared grid
class Location;
// Let's imagine you've defined this somewhere, and it represents
// an action a user can take. For example, "move the snake up" (which
// would likely be triggered by an "up arrow" keypress).
// Let's imagine you've defined this somewhere, and it blocks for
// user input then transforms that into an Action
Action AcceptUserInput();
class Fruit
{
public:
Location GetLocation() const;
void MoveTo(const Location newLocation);
private:
Location m_location;
};
class Snake
{
public:
Location GetLocation() const;
void MoveTo(const Location newLocation);
private:
Location m_location;
};
class Game
{
public:
void SetSnakeInitialLocation(const Location snakeLocation)
{
m_snake.MoveTo(snakeLocation);
}
void AddFruit(const Location fruitLocation)
{
Fruit newFruit;
newFruit.MoveTo(fruitLocation);
m_fruits.push_back(newFruit);
}
void PerformAction(const Action action)
{
// If the action is to move the snake, do that by
// calling things on m_snake, e.g. m_snake.MoveTo(newLocation)
// Otherwise, do whatever needed.
// Now, let's examine the state of the board. We have
// all the information needed to do that, and don't have
// to obtain it from anywhere else.
for (Fruit& fruit : m_fruits)
{
if (m_snake.Intersects(fruit))
{
// Grow the snake; remove the fruit
//
// (N.B. you can't use ranged-for like this if you're
// going to erase from m_fruits, but that detail is out
// of scope of this answer)
}
}
}
private:
std::vector<Fruit> m_fruits;
Snake m_snake;
};
int main()
{
Game game;
game.AddFruit(Location{20, 15});
game.SetSnakeInitialLocation(Location{0, 0});
while (true)
{
Action nextAction = AcceptUserInput();
game.PerformAction(nextAction);
}
}
现在基本上没有共享状态:只有独立的对象和拥有大量对象的单个实体。您不希望“上帝对象”在您的程序中绝对拥有并拥有一切,但您的游戏板确实拥有蛇和所有水果,因此将它们作为其成员存储在这里是有意义的。
这不是最好的代码。它可能有一些拼写错误,对于一个游戏,您可能想要研究诸如访问者模式之类的东西,这样PerformAction
最终就不会成为if
需要了解各种实体含义的大量 s 集合。
此外,我很想将位置保存在 中Game
,而不是它们是每个单独对象的属性。所以Fruit
会有像它的味道这样的属性(如果你有不同种类的水果),但它的位置将完全由Game
. 您必须考虑如何表示风蛇的所有组成点。
但这些细节与我在这里试图展示的内容无关。