#java #predicate #naming
Вопрос:
Я создаю карточную игру, и, возможно, я немного переутомляюсь. До того, как у меня появились методы pileHasMostSpades()
и pileHasMostSevens()
. Эти методы имели один и тот же код, за исключением условия if и целочисленного значения. Я смог объединить эти методы в один ( pileHasAtLeast()
), добавив предикат и целочисленный параметр.
pileHasAtLeast(count, predicate)
— проверяет, есть ли по крайней мере [количество] карт, соответствующих предикату.
Кроме того, каковы были бы альтернативные или, возможно, лучшие названия для этого нового стиля метода ( pileHasAtLeast()
)? Спасибо.
public int computeScore()
{
int score = scopas;
final Predicate<Card> spades = c -> c.getSuit() == Card.Suit.SPADES;
final Predicate<Card> sevens = c -> c.getValue() == Card.Value.SEVEN;
score = pile.size() > 20 ? 1 : 0;
score = pileHasAtLeast(6, spades) ? 1 : 0;
score = pileHasAtLeast(3, sevens) ? 1 : 0;
score = hasSevenOfSpades() ? 1 : 0;
return score;
}
private boolean pileHasAtLeast(int count, Predicate<Card> predicate)
{
int result = 0;
for(Card card : pile)
{
if(predicate.test(card))
{
result ;
}
}
return result > count;
}
Комментарии:
1. для этого вам не обязательно нужен специальный метод, вы можете написать
pile.stream().filter(c -> c.getSuit() == Card.Suit.SPADES).count() > 6
2. также ваш предикат был бы (на мой взгляд) лучше назван как
isSpade
, чемspades
3. и строго в отношении названия метода: это не «по крайней мере», это «больше, чем» (если только это не ошибка, и это должно быть
>=
вместо>
)4. Оригинальные методы, вероятно, делают код более читабельным (хотя это только мое мнение). Но вы, конечно, могли бы делегировать эти методы
Predicate
методу-accepting, чтобы избежать дублирования кода.5. @njzk2 Да, это ошибка, которую я исправил сразу после публикации, спасибо!
Ответ №1:
Я провел некоторый рефакторинг вашего кода
public int computeScore()
{
//here we count all of the conditions that are true (won't work if you need to add different score for each condition)
return scopas Stream.of(pile.size() > 20,
pileHasAtLeast(6, c -> c.getSuit() == Card.Suit.SPADES),
pileHasAtLeast(3, c -> c.getValue() == Card.Value.SEVEN),
pileHasAtLeast(1, c -> c.getValue() == Card.Value.SEVEN)
amp;amp; c.getSuit() == Card.Suit.SPADES)
.filter(condition -> condition)
.count();
}
private boolean pileHasAtLeast(int count, Predicate<Card> predicate)
{
return count <= pile.stream()
.filter(predicate)
.count();
}
Несколько советов:
- Не смешивайте императивные циклы, такие как «для», с функциональным программированием. Используйте потоки всякий раз, когда у вас нет побочных эффектов.
- Не храните предикаты, которые вы используете только один раз в переменной, вы теряете силу функционального программирования.
Комментарии:
1. Отвечая на ваш вопрос: использование предикатов-хорошая практика. В вашем случае лучше использовать их встраиваемыми.
2. Хороший рефакторинг, хотя я не уверен насчет второго совета и стиля в вашем коде. Я использовал переменные, чтобы сделать свой код более читабельным. Я смотрю на ваш код, и его трудно прочитать из-за всех вкладок и необъяснимых условий. Я хотел назвать предикаты так, чтобы они могли быть быстрее поняты читателями. Является ли плохая читаемость следствием функционального программирования или есть лучшее решение?
3. @Picarrow Я не нахожу это менее читабельным. Может быть, ты просто не привык к функциональности, я не знаю. Форматирование, однако, может быть изменено на то, что вам больше подходит.
4. Это определенно правда! Я еще не привык к функциональности. Но я не уверен, что есть лучший способ отформатировать его lol. Думаю, мне придется к этому привыкнуть.
Ответ №2:
Если у вас есть молоток, все выглядит как гвоздь.
Так обстоит дело и здесь. Предикаты хороши, но здесь, особенно в score = ... ? 1 : 0;
строках, полная логика низкого уровня сжата в определенную форму.
Было бы лучше написать код, а затем снова добавить абстракции.
Я все равно не стал бы списывать предикаты. Но не на карточке, а на стопке.
Если вы хотите, чтобы ваш счет был объяснен (для новичков в игре), отдельные предикаты вместе с его счетом и пояснительным текстом "* 1 Point: " "more than 6 spades"
Заимствование замены потока @nzjk2:
record ScoreRule(int score, Predicate<Pile>, String explanation);
ScoreRule[] scoreRules = {
new ScoreRule(1,
pile -> pile.size() > 20,
"more than 20 cards"),
new ScoreRule(1,
pile -> pile.stream().filter(c -> c.getSuit() == Card.Suit.SPADES).count() > 6,
"more than 6 spades"),
new ScoreRule(...),
...
};
... score counting and reporting ...
Причина, по которой это работает лучше, заключается в том, что нужно обрабатывать кучу в целом, а поток имеет большую логику оценки и выразительные возможности запросов, такие как количество, содержит, все совпадения, ни одного совпадения.
Комментарии:
1. Интересный подход! Хотя, как бы я подсчитал счет? Выполните итерацию через ScoreRule[] и протестируйте предикат для каждого правила?
2. @Picarrow да, и снова возможен поток массива, фильтрация по истинным предикатам и суммирование результатов. Но повторение-это нормально. Мне нравится этот декларативный подход: модель логики высокого уровня.
3. Теперь это безумно мощно, я должен начать думать так же, лол! Спасибо за понимание!