【问题标题】:Factory returning generic interface工厂返回通用接口
【发布时间】:2025-12-16 16:00:02
【问题描述】:

我有一个TemplateEngine 接口,其实现将是MoustacheTemplateEngineFreemarkerTemplateEngine 等。

public interface TemplateEngine<T> {
    public T compileTemplate(String templateStr);
    public void merge(T t, Map<String, Object> data);
    public String getTemplateLang();
}

compileTemplate 方法的目的是启用已编译模板的缓存。

这是一个实现示例:

import com.github.mustachejava.Mustache;

public class MoustacheTemplateEngine implements TemplateEngine<Mustache> {

    @Override
    public Mustache compileTemplate(String templateStr) {
        // return compiled template;
    }

    @Override
    public void merge(Mustache compiledTemplate, Map<String, Object> data) {
        // merge
    }

    @Override
    public String getTemplateLang() {
        return "moustache";
    }
}

我希望创建一个根据提供的模板语言返回TemplateEngine 的工厂。 工厂和使用工厂的客户对TemplateEngine 实现一无所知

public class TemplateEngineFactory {

    private Map<String, TemplateEngine<?>> TEMPLATE_ENGINE_REGISTRY = new HashMap<>();

    @PostConstruct
    public void init() {
        // Scan all TemplateEngine impls in classpath and populate registry
    }

    public TemplateEngine<?> getTemplateEngine(String templateLang) {
        return TEMPLATE_ENGINE_REGISTRY.get(templateLang);
    }

}

客户将使用以下工厂。

Map<String, Object> data = new HashMap<>();
data.put("name", "Tom");
TemplateEngine<?> templateEngine = factory.getTemplateEngine("moustache");
Object compiledTemplate = templateEngine.compileTemplate("Hi {{name}}");
templaeEngine.merge(compiledTemplate, data); // compile error

错误是The method merge(capture#3-of ?, Map&lt;String,Object&gt;) in the type TemplateEngine&lt;capture#3-of ?&gt; is not applicable for the arguments (Object, Map&lt;String,Object&gt;)

我了解该错误,并且我知道我的 API 设计存在缺陷,因为在工厂中使用了通配符。我的问题是如何为这种用例设计工厂并避免不安全的演员表?

【问题讨论】:

  • 你为什么不输入你的TemplateEngineFactory ?因此,当您实例化一些您想要的实现时,而不是使用通配符,调用 TemplateEngine 的任何方法都不会有任何问题。
  • 也许您也应该考虑将具体的模板实现(例如Moustache)隐藏在一个通用接口后面?
  • @yegodm 是的,但这并不能解决问题,因为我想避免在merge 方法中对编译后的模板进行类型转换。
  • 也许您也可以将merge() 委托给该接口?
  • @yegodm 在我得到正确的泛型之前,我仍然需要使用不安全的强制转换。

标签: java generics factory


【解决方案1】:

您需要在某个时候添加 unsafe 演员表。最好的选择是,

public <T> TemplateEngine<T> getTemplateEngine(String templateLang) {
    return (TemplateEngine<T>) TEMPLATE_ENGINE_REGISTRY.get(templateLang);
}

然后你可以这样称呼它,

TemplateEngine<Mustache> templateEngine = factory.getTemplateEngine("moustache");

【讨论】:

  • 这就是我对这个问题的评论的意思,我在一些项目上做了类似的事情,而且效果很好
  • 难道没有更好的方法来设计工厂,这样我就可以完全避免不安全的演员表吗?
  • 另外,这意味着客户端需要了解实现细节(在本例中为 Mustache 类)才能使用工厂。这对我来说是不行的。
  • 如果TemplateEngine 客户要求,则无法确切知道是什么类型。将Class 作为参数传递并执行class.cast。这至少可以消除警告,让您更灵活地处理调用者代码所要求的类型。
【解决方案2】:

只需去掉通配符,替换这个:

TemplateEngine&lt;?&gt; templateEngine = factory.getTemplateEngine("moustache");

到这里:

TemplateEngine templateEngine = factory.getTemplateEngine("moustache");

当你调用工厂来获取你想要的TemplateEngine时,你不需要显式地使用通配符。

【讨论】: