C++,处理多个构造函数重载和冗余代码
C++, dealing with multiple constructor overloads and redundant code
我最近对(重新)学习编程产生了兴趣,所以我开始学习 C++,因为它是一种常用的语言。但是,我 运行 遇到了障碍,我怀疑我的解决方案是否是绕过它的最佳方法。
我有一个相对复杂的 class (无论如何对我来说),大约有 20 个变量,为了简化,它们被分为 4 组。它还有一个在对象初始化期间调用的父 class。
但是,我不需要在所有对象中将它们设置为默认值以外的值,因此我设置了各种不同的构造函数重载以考虑所有可能的组合(总共 8 个构造函数)。因此,为了防止编写重复代码,我编写了一些私有函数,仅在构造函数期间调用,将变量设置为我在创建新对象时分配的值。
这是解决这个问题的最佳方式吗?我还考虑过将这些变量分组到 classes 或结构中,但感觉这太复杂了,在各种构造函数重载期间调用相关函数应该可以解决问题。如果这不是最优的,那么解决这个问题的最佳方法是什么?为什么?
我可以对我的问题提供更详细的描述,但这将是一堵相当大的文字墙(我首先写了那个,但它太失控了)。预先感谢您的意见。
根据要求,这里是 class 定义(武器)。父项 class(项目)已经定义并按预期工作,所以我不会粘贴它,这样人们就不必阅读大量的文字。
武器class定义:
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();
};
父项class定义
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;
};
为您的子系统单独 类。
使用 builder/factory 模式创建你的武器:
- How to implement the factory method pattern in C++ correctly
- https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns/Creational_Patterns
你也可以把弹药分开,只剩下很少的实际成员。通过这种方式,您可以使用更加模块化的方法构建所有内容,让您可以更轻松地扩展或修改您的功能
我在发布的 Weapon
API 中看到的一个问题是构造函数采用 lot 松散类型的参数,这使得代码使用它们很难理解和验证。例如,假设您(或您的开发伙伴)使用您的构造函数 API 将此行添加到您的代码库:
Weapon bfg(id, true, 3.0, 5.0, 6, modesPtr, COOLDOWN_QUICK, 5.0, 3, 4, RELOAD_SLOW, RELOAD_ANYTIME, 3.8, 12, 14);
阅读该行,很难看出大多数数字的含义。如果不小心遗漏了一个参数,或者两个参数的顺序颠倒了,那么那里存在错误可能并不明显(无论是对您还是对编译器);相反,您可能不会发现错误,直到某些游戏测试员(或客户?)提交错误报告,这是发现错误的一种昂贵且耗时的方法。
因此,我建议将必须传递给构造函数的参数数量减少到尽可能少。例如,上面的另一种写法可能是这样的:
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
诚然,这要冗长得多(而且确实有可能忘记设置您应该设置的内容),但至少当您查看它时,很明显 3.0
适用于EquipLoad
设置和 5.0
适用于 EquipLoadPerAmmo
设置,而不是相反。也就是说,您不必不断地在 .h 文件和代码之间来回查看以尝试找出每个值所指的内容。
请注意,每个设置方法都应采用产生有用结果所需的所有参数;因此,例如,如果在不指定 EquipLoadPerAmmo
的情况下指定 EquipLoad
没有任何意义,那么您不妨通过一次调用来设置这两个设置:
bfg.SetEquipLoadAndEquipLoadPerAmmo(3.0, 5.0);
... 这样就不可能(即编译时错误)编码人员犯下设置一个而忽略设置另一个的错误。
至于处理冗长以尽量减少冗余代码,下一步是将上述代码包装在一个函数中,这样对于任何给定的武器类型,只有一个地方可以创建它,例如:
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;
}
现在您的其余代码可以在需要创建新 BFG 枪时调用 Weapon bfg = MakeBFG(idCounter++);
。
除此之外,我同意另一位发帖者的观点——您的 class 似乎正在处理许多不同的事情,如果您能找到一种方法将其分解为多个更小的 classes(并非所有这些都需要通过 public API 公开;私有 classes 很好),这可能会帮助您管理代码的整体复杂性;如果您认为将来要继续添加新的 features/behaviors,那么这样做特别有益,因为如果您将所有内容放在一个 class 中,那么 class 的复杂性将会当您尝试使其支持越来越多的不同 behaviors/use-cases.
时,很快就会失控
我最近对(重新)学习编程产生了兴趣,所以我开始学习 C++,因为它是一种常用的语言。但是,我 运行 遇到了障碍,我怀疑我的解决方案是否是绕过它的最佳方法。 我有一个相对复杂的 class (无论如何对我来说),大约有 20 个变量,为了简化,它们被分为 4 组。它还有一个在对象初始化期间调用的父 class。
但是,我不需要在所有对象中将它们设置为默认值以外的值,因此我设置了各种不同的构造函数重载以考虑所有可能的组合(总共 8 个构造函数)。因此,为了防止编写重复代码,我编写了一些私有函数,仅在构造函数期间调用,将变量设置为我在创建新对象时分配的值。
这是解决这个问题的最佳方式吗?我还考虑过将这些变量分组到 classes 或结构中,但感觉这太复杂了,在各种构造函数重载期间调用相关函数应该可以解决问题。如果这不是最优的,那么解决这个问题的最佳方法是什么?为什么?
我可以对我的问题提供更详细的描述,但这将是一堵相当大的文字墙(我首先写了那个,但它太失控了)。预先感谢您的意见。
根据要求,这里是 class 定义(武器)。父项 class(项目)已经定义并按预期工作,所以我不会粘贴它,这样人们就不必阅读大量的文字。
武器class定义:
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();
};
父项class定义
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;
};
为您的子系统单独 类。
使用 builder/factory 模式创建你的武器:
- How to implement the factory method pattern in C++ correctly
- https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns/Creational_Patterns
你也可以把弹药分开,只剩下很少的实际成员。通过这种方式,您可以使用更加模块化的方法构建所有内容,让您可以更轻松地扩展或修改您的功能
我在发布的 Weapon
API 中看到的一个问题是构造函数采用 lot 松散类型的参数,这使得代码使用它们很难理解和验证。例如,假设您(或您的开发伙伴)使用您的构造函数 API 将此行添加到您的代码库:
Weapon bfg(id, true, 3.0, 5.0, 6, modesPtr, COOLDOWN_QUICK, 5.0, 3, 4, RELOAD_SLOW, RELOAD_ANYTIME, 3.8, 12, 14);
阅读该行,很难看出大多数数字的含义。如果不小心遗漏了一个参数,或者两个参数的顺序颠倒了,那么那里存在错误可能并不明显(无论是对您还是对编译器);相反,您可能不会发现错误,直到某些游戏测试员(或客户?)提交错误报告,这是发现错误的一种昂贵且耗时的方法。
因此,我建议将必须传递给构造函数的参数数量减少到尽可能少。例如,上面的另一种写法可能是这样的:
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
诚然,这要冗长得多(而且确实有可能忘记设置您应该设置的内容),但至少当您查看它时,很明显 3.0
适用于EquipLoad
设置和 5.0
适用于 EquipLoadPerAmmo
设置,而不是相反。也就是说,您不必不断地在 .h 文件和代码之间来回查看以尝试找出每个值所指的内容。
请注意,每个设置方法都应采用产生有用结果所需的所有参数;因此,例如,如果在不指定 EquipLoadPerAmmo
的情况下指定 EquipLoad
没有任何意义,那么您不妨通过一次调用来设置这两个设置:
bfg.SetEquipLoadAndEquipLoadPerAmmo(3.0, 5.0);
... 这样就不可能(即编译时错误)编码人员犯下设置一个而忽略设置另一个的错误。
至于处理冗长以尽量减少冗余代码,下一步是将上述代码包装在一个函数中,这样对于任何给定的武器类型,只有一个地方可以创建它,例如:
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;
}
现在您的其余代码可以在需要创建新 BFG 枪时调用 Weapon bfg = MakeBFG(idCounter++);
。
除此之外,我同意另一位发帖者的观点——您的 class 似乎正在处理许多不同的事情,如果您能找到一种方法将其分解为多个更小的 classes(并非所有这些都需要通过 public API 公开;私有 classes 很好),这可能会帮助您管理代码的整体复杂性;如果您认为将来要继续添加新的 features/behaviors,那么这样做特别有益,因为如果您将所有内容放在一个 class 中,那么 class 的复杂性将会当您尝试使其支持越来越多的不同 behaviors/use-cases.
时,很快就会失控