java - 在父 Java 类中注入(inject)样板代码

标签 java api oop service abstract

我正在尝试编写一个客户端库,它将使用 HttpClient 向网络服务器发送信息和从网络服务器接收信息。这个库的核心是一个 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 调用变得尴尬(但并非不可能),但更糟糕的是,我有完全不知道如何创建和注入(inject)一组 JSON/XML 映射器,这些映射器将知道将 responseBody 映射回哪个模型/实体。

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

最佳答案

正如评论中提到的那样,我们在编译时就知道我们想要发出的请求类型和响应类型,所以我不会让代码在运行时简单地解决这些问题,这样它就可以被提取到一个基类中。您必须为其编写额外的代码来做出这些决定,这只会为细微的错误留下空间。此外,让 API 构建器仅根据参数来计算要进行的调用将使您陷入困境;例如getMostExpensiveWidgetgetLastWidgetUpdated两者听起来都像是无参数方法,那么 API 构建器类如何知道要做什么?

因此,假设我们将为 API 构建器类提供用于不同 API 调用的单独方法。还假设映射器类上的方法不是静态的。然后 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 构建器方法(例如 buildForBuzzbuildForId)和特定的响应映射器(jsonFizzMapperxmlFizzMapper),因为我们不想在运行时做出这些决定。因此,唯一真正的冗余是对 Web 客户端的调用。假设JsonFizzMapperXmlFizzMapper实现一些 FizzMapper接口(interface),我的下一步是在 FizzService 中创建以下私有(private)方法类:

private Fizz sendRequest(String apiCall, FizzMapper responseMapper) {
    String responseBody = webClient.send(apiCall);
    return responseMapper.toFizz(reponseBody);
}

getFizzByBuzzgetFizzById现在看起来像这样:

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 方法传递给构建器方法(例如 buildForBuzz ),但您必须跳过接口(interface)和匿名类方面的障碍似乎不值得。

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

关于java - 在父 Java 类中注入(inject)样板代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13924426/

相关文章:

java - 如何删除 Hashmap 中重复的键值对? - 不只是复​​制键或值

iphone - 适用于 iOS 和网站的 Twitter 应用程序

php - 如何实现 Paypal Recurring Payment API 并为买家提供通过信用卡/借记卡 php 付款的选项?

Python:如何用一些已知的值初始化 OOP 列表?

python - 如何防止多次捕获异常

php - 奇怪的多重继承的优雅替代品

java - Android Studio,进行选择会打开插入键

java - 如何从 Android 微调器获取字符串

java - 无法从java应用程序访问mysql数据库

c# - 返回数据时出现 WCF CommunicationException