【问题标题】:Best design pattern for API chaining in JAVAJAVA中API链接的最佳设计模式
【发布时间】:2018-05-10 06:43:29
【问题描述】:

我想在 Java 中调用一系列 API 调用。要求是某些 API 的响应将在后续 API 调用的请求中使用。我可以使用某些循环来实现这一点。但我想以实现通用的方式使用设计模式。有什么帮助吗?

责任链不能满足我的需求,因为我一开始不知道我的请求上下文是什么。

String out = null;
Response res = execute(req);
out += res.getOut();
req.setXYZ(res.getXYZ);
Response res = execute(req);
out += res.getOut();
req.setABC(res.getABC);
Response res = execute(req);
out += res.getOut();

System.out.println("Final response::"+out);

【问题讨论】:

  • req 是什么类型?它始终是同一个对象吗?
  • 是的,它是同一个对象,在调用之间有时会通过消耗前一个调用的响应来修改它。

标签: java api design-patterns method-chaining


【解决方案1】:

想到以下几点:

  1. 对于返回对象的函数调用:永远不要返回 null。
  2. 对于不(否则)不返回任何内容的函数调用:return this
  3. 接受 API 中的功能接口,以便用户自定义行为
  4. 对于如上所述公开 API 的有状态对象,提供 Builder 模式,这样用户就不会在构造函数之间进行选择
  5. 描述的 Builder 的所有方法都必须是 void,因此返回 this

【讨论】:

    【解决方案2】:

    您可以创建一个ResponseStringBuilder 类,该类接受Function<Response,String> 以从Response 获取String

    public ResponseStringBuilder {
        private Request request;
        public StringBuilder resultBuilder = new StringBuilder();
        public ResponseBuilder(Request req) {
            this.request = req;
        }
        public ResponseStringBuilder fromExtractor(Function<Request, Response> getResponse, Function<Response,String> extract) {
            Response response = getResponse.apply(request);
            resultBuilder.append(extract.apply(response));
            return this;
        }
        public String getResult() {
             return resultBuilder.toString();
        }
    }
    

    这会让你打电话

    ResponseStringBuilder builder = new ResponseStringBuilder(req);
    @SuppressWarnings("unchecked")
    Function<Response,String> extractors = new Function[] {
        Response::getABC, Response::getXYZ 
    };
    for (Function<Response,String> ext : extractors) {
        builder = builder.fromExtractor(this::execute, ext);
    }
    System.out.println("final response: " + builder.getResult());
    

    不确定数组声明是否真的可以编译,但只要稍作修改,它就可以工作,你就明白了。

    【讨论】:

    • new Function&lt;Response,String&gt;[] { .. } 是如何工作的?
    • @Andrew 通过删除通用参数并添加警告抑制。一个小的编译器错误是否会使这个模式看起来如何的答案无效?
    【解决方案3】:

    您可以使用CompletableFuture 在 Java 中实现 Promise。问题是,您试图通过“管道”传递两种不同的东西:请求是可变的并且(有时)会发生变化,而结果是在调用过程中累积的。

    我通过创建一个名为Pipe 的类来解决这个问题,该类有一个请求,以及迄今为止结果的累加器。它对两者都有 getter,并且它有一些方便的方法来返回一个带有累积结果的新对象,甚至可以改变请求并在一次调用中累积。这使得 API 链接的代码更加简洁。

    字段、构造函数和getter 之后的with* 方法是处理累积和变异的方法。 chain 方法将它们放在一起:

    import java.util.concurrent.CompletableFuture;
    
    public class Pipe {
        private Request req;
        private String out;
    
        public Pipe(Request req, String out) {
            this.req = req;
            this.out = out;
        }
    
        public Request getReq() {
            return req;
        }
    
        public String getOut() {
            return out;
        }
    
        public Pipe with(String data) {
            return new Pipe(req, out + data);
        }
    
        public Pipe withABC(String abc, String data) {
            req.setABC(abc);
            return new Pipe(req, out + data);
        }
    
        public Pipe withXYZ(String xyz, String data) {
            req.setXYZ(xyz);
            return new Pipe(req, out + data);
        }
    
        public static void chain(Request req) throws Exception {
            var promise = CompletableFuture.supplyAsync(() -> new Pipe(req, ""))
            .thenApply(pipe -> {
                Response res = execute(pipe.getReq());
                return pipe.withABC(res.getABC(), res.getOut());
            })
            .thenApply(pipe -> {
                Response res = execute(pipe.getReq());
                return pipe.withXYZ(res.getXYZ(), res.getOut());
            })
            .thenApply(pipe -> {
                Response res = execute(pipe.getReq());
                return pipe.with(res.getOut());
            });
    
            var result = promise.get().getOut();
    
            System.out.println(result);
        }
    
        public static Response execute(Request req) {
            return req.getResponse();
        }
    }
    

    因为它是异步运行的,所以它可以抛出InterruptedException,如果其他东西中断它也可以抛出ExecutionException。我不知道你想怎么处理,所以我只是宣布chain 扔。

    如果你想在循环中应用 n 操作,你必须不断地重新分配承诺,如下所示:

    var promise = CompletableFuture.supplyAsync(() -> new Pipe(req, ""));
    
    for (...) {
        promise = promise.thenApply(pipe -> {
            Response res = execute(pipe.getReq());
            return pipe.with(res.getOut());
        });
    }
    
    var result = promise.get().getOut();
    

    我在这里使用了带有var 的Java 10 类型推断,但promiseresult 的类型将分别为CompletableFuture&lt;Pipe&gt;String

    (注意:最好使Request 不可变,并在管道中传递一个新的、已更改的,而不是对其进行变异。另一方面,您也可以包装StringBuilder 而不是String ,并且让你积累的数据也是可变的。现在它是可变和不可变的奇怪组合,但这与你的代码正在做的事情相匹配。)

    【讨论】:

    • 编写一个包含所有变体的管道类不会帮助我使其通用。相反,我有兴趣在执行方法上编写一个包装器。
    • @anz 好吧,你最了解你的问题空间。执行的包装器很好,但它会返回什么?下一个请求,还是下一个“输出”数据?要链接它们,你需要两者。
    • 我正计划创建一个执行器,它将使用带有请求序列的数据结构(可能是堆栈)。并且包装器被递归调用,它将请求对象和响应对象作为参数。根据我的请求偏好,包装器将从响应中提取必要的数据并在请求中设置,然后最终调用执行方法。执行结束,执行者的最终响应将包含完整的响应。
    【解决方案4】:

    感谢大家的投入,最后我找到了一种满足我需求的解决方案。我使用了一个 Singleton 来执行请求。对于每种类型的命令,都会有一组以特定顺序执行的请求。每个命令都有一个特定的请求顺序,我将它们存储在一个带有请求唯一 ID 的数组中。然后将数组保存在与命令名称对应的映射中。

    在一个循环中,我运行数组并执行,每次迭代后,我不断将响应设置回请求对象并最终准备输出响应。

        private static Map<RequestAction,String[]> actionMap = new HashMap<RequestAction, String[]>();
    
        static{
        actionMap.put(RequestAction.COMMAND1,new String[]{WebServiceConstants.ONE,WebServiceConstants.TWO,WebServiceConstants.FOUR,WebServiceConstants.THREE});
        actionMap.put(RequestAction.THREE,new String[]{WebServiceConstants.FIVE,WebServiceConstants.ONE,WebServiceConstants.TWO});}
    
    
        public Map<String,Object> execute(ServiceParam param) {
    
        String[] requestChain = getRequestChain(param);
    
        Map<String,Object> responseMap = new HashMap<String, Object>();
    
    
        for(String reqId : requestChain) {
    
            prepareForProcessing(param, tempMap,responseMap);
    
            param.getRequest().setReqId(reqId);
    
            //processing the request
            tempMap = Service.INSTANCE.process(param);
    
            //prepare responseMap using tempMap         
    
            param.setResponse(response);
    
        }
    
        return responseMap;
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-24
    • 1970-01-01
    • 2013-12-01
    • 1970-01-01
    • 2017-10-27
    • 1970-01-01
    相关资源
    最近更新 更多