SpringBoot中的抽象工厂模式@Service

时间:2019-09-30 09:58:39

标签: java spring-boot

我想知道如何在应用程序内部将Abstract Factory Implementation类用作@Service。 我有想要的逻辑的多个提供程序,并通过构造函数将它们注入。问题是我不确定我的Factory是否应具有构造函数,如果不确定,则应将其构造为私有。

我已将此类注释为@Service,但想知道其中的任何一种是否很好,以及什么是最佳实践。 谢谢,抱歉,如果鞋帮很乱,这实际上是我的第一个问题和帖子。

这是我的代码段:

    private final FirstClient first;
    private final SecondClient second;

    private SentimentFactoryImpl(FirstClient first, SecondCliend second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public SentimentClient get(String clientName) {
        if ("First".equalsIgnoreCase(clientName)) {
            return this.first;
        } else if ("Second".equalsIgnoreCase(clientName)) {
            return this.second;
        } 
        throw new UnknownClientException("Service doesn't provide support for client: " + clientName);
    }

4 个答案:

答案 0 :(得分:2)

实际上,Spring框架的核心是一种超级灵活且功能强大的抽象工厂。如果您使用的是Spring,则应将所有类创建功能委托给Spring核心。在您的情况下,我更喜欢使用Spring java Configurations进行类创建。相当于您春季工厂的样子。

@Configuration
public class ServiceConfig {

    @Bean
    public SentimentClient firstClient() {
        return new FirstClient();
    }

    @Bean
    public SentimentClient secondClient() {
        return new SecondClient();
    }
}

Spring核心识别出此类之后,每当其他Spring bean需要它时,它将用作工厂来获取服务的实例。您不需要像工厂中那样调用任何类型的get方法。 Spring是自动完成的。

@Service
public class SomeService {

    private final SentimentClient firstClient;

    @Autowired
    private SomeService(SentimentClient firstClient) {
        this.firstClient = firstClient;
    }

    // Some business logic here 
}

值得一提的是,Spring配置中的factory方法的名称是由此方法创建的bean(服务)的名称,并且具有类类型的名称用于查找自动装配所需的bean。

答案 1 :(得分:1)

您应考虑以下实践以应用最佳实践:

  1. 不要紧密耦合对象字段,而要注入它们。这将允许在需要时更改对象字段类。

  2. 要满足第一点,请务必按合同进行操作,即使用接口。

  3. 确定每次调用工厂时是单例还是新对象。

  4. 抽象工厂的可维护性。

在您的情况下,您已经遵守#2。休息一下 a)为FirstClientSecondClient创建bean。

b)如果这些必须从final关键字看来是单例的,但在工厂类中不是显而易见的,则使两个客户端类都是单例的。

c)而不是使用字符串clientName,而是创建一个枚举,因为它总是可维护且安全,因为您始终在寻找定义明确的值。

d)摆脱if-else,而使用一个映射,该映射填充为工厂的后构造。这样可以加快获取豆子的速度。

e)您还可以想到延迟初始化:)。

class SentimentFactoryImpl{
    private Map<Client, SentimentClient> factoryMap; 
    @Autowired
    private final FirstClient first;
    @Autowired
    private final SecondClient second;



    @Override
    public SentimentClient get(String clientName) {
       Client Client.getByName(clientName);
     if(!factoryMap.contains(client)){
         throw new UnknownClientException("Service doesn't provide support for client: " 
         + clientName);
      }
       return factoryMap.get(Clients.getByName(clientName));
    }

答案 2 :(得分:0)

由于您说此类被注释为@Service,所以我假设您希望两个客户端都自动连接到该客户端,并且它们也都是bean。

在这种情况下,您可以在构造函数中对其进行自动装配(在这种情况下,构造函数应该是公共的)。另外,您也可以不使用构造函数,而直接(或通过setter方法)自动连接字段-不建议这样做,更好的做法是使用构造函数自动连接。

答案 3 :(得分:0)

我知道您已经接受了答案,但我只想发表自己的看法。第一件事是我不想使用Abstract Factory模式,似乎您不是在创建对象,而只是想根据它们的名称来定位客户端对象。这就是Locator的工作,我们称它为ClientLocator(由于缺少更好的名称)。

这个想法是创建一个名为ClientLocator的类来实现ApplicationContextAware。通过实现此接口,Spring将通过setter方法注入ApplicationContext对象。使用ApplicationContext对象,我们可以按其名称查找bean,然后检查bean的类型以确保获得正确的bean,否则返回错误。

这是代码的外观。

客户

// SentimentClient.java
public interface SentimentClient {

    void call();

}


// FirstClient.java
@Service
public class FirstClient implements SentimentClient {


    @Override
    public void call() {
        System.out.println("Hello from the first client");
    }
}

// SecondClient.java
@Service
public class SecondClient implements SentimentClient {

    @Override
    public void call() {
        System.out.println("Hello from the second client");
    }
}


ClientLocator.java


@Component
public class ClientLocator implements ApplicationContextAware {


    private ApplicationContext ctx;

    public SentimentClient get(String name) {
        return ctx.getBean(name + "Client", SentimentClient.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
}

这是我们使用定位器的方式。

ClientLocatorDemo.java

@Component
public class ClientLocatorDemo implements CommandLineRunner {

    @Autowired
    private ClientLocator clientLocator;

    @Override
    public void run(String... args) {
        SentimentClient firstClient = clientLocator.get("first");
        SentimentClient secondClient = clientLocator.get("second");

        firstClient.call();
        secondClient.call();

        // output:
        // Hello from the first client
        // Hello from the second client
    }
}


通过这种方式,只要代码想要以其名称获取客户端,只需自动连接ClientLocator并调用get(String name)方法即可。

最重要的是,您可以根据需要创建任意数量的客户端实现,并按其名称获取它们(假定每个名称都有唯一的名称,并以“ Client”结尾),而不必一个接一个地自动接线。