【问题标题】:Functional solution instead of iterative功能解决方案而不是迭代
【发布时间】:2021-04-11 17:42:48
【问题描述】:

我必须执行以下任务。为简单起见,我创建了下表:

我们有输入和带有定义规则的表格。如果任何规则与此输入匹配,则对其应用操作。

例如输入HBC1234:

  1. starts_with H => true。
  2. 因此,将matching_value (H) 替换为M => 结果为MBC1234

以同样的方式迭代此输入的所有其他规则。

这是替换规则的实体:

@Data
@Entity
@NoArgsConstructor
public class ReplacementRule implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @NotNull
    @Enumerated(EnumType.STRING)
    private MatchingOperation matchingOperation;
    @NotNull
    private String matchingValue;

    @NotNull
    @Enumerated(EnumType.STRING)
    private ReplacementOperation replacementOperation;
    private String replacementValue;
}

以及该任务的实现:

private static String apply(ReplacementRule rule, String plateNumber) {
    switch (rule.getMatchingOperation()) {
        case STARTS_WITH:
            if (plateNumber.startsWith(rule.getMatchingValue())) {
                return switch (rule.getReplacementOperation()) {
                    case REPLACE -> plateNumber.replaceFirst(rule.getMatchingValue(), rule.getReplacementValue());
                    case REMOVE -> plateNumber.substring(rule.getMatchingValue().length());
                };
            }
        case CONTAINS:
            if (plateNumber.contains(rule.getMatchingValue())) {
                return switch (rule.getReplacementOperation()) {
                    case REPLACE -> plateNumber.replaceAll(rule.getMatchingValue(), rule.getReplacementValue());
                    case REMOVE -> plateNumber.replaceAll(rule.getMatchingValue(), "");
                };
            }
        case ENDS_WITH:
            if (plateNumber.endsWith(rule.getMatchingValue())) {
                return switch (rule.getReplacementOperation()) {
                    case REPLACE -> plateNumber.substring(0, getEndIndex(rule, plateNumber)).concat(rule.getReplacementValue());
                    case REMOVE -> plateNumber.substring(0, getEndIndex(rule, plateNumber));
                };
            }
        case EQUALS:
            if (plateNumber.equals(rule.getMatchingValue())) {
                return switch (rule.getReplacementOperation()) {
                    case REPLACE -> rule.getReplacementValue();
                    case REMOVE -> "";
                };
            }
    }
    return "";
}

private static int getEndIndex(ReplacementRule rule, String plateNumber) {
    return plateNumber.length() - rule.getMatchingValue().length();
}

最后的用法是这样的:

public Optional<WhiteList> checkReplacementRules(String plateNumber) {
    List<ReplacementRule> allRules = ruleRepository.findAll();

    Optional<WhiteList> result = Optional.empty();
    for (ReplacementRule rule : allRules) {
        String newPlate = transform(rule, plateNumber);
        if (StringUtils.isNotBlank(newPlate)) {
            result = whiteListRepository.findByNumberPlate(newPlate);
        }
    }

    return result;
}

它结合了匹配和逻辑,以一种方法apply() 进行替换。但是,它违反了Open-Closed Principle。但它有效。尽管解决方案不是最好的。

似乎这种任务符合函数式编程概念。

如何将其重新设计为实用风格?

Java 版本为 15

【问题讨论】:

  • 为什么你认为它违反了开闭原则? cases 会考虑添加吗?
  • @GyuHyeonChoi 谁知道。未来会添加哪些要求/条件始终是不可预测的。
  • 如果有新的需求,修改代码不是可以避免的吗?我认为您的代码可以始终如一地添加规则并满足要求。
  • “替换操作”的抽象真的有必要吗? “remove”和“replace with an empty string”没有区别,反正就是携带字符串。
  • @Holger 拍得好

标签: java java-8 functional-programming


【解决方案1】:

enum 类实现动作,即操作。

Regex 似乎非常适合您的工作。

private static String apply(ReplacementRule rule, String plateNumber) {
    String regex = rule.getMatchingOperation().regexFor(rule.getMatchingValue());
    String replacement = rule.getReplacementOperation().regexFor(rule.getReplacementValue());
    return Pattern.compile(regex)
                  .matcher(plateNumber)
                  .replaceFirst(replacement);
}
public enum MatchingOperation {
    STARTS_WITH(v -> "^" + Pattern.quote(v)),
    CONTAINS   (v -> Pattern.quote(v)),
    ENDS_WITH  (v -> Pattern.quote(v) + "$"),
    EQUALS     (v -> "^" + Pattern.quote(v) + "$");

    private final UnaryOperator<String> asRegex;

    private MatchingOperation(UnaryOperator<String> asRegex) {
        this.asRegex = asRegex;
    }

    public String regexFor(String matchingValue) {
        return this.asRegex.apply(matchingValue);
    }
}
public enum ReplacementOperation {
    REPLACE(Matcher::quoteReplacement),
    REMOVE (v -> "");

    private final UnaryOperator<String> asRegex;

    private ReplacementOperation(UnaryOperator<String> asRegex) {
        this.asRegex = asRegex;
    }

    public String regexFor(String replacementValue) {
        return this.asRegex.apply(replacementValue);
    }
}

【讨论】:

  • 与其在课堂外使用UnaryOperator&lt;String&gt;Pattern.compile,不如使用Function&lt;String,Pattern&gt; 更有意义。然后您可以将Pattern.compile(v, Pattern.LITERAL) 用于CONTAINS。更好的做法是将实际匹配操作包含在MatchingOperation 中(正如其类名已经暗示的那样)并返回Matcher,然后,您可以使用lookingAtmatches 而不是find 并且不要需要进行字符串连接以将锚点添加到模式中。
  • @Holger 您能否为您的建议展示实施方案。它会更容易理解。
【解决方案2】:

我不认为您的设计违反了开闭原则,但是,这是我的实现。由于它可以将未来的需求添加为函数,因此可以称为函数式。

@FunctionalInterface
interface Rule {
    public String apply(String string);
}

Rule 用于每个需求的函数。

class Replacement {
    ArrayList<Rule> rules = new ArrayList<Rule>();

    public Replacement addRule(Rule rule) {
        rules.add(rule);
        return this;
    }
    
    public String applyAll(String string) {
        System.out.printf("%s --> ", string);
        for (Rule rule : rules) {
            string = rule.apply(string);
        }   
        System.out.println(string);
        return string;
    }
}

Replacement可以addRuleapplyAll规则输入。

下面是Rules 如何作为函数添加和应用:

public class Main {
    public static void main(String[] args) {
        Replacement r = new Replacement()
        
        .addRule((String string) -> {
            if (string.startsWith("H"))
                return "M" + string.substring(1);
            return string;
        })
 
        .addRule((String string) -> {
            return string.replaceAll("A", "");
        })
        
        .addRule((String string) -> {
            if (string.endsWith("8"))
                return string.substring(0, string.length() - 1);
            return string;
        })
        
        .addRule((String string) -> {
            if (string.equals("ABC12B467"))
                return "ABC123467";
            return string;
        });

        r.applyAll("HBC1234");
        r.applyAll("ABC1234");
        r.applyAll("ABC9988");
        r.applyAll("ABC12B467");
    }
}

【讨论】:

  • 我将所有规则都存储在数据库中。所以只好从DB里全部拿到,一一申请搜索。
  • @catch23 您只需要定义一个函数如何从数据库加载规则并应用它,然后将函数传递给addRule。您已经有了解决方案,但是,您需要一种功能性的方法来做到这一点。
猜你喜欢
  • 1970-01-01
  • 2021-02-16
  • 2013-07-22
  • 2016-03-19
  • 2020-06-07
  • 2015-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多