Inne osoby już zasugerowały mój początkowy pomysł, metodę matrycową, ale oprócz konsolidacji instrukcji if można uniknąć części tego, co masz, upewniając się, że dostarczone argumenty znajdują się w oczekiwanym zakresie i używając zwrotów w miejscu (niektóre kodowanie standardy, które widziałem, egzekwują jeden punkt wyjścia dla funkcji, ale zauważyłem, że wielokrotne zwroty są bardzo przydatne do unikania kodowania strzałek, a przy powszechności wyjątków w Javie i tak nie ma sensu ścisłe egzekwowanie takiej reguły ponieważ każdy niewyłapany wyjątek zgłoszony w metodzie jest możliwym punktem wyjścia). Zagnieżdżanie instrukcji przełączania jest możliwe, ale dla małego zakresu wartości, które tu sprawdzasz, stwierdzam, czy instrukcje są bardziej zwarte i raczej nie spowodują dużej różnicy wydajności,
public int fightMath(int one, int two) {
if (one > 3 || one < 0 || two > 3 || two < 0) {
throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
}
if (one <= 1) {
if (two <= 1) return 0;
if (two - one == 2) return 1;
return 2; // two can only be 3 here, no need for an explicit conditional
}
// one >= 2
if (two >= 2) return 3;
if (two == 1) return 1;
return 2; // two can only be 0 here
}
W efekcie jest to mniej czytelne, niż mogłoby to wynikać z nieregularności części mapowania danych wejściowych i wyników. Zamiast tego preferuję styl matrycy ze względu na jego prostotę i sposób, w jaki można ustawić matrycę, aby miała sens wizualny (choć częściowo na to wpływ mają moje wspomnienia map Karnaugh):
int[][] results = {{0, 0, 1, 2},
{0, 0, 2, 1},
{2, 1, 3, 3},
{2, 1, 3, 3}};
Aktualizacja: Biorąc pod uwagę Twoją wzmiankę o blokowaniu / trafianiu, oto bardziej radykalna zmiana funkcji, która korzysta z wyliczonych typów własności / posiadania atrybutów dla danych wejściowych i wyniku, a także nieco modyfikuje wynik w celu uwzględnienia blokowania, co powinno skutkować większym funkcja czytelna.
enum MoveType {
ATTACK,
BLOCK;
}
enum MoveHeight {
HIGH,
LOW;
}
enum Move {
// Enum members can have properties/attributes/data members of their own
ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);
public final MoveType type;
public final MoveHeight height;
private Move(MoveType type, MoveHeight height) {
this.type = type;
this.height = height;
}
/** Makes the attack checks later on simpler. */
public boolean isAttack() {
return this.type == MoveType.ATTACK;
}
}
enum LandedHit {
NEITHER,
PLAYER_ONE,
PLAYER_TWO,
BOTH;
}
LandedHit fightMath(Move one, Move two) {
// One is an attack, the other is a block
if (one.type != two.type) {
// attack at some height gets blocked by block at same height
if (one.height == two.height) return LandedHit.NEITHER;
// Either player 1 attacked or player 2 attacked; whoever did
// lands a hit
if (one.isAttack()) return LandedHit.PLAYER_ONE;
return LandedHit.PLAYER_TWO;
}
// both attack
if (one.isAttack()) return LandedHit.BOTH;
// both block
return LandedHit.NEITHER;
}
Nie musisz nawet zmieniać samej funkcji, jeśli chcesz dodawać bloki / ataki o większej wysokości, tylko wyliczenia; dodanie dodatkowych rodzajów ruchów prawdopodobnie będzie wymagać modyfikacji funkcji. Ponadto, EnumSet
s może być bardziej rozszerzalne niż użycie dodatkowych wyliczeń jako właściwości głównego wyliczenia, np. EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);
A następnie attacks.contains(move)
zamiast move.type == MoveType.ATTACK
, chociaż użycie EnumSet
s będzie prawdopodobnie nieco wolniejsze niż bezpośrednie sprawdzanie równości.
Dla przypadku, gdy udany blok skutkuje w liczniku, można zastąpić if (one.height == two.height) return LandedHit.NEITHER;
z
if (one.height == two.height) {
// Successful block results in a counter against the attacker
if (one.isAttack()) return LandedHit.PLAYER_TWO;
return LandedHit.PLAYER_ONE;
}
Również zastąpienie niektórych if
instrukcji użyciem operatora trójskładnikowego ( boolean_expression ? result_if_true : result_if_false
) może uczynić kod bardziej zwartym (na przykład kod w poprzednim bloku stałby się return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;
), ale może to prowadzić do trudniejszych do odczytania onelinerów, więc nie Polecam go do bardziej skomplikowanych rozgałęzień.