Reputation: 3
I've been reading about keeping functions simple and specific to only one purpose. And that even a function that does a simple calculation and print out the result is already too much.
I've been working on an item shop system for a small game. The shop has a vector of all items available in the game. Each Item keeps track of its own count (is that bad?), and do not show up in the inventory output if its count is zero.
When a player (rider) wants to buy an item, the shop should check if the player has enough credit and if such item is in stock before the item gets removed from the shop (and then added to the player's inventory).
I have combined all of that into one function, thinking that this avoid repetition of traversing through the vector of Items.
/* ItemShop.cpp
* non-binary boolean:
* returns 0 if item is not in stock
* returns 1 if rider has insufficient creds to buy the item
* returns 2 if purchase is successful */
int ItemShop::buyItem(const string& itemName, const int cred, Item& newItem) {
// go through shop inventory and search for item with given name
for (int i = 0; i < items.size(); i++) {
if (items[i].getName() == itemName) {
if (items[i].getCount() > 0) { // item is in stock
if (items[i].getValue() <= cred) { // rider has enough creds to buy the item
newItem = items[i]; // pass a copy of the item by ref
newItem.setCount(1); // take one of that item
items[i].removeOne(); // remove one from the shop
return 2; // purchase is successful
}
return 1; // rider cannot afford to buy the item
}
}
}
return 0; // item is not in stock
}
In doing this approach, I end up with a larger multi-purpose function but I didn't have to go through the Item vector multiple times. I think if I were to break the function into separate ones, they would be:
Each of those functions would have to go through and find the item in the vector (unless maybe I pass a reference of it.. from the function?).
All in all, does less code repetition make my approach justifiable? If not, how should I break it down?
Upvotes: 0
Views: 146
Reputation: 490048
I do think an item keeping its own count is a poor idea. An "inventory" (or something on that order) keeps track of the items in stock and the number of each. An item should be just that: an item.
I think the suggestion to use a [unordered_]map is a good one. It pre-implements what is probably the single most complex part of your current function (though none of it is particularly complex).
Do note that some of this can get much trickier when/if multiple threads get involved. If multiple threads of execution are involved, your current pattern of "if we can do this, then do it" breaks down, because it introduces race conditions. You need to assure that removing the item from inventory and paying for the item happen as a single, atomic, transaction. As long as you're sure it'll only have a single thread of execution involved though, your current method is safe.
Upvotes: 0
Reputation: 21126
Your function doesn't do too many things - you are purchasing an item, which includes those three necessary steps (btw.: aren't you missing the payment?).
However (aside from other things that could be improved), you should make sure that the steps don't get intermingled. In particular avoid nested structures whenever possible.
You could e.g.rewrite your body (without changing the interface) like this:
int ItemShop::buyItem(const string& itemName, const int cred, Item& newItem) {
//find item
auto it = std::find_if(items.begin(), items.end(), [&](const Item& item) {return item.getName() == itemName; });
//Check if Item can be purchased
if (it == items.end() || it->getCount == 0) {
return 0; //item is not in stock
}
if (it->getValue() > cred) {
return 1; //rider can't afford item
}
//buy item
newItem = *it; // pass a copy of the item by ref <- Avoid that if possible
newItem.setCount(1); // take one of that item
it->removeOne(); // remove one from the shop
return 2; // purchase is successful
}
Upvotes: 0
Reputation: 1
You can break that up into the following operations:
item
itself already carries that information as it looks, it doesn't need to be executed inside the loopcred
value apparently, it doesn't need to be executed inside the loopUpvotes: 1
Reputation: 8215
Two recommendations:
std::map<string,Item>
where the string is
the item name. This will remove the search loop.You could also implement simple functions for the different checks, but that's a matter of taste I would say.
Upvotes: 2