在执行函数时避免连续的“if (...)”检查

Mat*_*NNZ 12 java design-patterns

我有一个如下所示的函数:

public Status execute() {
    
    Status status = doSomething();

    if (status != Status.ABORTED) {
        status = doSomethingElse();
    }

    if (status != Status.ABORTED) {
        status = doAgainSomethingElse(param1, param2);
    }

    if (status != Status.ABORTED) {
        doSomethingWhichDoesntReturn(param3);
    }

    //etc.
    
    return status;
}
Run Code Online (Sandbox Code Playgroud)

所以基本上这个函数需要返回一个Status. 这是由第一个函数计算的,然后在执行这些函数时,由后续函数重新计算status != Status.ABORTED

我想重构这段代码,但我没有任何有效的想法。

如果总是这样status = someFunction(someParam),我会使用一个列表Function<TypeInput, Status>并在循环中执行该列表:

List<Function<TypeInput, Status>> actions = List.of(function1, function2...);
for (Function<TypeInput, Status> f : actions) {
    if (status != Status.ABORTED) {
        status = f.apply(input);
    }
}
Run Code Online (Sandbox Code Playgroud)

但问题是每个动作可能不同(有时它是一个返回的函数Status,有时有参数但不总是相同的大小,有时它只是一个空函数等)

有谁有想法吗?

注意:只要statusget Status.ABORTED,我就可以返回(我不需要执行函数的其余部分,因为只有当statusis not 时才会执行任何操作Status.ABORTED)。

Mur*_*göz 10

这看起来是 try-catch 方法的一个很好的例子。您可以在任一方法中抛出异常,例如StatusAbortedException并捕获该异常以返回适当的状态。它看起来像这样

try {
 Status status = doSomethingElse();
 status = doAgainSomethingElse(param1, param2);
 status = doSomethingWhichDoesntReturn(param3); // this one probably does smth else
 return status;
} catch (StatusAbortedException e){
  // return Status.Aborted 
}
Run Code Online (Sandbox Code Playgroud)


Tho*_*mas 6

扩展戴夫的想法(我也沿着同样的思路思考),你可以提供一个表示类似条件链的类:

//individual "chain links", i.e. elements in the chain
interface ChainLink<V> {
    V execute(V v) throws Exception;
}

class ConditionalChain<V> {
    private final V initialValue;
    private final Predicate<V> condition;
    private final Collection<ChainLink<V>> links = new LinkedList<>();
    
    //creates the chain with the initial condition value and the condition
    public ConditionalChain(V initialValue, Predicate<V> condition) {
        this.initialValue = initialValue;
        this.condition = condition;
    }

    //execute the chain
    public V execute() throws Exception {
        V v = initialValue;
        
        for( ChainLink<V> link : links ) {
          //apply the condition first to test the initial value
          if( !condition.test(v) ) {
            break;
          }

          v = link.execute(v);                
        }
        
        return v;
    }
    
    //chain a Callable that returns a new value
    public ConditionalChain<V> chain(Callable<V> c) {
        links .add(v -> c.call() );
        return this;
    }
    
    //chain a Runnable that doesn't change the value
    ConditionalChainer<V> chain(Runnable r) {
        links .add(v -> { r.run(); return v; } );
        return this;
    }
    
    //helper to get the chain started
    public static <T>  ConditionalChain<T> start(T initialValue, Predicate<T> condition) {
        return new ConditionalChain<T>(initialValue, condition);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们使用自己的(内部)功能接口来允许返回状态,即使在使用可运行对象时也是如此,并支持抛出异常。

这也可以扩展到允许将当前状态作为参数的函数。

链本身可能看起来像这样:

 Status status = ConditionalChain.start(Status.RUNNING, s -> s != Status.ABORTED )
        .chain(() -> doSomething())
        .chain(() -> doSomethingElse())
        .chain(() -> doSomethingWhichDoesntReturn(param3))
        .chain(() -> doAgainSomethingElse("param1", "param2"))
        .execute();
Run Code Online (Sandbox Code Playgroud)

这样您就可以使用不同的谓词重用该链。您甚至可以返回一个“链结果”,其中包含已执行的最后一个元素的状态和索引,例如,如果您有兴趣在 后停止执行doSomethingElse()


Haa*_*eit 5

您可以选择多种选择。一种选择是延续传球风格。这在 Java 中看起来不是那么好,但您可以做类似的事情。

// This is pseudo code, intended to illustrate the concept.
cpsMethod(Arg... args, ClosureOverFunctionSoItIsNullary continuation) {
 // do stuff
 continuation.call();
}
Run Code Online (Sandbox Code Playgroud)

所以基本上,该方法将接下来应该发生的事情传递给它。在 Java 中这种方法有一些缺点,即您没有尾调用优化,因此您可能会出现堆栈溢出,而且也许更重要的是,它看起来与普通 Java 非常不同。

// Illustrative pseudo code
return doSomething(() -> doSomethingElse(() -> doAgainSomethingElse(param1, param2, () -> doSomethingWhichDoesntReturn())));
Run Code Online (Sandbox Code Playgroud)

这将删除 ifs,或者更确切地说,将测试放在每个方法中,现在必须决定它是要继续,还是只返回 Status.ABORTED。您当然可以通过将处理放在外面并仅将方法作为生产者,提供谓词/硬编码测试,并仅提供可变参数来使这件事更漂亮:

private continuationPasser(Supplier<Status> first, Supplier<Status>... rest) {
    Objects.requireNonNull(first);
    Status status = first.get();
    for(Supplier<T> continuation : methods) {
        status = continuation.get();
        if(status == Status.ABORTED) {
            return status;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Dirt 简单的代码,完全符合您的期望,现在您的调用将来自:

public Status execute() {
    
    Status status = doSomething();

    if (status != Status.ABORTED) {
        status = doSomethingElse();
    }

    if (status != Status.ABORTED) {
        status = doAgainSomethingElse(param1, param2);
    }

    if (status != Status.ABORTED) {
        doSomethingWhichDoesntReturn(param3);
    }

    //etc.
    
    return status;
}
Run Code Online (Sandbox Code Playgroud)

类似于:

public Status execute() {
    return continuationPasser(
      this::doSomething,
      this::doSomethingElse,
      () -> doAgainSomethingElse(arg1, arg2);
      () -> doSomethingWhichDoesntReturn(arg3));
Run Code Online (Sandbox Code Playgroud)

除了,你知道,最后一个不返回任何东西。如果让它返回一些东西很简单,那么你可以这样做。如果这不是微不足道的,您可以将类型从供应商更改为 Function<Status, T>,并且您可以根据需要传入最后一个状态。

但这是一个选择。采取一个功能性的想法并使其发挥作用。如果您知道什么是延续传递,这样做的好处是非常清楚。如果您愿意,您也可以将这个想法概括为采用谓词。另一种方法是稍微改变 continuationPasser,让它传入之前的结果,让方法自己决定他们想要做什么。

然后 continuationPasser 可以是这样的:

continuationPasser(Function<Status, Status> first, Function<Status, Status>... rest) {
    Objects.requireNonNull(first);
    Status status = first.apply(Status.SOME_REASONABLE_VALUE_LIKE_NOT_STARTED);
    // You could use some reduce function here if you want to.
    // The choice of a loop here is just my personal preference.
    for(Function<Status, Status> fun : rest) {
      status = rest.apply(status);
    }
    return status;
}
Run Code Online (Sandbox Code Playgroud)

这使得继续传递器变得更加简单。您首先应用第一个函数,以获得一个起始值。然后你就对其余的人进行 for-each。他们可以从检查 ABORTED 状态开始并提前退出。您仍然可以使用 if,但是您的主要运行代码现在看起来非常整洁。您始终可以将方法包装在以下内容中:

Function<Status, Status> runIfNotAborted(Supplier<Status> supplier) {
  return (Status s) -> s == ABORTED? ABORTED : supplier.get();
}

Function<Status, Status> returnPreviousStatus(Runnable code) {
  return (s) -> {
    code.run();
    return s;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在您甚至不必改变您的方法。(但是,如果您要使用这种样式,如果可用,那可能是更好的选择。)

public Status execute() {
    return continuationPasser(
      runIfNotAborted(this::doSomething),
      runIfNotAborted(this::doSomethingElse),
      runIfNotAborted(() -> doAgainSomethingElse(arg1, arg2)),
      runIfNotAborted(returnPreviousStatus(() -> doSomethingWhichDoesntReturn(arg3)));
Run Code Online (Sandbox Code Playgroud)

现在很清楚发生了什么。我们在函数之上构建函数,看起来有点像函数式装饰器模式。

这是一个非常笼统的想法,如果您愿意,您可以更专业地进行此操作或对其进行更多的概括。但是要小心,否则您将编写一个框架而不必编写 if/else。Jenkins 将这个想法用于它的管道,但它还有更多的东西来传递环境。