patternjavaMajor
DRY attack and defense
Viewed 0 times
anddefenseattackdry
Problem
I have these two methods on a class that differ only in one method call. Obviously, this is very un-DRY, especially as both use the same formula. Could anyone give me advice on how to tidy this up?
I thought of refactoring the loop to an external method but the different method calls has me stumped.
public class PlayerCharacter {
int level;
public int getAttack() {
int attack = 1 + level;
for(Equipment e : equipments)
attack += e.getAttack();
return attack * Math.sqrt(level);
}
public int getDefense() {
int attack = 1 + level;
for(Equipment e : equipments)
attack += e.getDefense();
return attack * Math.sqrt(level);
}
}I thought of refactoring the loop to an external method but the different method calls has me stumped.
Solution
A few things, bad messages first…
Double and Int
Math.sqrt returns a double. That is a floating point number, probably something like: \$2.352561\$. If you now "cast" that to an int (because your method says, "I only return integers"), you just cut everything behind the dot.
This can sometimes lead to funny behavior:
\$2.7371 \mapsto 2\$
\$2.0125 \mapsto 2\$
That's not nice…
Braces:
It's usually a very good idea to place braces, even when you could leave them out:
This is because if you want to do something else, too you need to add braces either way, and If you forget it, the results become "unpredictable"
Copy/Paste:
You should never do that, because as soon as you do, you usually can extract a method for it. But if you do it either way then at least take the bother to change the names…
'nuff said on the bad stuff ;)
Attributes
You might want to consider changing your attribute structure to be more "generic". Consider the following code:
or similarly for attack:
now we already can see, that if you do that twice, this wants to be a single method:
This would of course need a relatively large change in your code. You'd then need to change your Equipment from (wild conjecture):
to:
And create a new enum to hold all the different kinds of attributes you have:
Double and Int
return Math.sqrt(level);Math.sqrt returns a double. That is a floating point number, probably something like: \$2.352561\$. If you now "cast" that to an int (because your method says, "I only return integers"), you just cut everything behind the dot.
This can sometimes lead to funny behavior:
\$2.7371 \mapsto 2\$
\$2.0125 \mapsto 2\$
That's not nice…
Braces:
It's usually a very good idea to place braces, even when you could leave them out:
for (Equipment e : equipments) {
attack += e.getAttack();
}This is because if you want to do something else, too you need to add braces either way, and If you forget it, the results become "unpredictable"
Copy/Paste:
You should never do that, because as soon as you do, you usually can extract a method for it. But if you do it either way then at least take the bother to change the names…
public int getDefense() {
int attack = 1 + level;'nuff said on the bad stuff ;)
Attributes
You might want to consider changing your attribute structure to be more "generic". Consider the following code:
for (Equipment e : equipments) {
defense += e.getAttribute(Attribute.DEFENSE);
}or similarly for attack:
for (Equipment e : equipments) {
attack += e.getAttribute(Attribute.ATTACK);
}now we already can see, that if you do that twice, this wants to be a single method:
public int getPlayerAttribute(Attribute attribute) {
int attribute = 1 + level;
for (Equipment e : equipments) {
attribute += e.getAttribute(attribute);
}
return attribute * Math.sqrt(level);
}This would of course need a relatively large change in your code. You'd then need to change your Equipment from (wild conjecture):
public class Equipment {
private int attack;
private int defense;
//getters and setters
}to:
public class Equipment {
private Map stats = new HashMap<>();
// or a different Map implementation of your choice, like
// EnumMap, as suggested by @ratchet freak in a comment:
private Map stats = new EnumMap<>(Attribute.class);
public int getAttribute(Attribute attribute) {
return stats.get(attribute);
}
}And create a new enum to hold all the different kinds of attributes you have:
public enum Attribute {
ATTACK, DEFENSE, HITPOINTS, WHATEVER
}Code Snippets
return Math.sqrt(level);for (Equipment e : equipments) {
attack += e.getAttack();
}public int getDefense() {
int attack = 1 + level;for (Equipment e : equipments) {
defense += e.getAttribute(Attribute.DEFENSE);
}for (Equipment e : equipments) {
attack += e.getAttribute(Attribute.ATTACK);
}Context
StackExchange Code Review Q#57177, answer score: 26
Revisions (0)
No revisions yet.