设计模式荟萃
学习目标
在这里,你将系统学习了解设计模式荟萃
的相关内容
我们将以最简单直接
的方式为您呈现内容!
# 🍚 门面模式
我们选择门面模式的原因主要有以下几点:
简化了客户端与系统之间的交互,客户端只需要与门面交互,而不需要了解系统内部的复杂性。
降低了系统的耦合度,使系统更容易维护和修改。
提高了代码的可读性和可维护性,因为客户端代码更简洁清晰。
门面模式允许客户端通过一个统一的接口与系统交互,而不需要了解系统内部的复杂性,降低客户端与系统之间的耦合度
,使系统更易于维护、扩展和修改。
# 需求场景
- 通过适配器模式,我们有效地解耦了数据源的具体实现细节,只需关注统一的接口,即可实现对不同数据源的搜索请求的统一处理。
/**
* @author 邓哈哈
* 2023/8/31 16:35
* Function:
* Version 1.0
*/
@Service
public class SearchServiceImpl implements SearchService {
@Resource
private PostDataSource postDataSource;
@Resource
private PictureDataSource pictureDataSource;
@Resource
private ArticleDataSource articleDataSource;
@Override
public List<String> searchSuggestFromEs(String suggestText) {
postDataSource.search(searchText, pageSize, current));
articleDataSource.search(searchText, pageSize, current));
pictureDataSource.search(searchText, pageSize, current));
.............................
}
}
- 为了满足用户对聚合搜索的需求,我们希望提供一个
统一的接口
,该接口能够同时调用诗词搜索
、博文搜索
和图片搜索
功能。为了实现这一目标,我们决定采用门面模式
进行封装。
# 解决方案
# 定义统一接口
- 在
SearchService
中,定义聚合搜索统一接口searchAll
,对外提供聚合搜索服务:
/**
* 聚合搜索服务
*
* @param searchQueryRequest 聚合搜索词条
* @param request request
* @return 搜索结果
*/
SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request);
# 聚合搜索结果
-我们设计SearchVO
类,主要职责是存储聚合搜索的结果,并将结果返回给前端
@Data
public class SearchVO implements Serializable {
/**
* 博文搜索列表
*/
private List<ArticleVO> articleList;
/**
* 诗词搜索列表
*/
private List<PostVO> postVOList;
/**
* 图片搜索列表
*/
private List<Picture> pictureList;
private static final long serialVersionUID = 1L;
}
# 数据源分离
- 通过适配器模式,我们已经将不同的数据源:
Post
、Article
和Picture
独立出来,每个数据源都有自己的搜索实现。详见:适配器模式
# 搜索请求转发
- 在 searchSuggestFromEs 方法中,根据传入的 suggestText,搜索请求被分发到不同的数据源进行搜索,这里,postDataSource.search(), articleDataSource.search(), 和 pictureDataSource.search() 方法分别处理来自不同数据源的搜索请求。
@Override
public SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request) {
// 校验参数正确性
ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);
// 获取搜索词条
String searchText = searchQueryRequest.getSearchText();
long pageSize = searchQueryRequest.getPageSize();
long current = searchQueryRequest.getPageNum();
// 诗词搜索
CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() ->
postDataSource.search(searchText, pageSize, current));
// 博文搜索
CompletableFuture<Page<ArticleVO>> articleTask = CompletableFuture.supplyAsync(() ->
articleDataSource.search(searchText, pageSize, current));
// 图片搜索
CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() ->
pictureDataSource.search(searchText, pageSize, current));
// 组合异步任务结果
CompletableFuture.allOf(postTask, pictureTask, articleTask).join();
// 整合不同数据源的搜索结果
try {
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
Page<ArticleVO> articlePage = articleTask.get();
searchVO = new SearchVO();
searchVO.setPostVOList(postVOPage.getRecords());
searchVO.setPictureList(picturePage.getRecords());
searchVO.setArticleList(articlePage.getRecords());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
// 返回聚合搜索结果
return searchVO;
}
# 提供客户端搜索服务
- 设计
SearchController
,接收客户端发来的搜索请求参数,调用SearchService
的searchAll
方法处理搜索请求,将返回的SearchVO
对象封装为成功的响应结果并返回给客户端
@RestController
@RequestMapping("/search")
@Slf4j
public class SearchController {
@Resource
private SearchService searchService;
@PostMapping("/all")
public BaseResponse<SearchVO> searchAll(@RequestBody SearchQueryRequest searchQueryRequest,
HttpServletRequest request) throws IOException, ExecutionException, InterruptedException {
// controller层对参数的校验
SearchVO searchVO = searchService.searchAll(searchQueryRequest, request);
return ResultUtils.success(searchVO);
}
}
至此,我们成功运用门面模式构建了
统一接口
,该接口能够整合诗词搜索
、博文搜索
以及图片搜索
等多种功能。这一设计确保了客户端能够通过单一的接口调用,便捷地获取所需的信息,极大地提升了
用户体验
和系统效率
。
# 🥗 适配器模式
我们选择适配器模式的原因主要有以下几点:
解耦
:适配器模式有助于解耦不兼容的接口,使得不同的系统或组件之间可以更灵活地协同工作,减少因接口不匹配而导致的问题。提高复用性
:通过适配器模式,我们可以将一些不兼容的类组合成新的功能,从而提高代码的复用性。扩展性
:适配器模式使得系统更加模块化,便于后续的扩展和维护。当有新的接口或组件需要加入时,只需适配新的接口,而无需修改现有代码。灵活性
:通过适配器模式,我们可以轻松地在运行时切换不同的适配器实现,以适应不同的场景和需求。
# 需求场景
在具体实施前,我们需要深入分析当前的业务逻辑
和设计痛点
,明确需要适配的范围和目标,精确应用适配器模式。
# 接口不一致
我们对外分别提供诗词搜索、博文搜索和图片搜索功能,而这三者的实现的请求接口是不一致的
在
PostService
下,我们编写了searchFromEs
方法进行诗词数据访问,实现了诗词搜索功能:
/**
* 从 ES 查询
*
* @param postQueryRequest 诗词搜索请求参数
* @return 诗词列表
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
- 在
ArticleSercive
下,我们编写了searchFromEs
方法进行博文数据访问,实现了博文搜索功能:
/**
* 从 ES 查询
*
* @param articleQueryRequest 博文查询请求参数
* @return 博文列表
*/
Page<ArticleVO> searchFromEs(ArticleQueryRequest articleQueryRequest);
- 在
PictureService
下,我们编写了searchFromEs
方法进行诗词数据访问,实现了图片搜索功能:
/**
* 图片搜索
*
* @param searchText 搜索关键词
* @param pageSize 每页容量
* @param currentPage 当前页码
* @return 图片列表
*/
Page<Picture> listPictureVOByPage(String searchText, long pageSize, long currentPage);
# 问题分析
显然,目前存在的方法在实现
请求参数
上存在不一致性
,这无疑增加了维护的复杂性
。为了提高代码的可维护性和可扩展性,我们需要在
不修改
现有方法的基础上,实现这三个接口调用的统一化
。因此,考虑使用
适配器模式
来优化当前的实现方式,以解决这种不一致性问题。
# 解决方案
# 设计目标接口
/**
* 适配器 目标值
*/
public interface DataSource<T> {
/**
* 统一查询
*
* @param searText 搜索词条
* @param pageSize 每页数
* @param current 当前页
* @return Page
*/
Page<T> search(String searText, long pageSize, long current) throws IOException;
}
# 确定适配者
- 为了提升系统的
兼容性
和可扩展性
,我们将针对PostService
、ArticleService
和PictureService
这三个服务进行适配实现。
# 实现目标接口
- 新增
PostDataSource
类,该类将完全实现DataSource
接口,以提供诗词搜索
的适配实现。
/**
* 诗词搜索 适配实现
*/
@Service
public class PostDataSource implements DataSource<PostVO> {
@Resource
private PostService postService;
@Override
public Page<PostVO> search(String searText, long pageSize, long current) {
PostQueryRequest postQueryRequest = new PostQueryRequest();
postQueryRequest.setSearchText(searText);
postQueryRequest.setPageSize(pageSize);
postQueryRequest.setPageNum(current);
Page<Post> postPage = postService.searchFromEs(postQueryRequest);
return postService.getPostVOPage(postPage, null);
}
}
- 新增
ArticleDataSource
类,该类将完全实现DataSource
接口,以提供博文搜索
的适配实现。
/**
* 博文搜索 适配实现
*/
@Service
public class ArticleDataSource implements DataSource<ArticleVO> {
@Resource
private ArticleService articleService;
@Override
public Page<ArticleVO> search(String searText, long pageSize, long current) {
ArticleQueryRequest articleQueryRequest = new ArticleQueryRequest();
articleQueryRequest.setSearchText(searText);
articleQueryRequest.setPageSize(pageSize);
articleQueryRequest.setPageNum(current);
return articleService.searchFromEs(articleQueryRequest);
}
}
- 新增
PictureDataSource
类,该类将完全实现DataSource
接口,以提供图片搜索
的适配实现。
/**
* 图片搜索 适配实现
*/
@Service
public class PictureDataSource implements DataSource<Picture> {
@Resource
private PictureService pictureService;
@Override
public Page<Picture> search(String searText, long pageSize, long current) {
return pictureService.listPictureVOByPage(searText, pageSize, current);
}
}
# 实现统一调用
- 通过适配器模式,我们无需关心具体的数据源
实现细节
,只需调用统一的接口即可。至此,我们已经能够统一处理
来自不同数据源
的搜索请求了:
/**
* @author 邓哈哈
* 2023/8/31 16:35
* Function:
* Version 1.0
*/
@Service
public class SearchServiceImpl implements SearchService {
@Resource
private PostDataSource postDataSource;
@Resource
private PictureDataSource pictureDataSource;
@Resource
private ArticleDataSource articleDataSource;
@Override
public List<String> searchSuggestFromEs(String suggestText) {
postDataSource.search(searchText, pageSize, current));
articleDataSource.search(searchText, pageSize, current));
pictureDataSource.search(searchText, pageSize, current));
.............................
}
}
- 通过专业的适配工作,我们能够确保这些服务与其他使用相同接口的系统或组件
无缝对接
,构建了一个更加健壮
和灵活
的系统架构。
# 🍜 注册器模式
我们选择注册器模式的原因主要有以下几点:
动态管理
:注册器模式允许在运行时动态地添加、删除和查找组件,而无需修改现有代码或重启应用程序。灵活性
:通过使用注册器模式,我们可以轻松地添加或删除组件,从而轻松地扩展应用程序的功能。集中管理
:注册器模式提供了一个集中的地方来管理组件,使得查找、配置和使用组件变得更加简单。简化组件交互
:注册器模式简化了组件之间的交互,使得组件之间的通信更加清晰和一致。易于测试和维护
:由于组件之间的耦合度较低,这使得组件更容易测试和维护。
通过使用注册器模式,应用程序可以在运行时动态地添加或删除组件,而无需修改现有代码。此外,由于组件之间的耦合度较低,这使得组件更容易测试和维护。
# 需求场景
- 我们已成功地运用门面模式构建了一个统一的接口, 整合了
诗词搜索
、博文搜索
以及图片搜索
,这一设计确保了客户端能够通过单一的接口调用,便捷地获取所需的信息,极大地提升了用户体验和系统效率。
@RestController
@RequestMapping("/search")
@Slf4j
public class SearchController {
@Resource
private SearchService searchService;
@PostMapping("/all")
public BaseResponse<SearchVO> searchAll(@RequestBody SearchQueryRequest searchQueryRequest,
HttpServletRequest request) throws IOException, ExecutionException, InterruptedException {
// controller层对参数的校验
SearchVO searchVO = searchService.searchAll(searchQueryRequest, request);
return ResultUtils.success(searchVO);
}
}
- 然而,随着
业务需求
的变化,用户调用聚合搜索接口的参数也发生了相应的调整。为了更好地满足用户需求,我们根据前端发来的搜索关键词
,动态
地调用相应的搜索接口,确保准确性
和高效性
。
# 解决方案
在新需求中,我们支持客户端通过传输
搜索类型字段
,精确选择执行特定类型的聚合搜索。为了实现这一目标,我们选择使用
注册器模式
,在服务端,我们通过分析客户端传输的搜索类型,动态切换
到指定类型的聚合搜索服务,从而实现精确的聚合搜索。
# 搜索类型枚举
- 新增搜索枚举类型
SearchTypeEnum
,实现搜索类型枚举(构造器和 getter、setter 方法省略)
/**
* 搜索类型枚举
*/
public enum SearchTypeEnum {
POST("诗词", "post"),
ARTICLE("博文", "article"),
PICTURE("图片", "picture");
private final String text;
private final String value;
/**
* 根据 value 获取枚举
*
* @param value
* @return
*/
public static SearchTypeEnum getEnumByValue(String value) {
if (ObjectUtils.isEmpty(value)) {
return null;
}
for (SearchTypeEnum anEnum : SearchTypeEnum.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
}
# 数据源注入
- 创建注册器类
DataSourceRegistry
,用于管理注册的对象,支持根据给定的搜索类型
来获取相应的数据源
/**
* 数据源注册器
*/
@Component
public class DataSourceRegistry {
// 诗词数据源
@Resource
private PostDataSource postDataSource;
// 图片数据源
@Resource
private PictureDataSource pictureDataSource;
// 博文数据源
@Resource
private ArticleDataSource articleDataSource;
// 关联搜索类型和数据源
private Map<String, DataSource<T>> typeDataSourceMap;
@PostConstruct
public void doInit() {
System.out.println(1);
typeDataSourceMap = new HashMap() {{
put(SearchTypeEnum.POST.getValue(), postDataSource);
put(SearchTypeEnum.PICTURE.getValue(), pictureDataSource);
put(SearchTypeEnum.ARTICLE.getValue(), articleDataSource);
}};
}
..............................
}
# 支持获取数据源
- 接收搜索类型参数,通过
注册器模式
,在注册器中选择合适的数据源,执行精确
的搜索操作
/**
* 获取搜索接口
*
* @param type 搜索类型
* @return 对应数据源
*/
public DataSource getDataSourceByType(String type) {
if (typeDataSourceMap == null) {
return null;
}
return typeDataSourceMap.get(type);
}
# 全新聚合搜索结果
- 我们更新
SearchVO
类,主要职责是存储聚合搜索的结果,并将结果返回给前端
@Data
public class SearchVO implements Serializable {
/**
* 博文搜索列表
*/
private List<ArticleVO> articleList;
/**
* 诗词搜索列表
*/
private List<PostVO> postVOList;
/**
* 图片搜索列表
*/
private List<Picture> pictureList;
/**
* 聚合搜索结果列表
*/
private List<?> dataList;
private static final long serialVersionUID = 1L;
}
# 全新客户端搜索服务
- 在
SearchController
中,接收客户端发来的搜索请求参数,分析客户端传输的搜索类型,动态切换
到指定类型的聚合搜索服务,实现精确的聚合搜索。
/**
* 聚合搜索服务
*
* @param searchQueryRequest 聚合搜索词条
* @param request request
* @return 搜索结果
*/
@Override
public SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request) {
// 检查type类型
String type = searchQueryRequest.getType();
// 校验参数正确性
ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);
// 获取页面类型
SearchTypeEnum enumByValue = SearchTypeEnum.getEnumByValue(type);
String searchText = searchQueryRequest.getSearchText();
long pageSize = searchQueryRequest.getPageSize();
long current = searchQueryRequest.getPageNum();
SearchVO searchVO = null;
// 执行查询全部数据
if (enumByValue == null) {
// 诗词搜索
CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() ->
postDataSource.search(searchText, pageSize, current));
// 博文搜索
CompletableFuture<Page<ArticleVO>> articleTask = CompletableFuture.supplyAsync(() ->
articleDataSource.search(searchText, pageSize, current));
// 图片搜索
CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() ->
pictureDataSource.search(searchText, pageSize, current));
CompletableFuture.allOf(postTask, pictureTask, articleTask).join();
try {
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
Page<ArticleVO> articlePage = articleTask.get();
searchVO = new SearchVO();
searchVO.setPostVOList(postVOPage.getRecords());
searchVO.setPictureList(picturePage.getRecords());
searchVO.setArticleList(articlePage.getRecords());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
// 执行指定搜索接口
} else {
// 分类查询
searchVO = new SearchVO();
// 获取数据源
DataSource<?> dataSourceByType = dataSourceRegistry.getDataSourceByType(type);
try {
Page<?> page = dataSourceByType.search(searchText, pageSize, current);
searchVO.setDataList(page.getRecords());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 返回聚合搜索结果
return searchVO;
}
通过运用
注册器模式
,我们成功地实现了客户端能够通过传输搜索类型字段
,精确选择并执行``特定类型的聚合搜索。这一改进使我们的系统展现出更高的
灵活性
和可扩展性
,从而更好地适应不断变化的业务需求。