基于运行时提供的字符串查找要使用的接口实现的优雅解决方案

use*_*318 5 java spring factory inversion-of-control

在重构一些代码的同时,我发现我们应该在几个地方使用一些多态,而不是必须在这个地方有一堆if/else块.

虽然面向对象的类很容易实现,但是当我们必须决定使用什么实现时,就会出现问题.似乎有很多解决方案,但我想看看是否有更优雅或"最佳实践"的方法来做到这一点.

所以基本上,例如,为了举例说明,我将选择一张信用卡,对于每种信用卡类型,我都有一个实现.因此我有这样的类结构:

  • CreditCard(摘要)
    • 签证
    • 万事达
    • 美国运通

每个子类都将扩展CreditCard超类.

现在在Web应用程序中,我将传递一个String,表示用户选择的卡类型.现在我需要将它路由到实际的实现类本身.这是众多选择发挥作用的地方.

如果有更好的选择或者我坚持这些,请告诉我.

工厂:

        @Autowired @Qualifier("visa") private CreditCard visa;
        @Autowired @Qualifier("mastercard") private CreditCard mastercard;
        @Autowired @Qualifier("amex") private CreditCard amex;

        public CreditCard getCreditCard(String input) {
        {
            if ("visa".equals(input)) {
                    return visa;
            } else if ("mastercard".equals(input)) {
                    return mastercard;
            } else if ("amex".equals(input)) {
                    return amex;
            }

            return null;
        }
Run Code Online (Sandbox Code Playgroud)

地图:

        @Autowired HashMap<String, CreditCard> creditCardImpls;

        public CreditCard getCreditCard(String input) {
            return creditCardImpls.get(input);
        }
Run Code Online (Sandbox Code Playgroud)

ApplicationContext getBean:

        @Autowired private ApplicationContext applicationContext;

        public CreditCard getCreditCard(String input) {
            return applicationContext.getBean(input);
        }
Run Code Online (Sandbox Code Playgroud)

我在这里看到的问题是,如果我们将来要添加更多信用卡类型,我将不得不在可能的几个不同领域进行自动装配.然后,Map的问题是我们没有使用Spring来抓取bean.对于来自ApplicationContext的getBean,我们不遵循Spring提供的IoC.

这个问题的最佳解决方案或最佳实践是什么?

Ada*_*lik 2

首先对您的解决方案进行一些评论,然后是我自己的建议。

  1. 这违反了开闭原则依赖倒置原则。工厂不仅需要明确地分别了解所有的bean及其对应的信用卡类型( input)映射,而且每次要引入新的信用卡类型时,都需要修改工厂类代码。
  2. 在我看来,这是迄今为止最好的解决方案。它动态地发现 bean 并依赖于CreditCard抽象。与您的评论相反,“它不是使用 Spring 来确定要使用哪个 bean”,该映射以 bean ID 作为键,并且由 Spring 自动装配,因此我们实际上在这里广泛使用 Spring。然而,由于映射键是 bean ID,因此将业务逻辑(信用卡类型 - input)与技术实现(Spring bean ID)混合在一起。如果您想实现一个单独的 bean 来处理“mastercard Platinum”信用卡类型怎么办?由于技术原因,您不能拥有这样的 bean ID(由于空间)。或者在某些时候您会希望某个 Bean 可以处理多种卡片类型。然后,您需要在业务标识符和 bean ID 之间实现另一个转换映射。请参阅下面我的解决方案以进行增强。
  3. 您应该尽可能避免将代码与框架代码(Spring 或任何其他代码)耦合。正如您所评论的,我们应该让 Spring 为我们处理 DI,以便工厂类具有注入的依赖项,而不必显式地请求它们。

我的建议是将业务密钥与 bean ID 分开。因此我们需要CreditCard能够说出它可以处理哪种信用卡类型。

abstract class CreditCard {
    public abstract String getType();
}
Run Code Online (Sandbox Code Playgroud)

然后每个子类返回自己的类型。

或者

abstract class CreditCard {
    private String type;

    protected CreditCard(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}
Run Code Online (Sandbox Code Playgroud)

每个子类super(<the subclass type>)在自己的构造函数中调用构造函数。

然后,在工厂里,你就可以这样实施。

@Autowire private Collection<CreditCard> creditCards;

private Map<String, CreditCard> creditCardsMap = new HashMap<>();

@PostCostruct
public void mapCreditCards() {
    for (CreditCard creditCard : creditCards) {
        creditCardsMap.put(creditCard.getType(), creditCard);
    }
}

public CreditCard getCreditCard(String type) {
    return creditCardsMap.get(type);
}
Run Code Online (Sandbox Code Playgroud)

@PostConstruct方法将在(所有类型为 的 bean)自动装配后运行creditCards从而CreditCard允许在业务键而不是技术键下进行动态发现和映射。