使用Java泛型设计转换器SPI的正确方法

时间:2016-04-18 11:19:30

标签: java generics

little library的作者使用Mailgun service发送电子邮件。

该库目前有一种非常简单的机制,可以使用某种HTML DSL生成丰富的邮件正文内容。我正在研究下一个版本,我希望提供转换器SPI,以便您可以传递根据需要进行格式化的对象。我正在考虑数字,日期,货币价值等。

我在使用泛型,通配符类型,边界等方面苦苦挣扎。我认为我的问题更多的是一般设计,而不是泛型使用的细节。所以我的问题是,鉴于Java泛型的限制,我应该如何设计我的SPI。

我已经开始定义转换器接口:

public interface ContentConverter<T> {
    String toString(T value);
}

整个库中都有一个配置对象。在那里,用户应该使用这种注册方法注册该接口的实例。

public <T> Configuration registerConverter(ContentConverter<? super T> converter,
                                           Class<T> classToConvert)
{
    converters.add(new Converter<>(classToConvert, converter));
    return this;
}

Converter类是封装转换器及其类的内部类。

private final static class Converter<T> {
    private Class<T> classOfConverter;
    private ContentConverter<? super T> contentConverter;

    Converter(Class<T> classOfConverter,
              ContentConverter<? super T> contentConverter)
    {
        this.classOfConverter = classOfConverter;
        this.contentConverter = contentConverter;
    }
}

Configuration会保留转换器列表。

private List<Converter<?>> converters = ...

最后,配置按需提供转换器。

@SuppressWarnings("unchecked")
public <T> ContentConverter<T> converter(Class<T> classToConvert) {
    for (Converter<?> converter : converters)
        if (classToConvert.isAssignableFrom(converter.classOfConverter))
            return (ContentConverter<T>) converter.contentConverter;
    return (ContentConverter<T>) defaultConverter;
}

默认转换器只是调用Object.toString()

private final static ContentConverter<Object> defaultConverter =
    new ContentConverter<Object>() {
        @Override
        public String toString(Object value) {
            return value.toString();
        }
    };

当用户构建正文消息时,他可以调用这两种方法中的任何一种来使用配置的转换器添加对象。

public Builder text(Object value) {
    ContentConverter<?> converter = configuration.converter(value.getClass());
    return text(value, converter);
}

public <T> Builder text(T value, ContentConverter<T> converter) {
    return text(converter.toString(value));
}

public Builder text(String s) {
    [...] // this is where the content is really appendend
    return this;
}

但这并不奏效。第一种方法不能编译。我尝试了不同的组合,但我认为问题更深入,有关该类型的删除。

2 个答案:

答案 0 :(得分:0)

只需将第一个text方法的value参数设为通用。 (你仍然需要为类对象进行强制转换):

public <T> Builder text(T value) {
    ContentConverter<T> converter = configuration.converter((Class<T>)value.getClass());
    return text(value, converter);
}

答案 1 :(得分:0)

确保您正在进行正确的演员,也是为了价值:

o6

并在您的第一个文字中定义# [o2, o4, o5, o7, o8, o9, o10] &#39;方法

无论如何,请注意您的代码会产生public <T> Builder text(Object value) { Configuration configuration = new Configuration(); ContentConverter<T> converter = configuration.converter((Class<T>)value.getClass()); return text((T)value, converter); } ,因为两个T方法都是相互调用的,并且永远不会获得任何输出。