在父Java类中注入样板代码

时间:2012-12-18 00:16:07

标签: java api oop service abstract

我正在尝试编写一个客户端库,它将使用HttpClient向/从Web服务器发送和接收信息。该库的核心是WebClient类:

public class WebClient {
    public String send(String apiUrl) {
        // Use HttpClient to send a message to 'apiUrl', such as:
        // http://example.com?a=1&response=xml
        //
        // Wait for a response, and extract the HTTP response's body out
        // as raw text string. Return the body as a string.
    }
}

现在再次,这是我写的mylib.jar)。由各种不同的服务组成的库,其中每个服务都有1个以上的方法,API开发人员可以使用这些方法读取/写入服务器的数据。所以服务如:

WidgetService
    getAllWidgets
    getMostExpensiveWidget
    getLastWidgetUpdated
    etc...
FizzService
    getFizzById
    getFizziestFizz
    etc...
BuzzService
    etc...
...

每种服务方法将采用1个以上的Java原语或1个以上的模型实例(实体,值对象等)。 每个服务方法都会返回一个这样的模型对象。例如:

public class FizzService {
    public Fizz getFizzById(Long fizzId) {
        // ...
    }

    public Fizz getFizzByBuzz(Buzz buzz) {
        // ...
    }
}

这一点很重要,因为这意味着WebClient#send()收到的HTTP响应主体最终需要映射回Java对象。

从API开发人员的角度来看,我只是希望开发人员实例化每个服务实例,将其传递给WebClient以便在引擎盖下使用,然后让服务执行参数和api url之间的所有映射,以及HTTP响应主体和模型/实体。

例如:

public class FizzService {
    private WebClient webClient;
    private FizzApiBuilder fizzApiBuilder;

    // Getters & setters for all properties...

    public Fizz getFizzByBuzz(Buzz buzz) {
        // apiCall == "http://example.com/fizz-service/getFizzByBuzz?buzz_id=93ud94i49&response=json"
        String apiCall = fizzApiBuilder.build(buzz).toString();

        // We asked API to send back a Fizz as JSON, so responseBody is a JSON String representing
        // the correct Fizz.
        String responseBody = webClient.send(apiCall);

        if(apiCall.contains("json"))
            return JsonFizzMapper.toFizz(reponseBody);
        else
            return XmlFizzMapper.toFizz(responseBody);
    }
}

// What the API developer writes:
WebClient webClient = WebClientFactory.newWebClient();
FizzService fizzSvc = new FizzService(webClient);

Buzz b = getBuzz();

Fizz f = fizzSvc.getFizzByBuzz(b);

到目前为止,我喜欢这个设置。但是,我需要所有服务

String apiCall = someBuilder.build(...)
String responseBody = webClient.send(apiCall)
if(apiCall.contains("json"))
    return JsonMapper.toSomething(responseBody)
else
    return XmlMapper.toSomething(responseBody)

这开始闻起来像抽象的主要用例。理想情况下,我希望将所有这些样板代码放在AbstractService中,并让每个服务扩展该抽象类。只有一个问题:

public abstract class AbstractService {
    private WebClient webClient;
    private ServiceApiBuilder apiBuilder;

    // Getters & setters for all properties...

    public Object invoke(Object... params) {
        // apiCall == "http://example.com/fizz-service/getFizzByBuzz?buzz_id=93ud94i49&response=json"
        // The injected apiBuilder knows how to validate params and use them to build the api url.
        String apiCall = apiBuilder.build(params).toString();

        String responseBody = webClient.send(apiCall);

        if(apiCall.contains("json"))
            return jsonMapper.to???(reponseBody);
        else
            return xmlMapper.to???(responseBody);
    }
}

public class FizzService extends AbstractService { ... }

问题在于,将功能抽象为AbstractService会使构建API调用的内容尴尬(但并非不可能),但更糟糕的是,我绝对不知道如何创建和注入一组JSON / XML映射器,这些映射器将知道要将responseBody映射回的模型/实体。

所有这一切都开始发臭了。当我的代码发臭时,我寻找解决方案,如果我找不到任何解决方案,我来到这里。所以我问:我采取了一种根本错误的方法,如果是这样,我的方法应该是什么?如果我很接近,那么如何注入正确的映射器并使这些代码更好闻?请记住我的代码片段,显示我希望API开发人员如何实际使用模型/服务 - 这是最终的这里的目标。提前谢谢。

1 个答案:

答案 0 :(得分:2)

正如评论中所提到的那样,我们知道我们想要做出的请求类型以及编译时的响应类型,所以我不会让代码在运行时将这些内容完成,以便它可以被提取到基类中。你必须为它做出额外的代码来做出这些决定,这只会留下微妙的错误空间。此外,让API构建器根据参数计算出基于make的调用,将把你画成一个角落;例如getMostExpensiveWidgetgetLastWidgetUpdated都听起来像无参数方法,那么API构建器类将如何知道该怎么做?

因此,假设我们将为API构建器类提供不同API调用的单独方法。还假设mapper类上的方法不是静态的。然后getFizzByBuzz方法可能如下所示:

public Fizz getFizzByBuzz(Buzz buzz) {
    String apiCall = fizzApiBuilder.buildForBuzz(buzz).toString();
    String responseBody = webClient.send(apiCall);
    return jsonFizzMapper.toFizz(reponseBody);
}

同样,getFizzById方法可能如下所示:

public Fizz getFizzById(Long fizzId) {
    String apiCall = fizzApiBuilder.buildForId(fizzId).toString();
    String responseBody = webClient.send(apiCall);
    return xmlFizzMapper.toFizz(reponseBody);
}

这两种方法都遵循相同的模式,它们因特定的API构建器方法(例如buildForBuzz vs buildForId)和特定的响应映射器(jsonFizzMapper vs {{1}而不同因为我们不想在运行时做出这些决定。因此,唯一真正的冗余是对Web客户端的调用。假设xmlFizzMapperJsonFizzMapper实现了一些XmlFizzMapper接口,我的下一步是在FizzMapper类中创建以下私有方法:

FizzService

private Fizz sendRequest(String apiCall, FizzMapper responseMapper) { String responseBody = webClient.send(apiCall); return responseMapper.toFizz(reponseBody); } getFizzByBuzz现在看起来像这样:

getFizzById

我认为这是纯洁与实用主义之间的良好平衡。理想情况下,您只需将sendRequest方法传递给构建器方法(例如public Fizz getFizzByBuzz(Buzz buzz) { String apiCall = fizzApiBuilder.buildForBuzz(buzz).toString(); return sendRequest(apiCall, jsonFizzMapper); } public Fizz getFizzById(Long fizzId) { String apiCall = fizzApiBuilder.buildForId(fizzId).toString(); return sendRequest(apiCall, xmlFizzMapper); } ),但是在接口和匿名类方面您必须跳过的箍似乎不值得。

如果你想要更进一步,你可以使sendRequest方法通用,然后将它放入一个抽象基类(或者最好支持composition over inheritance并将它放在一个完全独立的类中),然后你需要使您的映射器接口通用。但是因为那个类只包含一个两行方法,所以我觉得它可能不值得。因此,我认为为每个服务类创建一个相应的私有方法应该没问题,但如果您认为它可能会变得更复杂,那么在其他地方提取功能。