У меня есть эти два метода на классе, которые отличаются только по одному вызову метода. Очевидно, это - очень неDRY, тем более, что оба используют ту же формулу.
int PlayerCharacter::getAttack() {
int attack;
attack = 1 + this->level;
for(int i = 0; i < this->current_equipment; i++) {
attack += this->equipment[i].getAttack();
}
attack *= sqrt(this->level);
return attack;
}
int PlayerCharacter::getDefense() {
int defense;
defense = 1 + this->level;
for(int i = 0; i < this->current_equipment; i++) {
defense += this->equipment[i].getDefense();
}
defense *= sqrt(this->level);
return defense;
}
Как я могу убрать это в C++?
На мой взгляд, то, что у вас есть, вполне подходит, так как это позволит вам настраивать атаку/защиту больше, чем если бы вы представляли их обе одной функцией. Как только вы начнете тестировать свою игру, вы начнете балансировать формулы атаки/защиты, поэтому наличие отдельных функций для них - это нормально.
Вся концепция DRY [не повторяйтесь] заключается в том, чтобы предотвратить превращение вашего кода в огромный copy & paste fest. В вашей ситуации формулы защиты/атаки будут меняться со временем [например, что если у персонажей есть баффы/статусные повреждения? Определенный статусный недуг может снизить защиту вдвое, увеличив при этом атаку на 2 (Берсерк, отсылка к FF, хех)]
.Помимо ответа ltzWarty , я бы порекомендовал преобразовать ваш цикл в функцию для лучшей читаемости:
int PlayerCharacter::getEquipmentAttack() {
int attack = 0;
for(int i = 0; i <= current_equipment; i++) {
attack += this.equipment[i].getAttack();
}
return attack;
}
int PlayerCharacter::getAttack() {
int attack = 1 + this->level;
attack += getEquipmentAttack();
attack *= sqrt(this->level);
return attack;
}
Кроме того, когда вы объявляете атаку локальной переменной
, вы должны ] инициализировать его немедленно .
Один из простых способов - представить все атрибуты части оборудования в массиве, индексированном перечислением.
enum Attributes {
Attack,
Defense,
AttributeMAX
};
class Equipment {
std::vector<int> attributes;
Equipment(int attack, int defense): attributes(AttributeMAX)
{
attributes[ATTACK] = attack;
attributes[DEFENSE] = defense;
}
};
Затем вы измените свою функцию на
int PlayerCharacter::getAttribute(int& value, Attribute attribute) {
value = 1 + this->level;
for(int i = 0; i <= current_equipment; i++) {
value += this->equipment[i].attributes[attribute];
}
value *= sqrt(this->level);
return value;
}
И вы можете назвать ее так
player.getAttribute(player.attack, Attack);
В зависимости от другого кода в приложении это может стоить того, а может и не стоить, но подход ООП будет делать объекты значений защиты и атак класса, а не простого int
. Затем вы можете получить их из общего базового класса, у которого есть метод get (), который вызывает виртуальный метод getEquipmentRate (), определенный каждым из подклассов по мере необходимости.
ну, я бы хотя бы подумал об извлечении sqrt (this.level);
как отдельная функция с именем getLevelModifier ()
и
defense = 1 + this.level;
attack = 1 + this.level;
может быть
defense = getBaseDefense();
attack= getBaseAttack();
Это не только добавляет гибкости, но и автоматически документирует вашу функцию.
С точки зрения строгого рефакторинга вы могли бы сделать это:
int PlayerCharacter::getDefense() {
return getAttribute(&EquipmentClass::getDefense);
}
int PlayerCharacter::getOffense() {
return getAttribute(&EquipmentClass::getOffense);
}
int PlayerCharacter::getAttribute(int (EquipmentClass::*attributeFun)()) {
int attribute = 0;
attribute= 1 + this->level;
for(int i = 0; i <= current_equipment; i++) {
attribute += this->equipment[i].*attributeFun();
}
attribute *= sqrt(this->level);
return attribute;
}
IMO, ItzWarty делает разумное замечание - вы можете просто оставить код в покое. Предполагая, что вы решите, что изменение - это хорошо, вы можете сделать что-то вроде этого:
class equipment {
public:
int getAttack();
int getDefense();
};
int PlayerCharacter::getBattleFactor(int (equipment::*get)()) {
int factor = level + 1;
for (int i=0; i<current_equipment; ++i)
factor += equipment[i].*get();
return factor * sqrt(level + 1);
}
Вы бы назвали это так:
int attack = my_player.getBattleFactor(&equipment::getAttack);
или:
int defense = my_player.GetBattleFactor(&equipment::getDefense);
Изменить:
Другой очевидной возможностью было бы постановление, что любая часть экипировки может только быть оборонительной или наступательной. В этом случае все становится еще проще, до такой степени, что даже может возникнуть сомнение, действительно ли вам нужна функция:
class PlayerCharacter {
std::vector<equipment> d_equip;
std::vector<equipment> o_equip;
// ...
int d=level+1+std::accumulate(d_equip.begin(), d_equip.end(), 0)*sqrt(level+1);
int o=level+1+std::accumulate(o_equip.begin(), o_equip.end(), 0)*sqrt(level+1);