我在这里使用谓词是不好的做法还是额外的?

Posted

技术标签:

【中文标题】我在这里使用谓词是不好的做法还是额外的?【英文标题】:Is my usage of predicates here bad practice or extra? 【发布时间】:2021-10-16 04:06:22 【问题描述】:

我正在做一个纸牌游戏,我可能有点过度设计。在我有方法pileHasMostSpades()pileHasMostSevens() 之前。这些方法具有相同的代码,除了 if 条件和整数值。通过添加谓词和整数参数,我能够将这些方法合并为一个 (pileHasAtLeast())。

pileHasAtLeast(count, predicate) - 检查是否至少有 [count] 张卡片与谓词匹配。

此外,对于这种新型方法 (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;

【问题讨论】:

你不一定需要专门的方法,你可以写pile.stream().filter(c -&gt; c.getSuit() == Card.Suit.SPADES).count() &gt; 6 你的谓词(在我看来)命名为isSpade 比命名为spades 更好 严格意义上的方法名称:它不是“至少”,而是“超过”(除非这是一个错误,它应该是 &gt;= 而不是 &gt; 原始方法可能使代码更具可读性(尽管这只是我的看法)。但是您当然可以将这些方法委托给 Predicate-accepting 方法以避免代码重复。 @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) 
                                            && c.getSuit() == Card.Suit.SPADES)
                       .filter(condition -> condition)
                       .count();



private boolean pileHasAtLeast(int count, Predicate<Card> predicate)

    return count <= pile.stream()
         .filter(predicate)
         .count();

几个提示:

不要将“for”等命令式循环与函数式编程混合使用。在没有副作用的情况下使用流。 不要将只使用一次的谓词存储在变量中,否则会失去函数式编程的威力。

【讨论】:

回答您的问题:使用谓词是一种很好的做法。在您的情况下,最好内联使用它们。 不错的重构,但我不确定第二个技巧和代码中的样式。我使用变量使我的代码更具可读性。我正在查看您的代码,由于所有选项卡和无法解释的情况,很难阅读。我想命名谓词,以便读者更快地理解它们。可读性差是函数式编程的结果还是有更好的解决方案? @Picarrow 我不觉得它的可读性降低了。也许,你只是不习惯功能,我不知道。不过可以将格式更改为更适合您的格式。 这绝对是真的!我还不习惯功能。但我不确定是否有更好的方法来格式化它,哈哈。我想我得习惯了。【参考方案2】:

如果你有一把锤子,一切看起来都像钉子。

这里也是如此。谓词很好,但在这里,尤其是 score += ... ? 1 : 0; 行,完整的低级逻辑被压入特定的形式。

最好把代码写出来,然后再添加抽象。

我仍然不会注销谓词。但不是在卡片上,而是在一堆上。

您是否想要解释您的分数(针对游戏初学者)、单个谓词及其分数和说明文字"* 1 Point: " + "more than 6 spades"

借用 @nzjk2 的 Stream 替换:

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 ...

之所以效果更好,是因为堆作为一个整体是要处理的情况,一个Stream有很多求值逻辑,以及count、contains、all match、none match等表达查询能力。

【讨论】:

有趣的方法!虽然我将如何计算分数?遍历 ScoreRule[] 并针对每个规则的谓词进行测试? @Picarrow 是的,数组的 Stream 也是可能的,过滤真正的谓词并对分数求和。但是迭代很好。我喜欢这种声明式方法:逻辑的高级模型。 现在这太强大了,我得开始这样想了,哈哈!感谢您的洞察力!

以上是关于我在这里使用谓词是不好的做法还是额外的?的主要内容,如果未能解决你的问题,请参考以下文章

Docker:CentOS/RHEL额外配置

从接口转换到类,糟糕的设计还是小违规?

Accounts.onCreateUser 在创建新用户时添加额外的属性,好的做法?

将方法存储在将在“if”条件下使用的变量中是不好的做法还是不正确?

带有额外列的多对多自引用原则

Javascript Lint 声称额外的分号不好(在 `if` 之后)