问题
I've developed a recent interest in (re-)learning programming, so I have taken up C++ as it is a commonly used language. However, I've ran into a roadblock, and I have doubts whether my solution is the best way to go around it. I have a relatively complex class (for me anyways), with around 20 variables, which have been divided into 4 groups for simplification. It also has a parent class which is called during object initialization.
However, I do not have a need to set these to values other than their default values in all objects, so I have set-up various different constructor overloads to account for all possible combinations (8 constructors in total). Therefore, in order to prevent writing repetitive code, I have written an handful of private functions, only called during the constructor, that set variables to the value I assign when creating a new object.
Is this the optimal way of solving this problem? I have also thought of grouping these variables into classes or structs, but it just feels like that's needlessly complicated, when calling the relevant functions during the various constructor overloads should do the trick. If this is not optimal, what would be the best way of solving this? And why?
I can provide with a more detailed description of my problem, but it'd be a pretty big wall of text (I had first written that one up, but it got way too out of hand). Thank you in advance for your input.
As requested, here is the class definition (Weapon). The parent class (Item) is already defined and working as intended, so I will not paste it, so people will not have to read a massive wall of text.
Weapon class definition:
class Weapon: public Item {
public:
// Default constructor
Weapon();
// Full constructor
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short StartEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Cooldown System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Reload System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constuctor for Weapons without Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Cooldown nor Reload System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Cooldown nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Reload nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Cooldown, Reload nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short maxMagazine, unsigned short MaxAmmunition);
~Weapon();
void m_print();
/*Edited public get and set functions for each variable as they are not relevant*/
private:
// Ubiquitous variables
unsigned short WepGenericID = 0;
unsigned short WepVariantID = 0;
unsigned short WepSkinID = 0;
double EquipLoad = 0;
double EquipLoadperAmmo = 0;
unsigned short ModesNo = 1;
Mode* pModes = NULL;
unsigned short MaxAmmunition = 0;
unsigned short CurrentAmmunition = 0;
unsigned short MaxMagazine = 0;
unsigned short CurrentMagazine = 0;
// Cooldown System variables
bool WeaponCooldown = false;
unsigned short CooldownType = 0;
double CooldownDuration = 0;
unsigned short CooldownAction = 0;
double CooldownPeriod = 0;
// Reload System variables
unsigned short ReloadType = 0;
unsigned short ReloadStyle = 0;
double ReloadTime = 0;
// Energy System variables
unsigned short CurrentEnergy = 0;
unsigned short MaxEnergy = 0;
//Constructor Auxiliary Functions
void m_setGeneralWeapon(double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short MaxMagazine, unsigned short MaxAmmunition);
void m_setCooldownSystem(unsigned short CooldownType, double CooldownDuration, unsigned short CooldownAction, double CooldownPeriod);
void m_setReloadSystem(unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime);
void m_setEnergySystem(unsigned short StartEnergy, unsigned short MaxEnergy);
void m_setWeaponIDs();
void m_WepNameDecisionTree();
string m_searchName();
};
Item parent class definition
class Item {
public:
Item();
Item(unsigned GenericID);
Item(unsigned GenericID, bool NameFlag);
~Item();
void m_setCustomName();
private:
unsigned GenericID = 0;
unsigned short GenCategoryID = 0;
unsigned short GenSubCategoryID = 0;
bool NameFlag = false;
string ItemName = "Missingno";
unsigned long InstanceID = 0;
};
回答1:
make seperate classes for your subsystems.
create your weapons using a builder/factory pattern:
- How to implement the factory method pattern in C++ correctly
- https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns/Creational_Patterns
you could also seperate the ammo, leaving you with only few actual members. This way you can build everything with a more modular approach, giving you the possibility to easier extend or modify your functionality
回答2:
One problem I see with the Weapon
API as posted is that the constructors take a lot of loosely-typed arguments, which makes the code that uses them hard to understand and verify. For example, say you (or your fellow developer) have used your constructor API to add this line to your codebase:
Weapon bfg(id, true, 3.0, 5.0, 6, modesPtr, COOLDOWN_QUICK, 5.0, 3, 4, RELOAD_SLOW, RELOAD_ANYTIME, 3.8, 12, 14);
Reading that line, it's very hard to see what most of the numbers mean. If an argument was accidentally omitted, or the ordering of two of the arguments was reversed, it will likely not be obvious (either you or to the compiler) that there is a bug there; instead, you might not find out about the error until some play tester (or customer?) files a bug report, which is an expensive and time-consuming way to discover errors.
Therefore, I recommend reducing the number of arguments you have to pass to the constructor to as few as you can get away with. For example, another way to write the above might be this:
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
Admittedly that's a lot more verbose (and it does make it possible to forget to set something you should have set), but at least when you look at it, it's very obvious that 3.0
applies to the EquipLoad
setting and 5.0
applies to the EquipLoadPerAmmo
setting, and not the other way around. i.e. you don't have to constantly look back and forth between your .h file and the code to try and figure out what each value is referring to.
Note that each set-method should take all of the parameters needed to yield a useful result; so for example if it doesn't make any sense to specify an EquipLoad
without also specifying EquipLoadPerAmmo
, then you might as well have both of those set by a single call instead:
bfg.SetEquipLoadAndEquipLoadPerAmmo(3.0, 5.0);
... so that it becomes impossible (i.e. a compile-time error) for a coder to make the mistake of setting one but neglecting to set the other.
As for handling the verboseness to minimize redundant code, the next step would be to wrap the above code inside a function, so that for any given weapon-type there is only one place that creates it, e.g. :
Weapon MakeBFG(unsigned id)
{
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
return bfg;
}
Now the rest of your code can just call Weapon bfg = MakeBFG(idCounter++);
whenever it wants to create a new BFG gun.
Outside of that, I agree with the other poster -- your class seems to be handling a number of different things, and if you can find a way to decompose it into multiple smaller classes (not all of which need to be exposed via the public API; private classes are great), that would probably help you manage the overall complexity of the code; doing so is especially beneficial if you think you will want to continue to add new features/behaviors in the future, since if you keep everything together in a single class, the complexity of that class will quickly get out of hand as you try to make it support more and more different behaviors/use-cases.
来源:https://stackoverflow.com/questions/50555223/c-dealing-with-multiple-constructor-overloads-and-redundant-code