适配器模式

初步构造

在利用minio开发文件传输模块的时候,发现一个问题。如果单一的使用minio的话,考虑一种好的情况,也许之后我的博客需要上传大量数据的时候,或许我的小服务器无法承受这么大的数据量以及访问量,这时候想要切换其他的oss云服务,如果在代码中写了固定的操作,似乎之后切换会非常的繁琐。比如我想用阿里云的oss服务了,该如何切换呢?

索性在开发的时候就一并写其他oss的操作,这就会涉及到一个问题,我定义了一个接口,不同的oss服务的实现类均继承自我这个接口,那么我再使用注解注入的时候,要写名称去注入吗?

1
2
3
4
5
6
7
8
public interface StorageService {

/**
* 列出所有桶
*/
List<String> listBuckets();
......
}

image-20240624234703290

image-20240624234713743

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class FileController {

@Resource
private StorageService minioStorageService;

@RequestMapping("/testGetAllBuckets")
public String testGetAllBuckets() throws Exception {
List<String> allBuckets = minioStorageService.listBuckets();
return allBuckets.get(0);
}
}

如果在controller类中,需要通过这种方式去引入不同的实现类,那么在之后想切换实现类,依然非常的麻烦。需要修改每一个变量的名称。

遇事不决,抽一层。

有什么方法可以让我们不用去代码里操作,就可以更改使用的oss服务呢?或许我们可以在配置文件中实现。

首先,实现类均继承 StorageService ,这个毋庸置疑。

我们现在要做的,就是再抽一层,使其可以根据我们配置文件中的变量,去找到对应的实现类进行方法的调用。

那么这就需要写一个配置类了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
public class StorageConfig {

@Value("${storage.service.type}")
private String storageType;

@Resource
private StorageService aliyunStorageService;

@Resource
private StorageService minioStorageService;

@Bean
public StorageService storageService() {
if("aliyun".equals(storageType)){
return aliyunStorageService;
}else if("minio".equals(storageType)){
return minioStorageService;
}else{
throw new IllegalArgumentException("未找到对应的文件存储处理服务类型");
}
}
}

这里我们将实现类的进行依赖注入,再通过 @Bean注解以及配置文件中的value,去给需要注入 StorageService 依赖的其他类,根据配置文件中的值,返回对应的StorageService 实现类。

1
2
3
storage:
service:
type: minio

接下来,咱们抽一层FileService,让外部调用这个类,这个类即是沟通外部调用以及刚刚实现的StorageService之间的桥梁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class FileService {

private final StorageService storageService;

public FileService(StorageService storageService) {
this.storageService = storageService;
}

/**
* 列出所有桶
*/
public List<String> listBuckets(){
return storageService.listBuckets();
}
}

构造器注入是一种常见的依赖注入方式。在FileService类中,StorageService是通过构造器注入的。 当Spring创建FileService的实例时,它会查找一个可以用来注入的StorageService实例。

这个实例是由StorageConfig类中的storageService()方法提供的,该方法根据配置的存储类型返回相应的StorageService实例。

我们就可以直接用FileService在外部进行方法调用了。

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class FileController {

@Resource
private FileService fileService;

@RequestMapping("/testGetAllBuckets")
public String testGetAllBuckets() throws Exception {
List<String> allBuckets = fileService.listBuckets();
return allBuckets.get(0);
}
}

这还算不上适配器模式,接下来我们进一步改进,实现简单的适配器模式

进一步改进

适配器模式是一种设计模式,它允许接口不兼容的对象能够一起工作。这种模式通常用来将一个类的接口转换成另一个客户端期望的接口。

适配器模式主要包括以下三个角色:

  1. 目标(Target):这是客户端期望的接口。客户端通过这个接口与应用的其他对象交互。

  2. 被适配者(Adaptee):这是需要适配的类。它定义了一个已存在的接口,这个接口需要适配。

  3. 适配器(Adapter):这是适配器模式的核心。适配器实现了目标接口,并在内部维护一个被适配者的实例。适配器将客户端的请求转发给被适配者。

适配器模式的主要目的是使得原本由于接口不兼容而不能一起工作的类可以一起工作。

StorageAdapter接口就是目标接口,而AliyunStorageAdapterMinioStorageAdapter类就是适配器,它们实现了StorageAdapter接口,并提供了与StorageAdapter接口兼容的方法。

FileService类中,使用了StorageAdapter类型的字段,而不是直接使用AliyunStorageAdapterMinioStorageAdapter

这样,无论实际的存储服务是阿里云还是Minio,FileService都可以通过相同的接口与它们交互,这就是适配器模式的应用。

具体的实现方式如下:

定义一个目标接口StorageAdapter,这个接口定义了所有存储服务都应该实现的方法。

1
2
3
4
5
6
7
8
9
10
11
/**
* 存储适配器
*/
public interface StorageAdapter {

/**
* 列出所有桶
*/
List<String> listBuckets();
......
}

创建适配器类AliyunStorageAdapterMinioStorageAdapter,这两个类实现了StorageAdapter接口,并提供了具体的实现。

1
2
3
4
5
6
7
public class AliyunStorageAdapter implements StorageAdapter {
// ...
}

public class MinioStorageAdapter implements StorageAdapter {
// ...
}

在FileService类中,使用StorageAdapter类型的字段,而不是具体的AliyunStorageAdapterMinioStorageAdapter。这样,无论实际的存储服务是什么,FileService都可以通过相同的接口与它们交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class FileService {

private final StorageAdapter storageAdapter;

public FileService(StorageAdapter storageAdapter) {
this.storageAdapter = storageAdapter;
}

/**
* 列出所有桶
*/
public List<String> listBuckets(){
return storageAdapter.listBuckets();
}
}

StorageConfig类中,根据配置的存储类型,创建相应的StorageAdapter实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class StorageConfig {

@Value("${storage.service.type}")
private String storageType;

@Bean
public StorageAdapter storageAdapter() {
if("aliyun".equals(storageType)){
return new AliyunStorageAdapter();
}else if("minio".equals(storageType)){
return new MinioStorageAdapter();
}else{
throw new IllegalArgumentException("未找到对应的文件存储处理服务类型");
}
}
}