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)
扩展戴夫的想法(我也沿着同样的思路思考),你可以提供一个表示类似条件链的类:
//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()。
您可以选择多种选择。一种选择是延续传球风格。这在 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 将这个想法用于它的管道,但它还有更多的东西来传递环境。
| 归档时间: |
|
| 查看次数: |
278 次 |
| 最近记录: |