What is Fluent API?
举个 jOOQ
的例子,一目了然
ctx.select(BOOK.ID, BOOK.NAME)
.from(BOOK)
.where(BOOK.AUTHOR.eq(0))
.fetch();
Fluent API的设计方法,非常适合作为复杂对象的 Builder。构造过程流程,指引性良好,而且能够保证有序。
流式调用也可以用于构建DSL(Domain-Specific Language),用于解决特定领域问题。
更复杂的 Demo,可以参考 COLA 的 StateMachine 和 jOOQ 的实现。
案例:封装 CompletableFuture
首先让我们看一个使用 CompletableFuture 的案例。下面这个例子是异步获取工资和年龄。这个方法内使用过多 try catch,跟 if else 过多一样,使得代码结构散乱难以理解。
@Test
void tooMuchTryCatch() {
CompletableFuture<Integer> salaryFuture = CompletableFuture.supplyAsync(() -> 1000);
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(() -> 30);
Integer salary = null;
try {
salary = salaryFuture.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
assertEquals(salary, 1000);
Integer age = null;
try {
age = ageFuture.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
assertEquals(age, 30);
}
这是一个来自我们项目中的场景:同时调用多个接口,如果接口异常则返回null或一个兜底值。像这样的调用模式如果经常在项目中出现,那么我们认为这是一个特定的领域问题,应该考虑使用一个DSL解法。
为了避免方法内使用过多的 try catch,我们可以使用 CompletableFuture.exceptionally()
。当异步调用发生错误时,我们经常只需要打印一条日志,再返回一个默认值。因此,我们可以封装个工具来规范化 Future 的构造。
接下来开始表演。首先定义API,有三个step:First
、Second
、Final
。
public interface Async { }
public interface AsyncContext {
<T> FirstStep<T> async(Supplier<T> supplier);
}
public interface FinalStep<T> extends Async {
CompletableFuture<T> run();
}
public interface FirstStep<T> extends FinalStep<T> {
SecondStep<T> errMsg(String errMsg);
FinalStep<T> fallback(T fallback);
FinalStep<T> onFailure(Function<Throwable, ? extends T> onFailure);
}
public interface SecondStep<T> extends FinalStep<T> {
FinalStep<T> fallback(T fallback);
}
然后是具体实现:
@Slf4j
public class AsyncImpl<T> implements FirstStep<T>, SecondStep<T> {
private Supplier<T> supplier;
private Function<Throwable, ? extends T> onFailure;
private String errMsg = "异步执行异常";
private T fallback;
private final Executor executor;
public AsyncImpl(Supplier<T> supplier, Executor executor) {
this.supplier = supplier;
this.executor = executor;
}
@Override
public SecondStep<T> errMsg(String errMsg) {
this.errMsg = errMsg;
return this;
}
@Override
public FinalStep<T> fallback(T fallback) {
this.fallback = fallback;
return this;
}
@Override
public FinalStep<T> onFailure(Function<Throwable, ? extends T> onFailure) {
this.onFailure = onFailure;
return this;
}
@Override
public CompletableFuture<T> run() {
if (onFailure == null) {
onFailure = ex -> {
log.warn("{}", errMsg, ex);
return fallback;
};
}
CompletableFuture<T> future = executor == null ?
CompletableFuture.supplyAsync(supplier) :
CompletableFuture.supplyAsync(supplier, executor);
return future.exceptionally(onFailure);
}
}
@Component
@RequiredArgsConstructor
public class AsyncContextImpl implements AsyncContext {
private final Executor executor;
@Override
public <T> FirstStep<T> async(Supplier<T> supplier) {
return new AsyncImpl<>(supplier, executor);
}
}
最后写个单元测试。这个单元测试介绍了这个 fluent api 的用法,无需多言。
class AsyncContextTest {
private AsyncContext asyncCtx;
@BeforeEach
void setup() {
asyncCtx = new AsyncContextImpl(null);
}
private Integer fuckUpTask() {
throw new RuntimeException("FUCK UP");
}
@Test
void defaultFail() {
CompletableFuture<Integer> future = asyncCtx.async(this::fuckUpTask)
.run();
assertNull(future.join());
}
@Test
void onFailure() {
CompletableFuture<Integer> future = asyncCtx.async(this::fuckUpTask)
.onFailure(ex -> -1)
.run();
assertEquals(future.join(), -1);
}
@Test
void errMsg() {
CompletableFuture<Integer> future = asyncCtx.async(this::fuckUpTask)
.errMsg("FUCK UP")
.fallback(-1)
.run();
assertEquals(future.join(), -1);
}
@Test
void multiTasks() {
CompletableFuture<Integer> failFuture = asyncCtx.async(this::fuckUpTask)
.fallback(-1)
.run();
CompletableFuture<Integer> successFuture = asyncCtx.async(() -> 1)
.run();
assertEquals(failFuture.join(), -1);
assertEquals(successFuture.join(), 1);
}
}
Kotlin version
最近学习了一下 kotlin,顺手就写个了 kotlin 的版本。
api.kt
interface Async
interface FinalStep<T>: Async {
fun run(): CompletableFuture<T>
}
interface FirstStep<T>: Async, FinalStep<T> {
fun onFailure(x: (Throwable) -> T): FinalStep<T>
fun errMsg(msg: String): SecondStep<T>
fun fallback(back: T): FinalStep<T>
}
interface SecondStep<T>: Async, FinalStep<T> {
fun fallback(back: T): FinalStep<T>
}
interface AsyncContext {
fun <T> async(x: () -> T): FirstStep<T>
}
impl.kt
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
class AsyncContextImpl(private val executor: Executor? = null) : AsyncContext {
override fun <T> async(x: () -> T): FirstStep<T> {
return AsyncImpl(x, executor)
}
}
class AsyncImpl<T>(private val supplier: () -> T,
private val executor: Executor?): FirstStep<T>, SecondStep<T> {
private var onFailure: ((Throwable) -> T?)? = null
private var errMsg: String = "FUCK UP"
private var fallback: T? = null
override fun onFailure(x: (Throwable) -> T): FinalStep<T> {
this.onFailure = x
return this
}
override fun errMsg(msg: String): SecondStep<T> {
this.errMsg = msg
return this
}
override fun fallback(back: T): FinalStep<T> {
this.fallback = back
return this
}
override fun run(): CompletableFuture<T> {
if (onFailure == null)
onFailure = { ex: Throwable ->
log.warn("{}", errMsg, ex)
fallback
}
return createFuture().exceptionally(onFailure)
}
private fun createFuture(): CompletableFuture<T> {
return if (executor == null)
CompletableFuture.supplyAsync(supplier)
else
CompletableFuture.supplyAsync(supplier, executor)
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。