聚合平台项目优化(门面模式,适配器模式,注册器模式)

前言:

这篇文章的思路就是抛出问题,再思考解决方案,最后利用设计模式解决问题

项目背景:

聚合搜索平台的主要功能就是一个有强大搜索能力的一个项目

用户输入一个词,同时可以搜索出用户,文章和图片这种功能

门面模式:

然后现在的情况是:

用户输入一个词,我们是将三个部分全都搜索出来

但是我们怎么样能让前端又能一次搜出所有数据、又能够分别获取某一类数据(比如分页场景)。

我们的解决办法就是让前端传一个参数:type

枚举type:比如有user用户,post文章,picture文章

这就引出了这篇文章的第一个设计模式:门面模式

门面模式(Facade Pattern)是一种结构性设计模式,它为复杂子系统提供一个简化的接口。通过创建一个门面类,门面模式可以隐藏系统的复杂性,使得外部调用变得更简单。

主要特点
  1. 简化接口:门面模式通过搭建一个统一的接口来简化对底层复杂系统的访问。
  2. 降低耦合:它减少了客户端与复杂系统之间的依赖性,客户端只需要与门面交互,而不需要直接与内部组件进行交互。
  3. 提高可维护性:由于系统内部的细节被封装在门面类中,后期对系统进行修改时,客户代码无需修改。

用户无需去理会后端业务逻辑有多复杂,只需要知道自己需要获取那一种的信息

这也有点像微服务项目的网关

用户不需要知道那么多服务器的ip地址

只需要把自己的请求发送给网关即可。

我们来看代码:

具体代码实现:

首先我们需要有一个SearchController来接收请求

还要有一个dto和vo用来接收请求参数和作为参数返回给前端。

还有一个状态的枚举

@Data
public class SearchRequest implements Serializable {


    /**
     * 查询
     */
    private String searchText;
    /**
     * 查询接口的类型
     */
    private String type;
    private static final long serialVersionUID = 1L;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SearchVO implements Serializable {
    private List<UserVO> userList;
    private List<PostVO> postList;
    private List<Picture> pictureList;
    private static final long serialVersionUID = 1L;
}
/**
 * 用户角色枚举
 *
 * @author <a href="https://github.com/liyupi">程序员鱼皮</a>
 * @from <a href="https://yupi.icu">编程导航知识星球</a>
 */
public enum SearchTypeEnum {

    POST("帖子","post"),
    USER("帖子","user"),
    PICTURE("帖子","picture");

    private final String text;

    private final String value;

    SearchTypeEnum(String text, String value) {
        this.text = text;
        this.value = value;
    }

    /**
     * 获取值列表
     *
     * @return
     */
    public static List<String> getValues() {
        return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
    }

    /**
     * 根据 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;
    }

    public String getValue() {
        return value;
    }

    public String getText() {
        return text;
    }
}

 状态的枚举里面就三个参数:

user对应用户   post对应文章   picture对应图片

然后具体看controller层的代码:

@Component
@Slf4j
@RequiredArgsConstructor
public class SearchFacade {

    private final UserService userService;
    private final PostService postService;
    private final PictureService pictureService;

    public SearchVO searchAll(@RequestBody SearchRequest searchRequest, HttpServletRequest request){
        String type = searchRequest.getType();
        SearchTypeEnum searchTypeEnum = SearchTypeEnum.getEnumByValue(type);
        final String value = searchTypeEnum.getValue();
        ThrowUtils.throwIf(StringUtils.isBlank(value), ErrorCode.PARAMS_ERROR);//传入的值是非法的
        SearchVO searchVO = new SearchVO();
        final String searchText = searchRequest.getSearchText();
        if(type==null){
            UserQueryRequest queryRequest = new UserQueryRequest();
            queryRequest.setUserName(searchText);
            final Page<UserVO> userVOPage = userService.listUserVOByPage(queryRequest);
            PostQueryRequest postQueryRequest = new PostQueryRequest();
            postQueryRequest.setSearchText(searchText);
            final Page<PostVO> postVOPage = postService.listPostVOByPage(postQueryRequest,request);
            final Page<Picture> picturePage = pictureService.SearchPicture(searchText, 1, 10);
            searchVO.setUserList(userVOPage.getRecords());
            searchVO.setPostList(postVOPage.getRecords());
            searchVO.setPictureList(picturePage.getRecords());
        }else {
            switch (searchTypeEnum) {
                case POST -> {
                    PostQueryRequest postQueryRequest = new PostQueryRequest();
                    postQueryRequest.setSearchText(searchText);
                    final Page<PostVO> postVOPage = postService.listPostVOByPage(postQueryRequest,request);
                    searchVO.setPostList(postVOPage.getRecords());
                    break;
                }
                case USER -> {
                    UserQueryRequest queryRequest = new UserQueryRequest();
                    queryRequest.setUserName(searchText);
                    final Page<UserVO> userVOPage = userService.listUserVOByPage(queryRequest);
                    searchVO.setUserList(userVOPage.getRecords());
                    break;
                }
                case PICTURE -> {
                    final Page<Picture> picturePage = pictureService.SearchPicture(searchText, 1, 10);
                    searchVO.setPictureList(picturePage.getRecords());
                    break;
                }
            }
        }
        return searchVO;
    }
}

整体的代码逻辑其实很简单:

首先就是先把参数取出来进行判断

如果传入的type不合法(就不是枚举中提到的三种)直接抛异常

然后如果是type为空,就查询全部列表

就把SearchVO里面的三个变量

private List<UserVO> userList;
private List<PostVO> postList;
private List<Picture> pictureList;

都填充上

然后如果不是,我们就用switch来判断你是想要对应哪一个service来查询

整体逻辑很简单。

适配器模式:

上面的逻辑已经可以解决问题了,不过这一篇文章是优化

所以我们还是有优化点的

我们首先还是抛出问题

上面这段代码的问题:

  1. 很冗余(重复的逻辑写了很多)
  2. 不易于扩展(如果还有其它的搜索要求,比如视频,公众号这种,想要添加进来需要再继续写逻辑(堆屎山))
  3. 如果有其它的服务想要接入这个服务非常的麻烦,怎么说这个麻烦呢,首先我们自己的每个service中的方法都千奇百怪的(不过这个项目还好)如果再加入其它服务,就很乱

综上所诉,我们的解决办法就是需要一个标准,就是每个人接口都按照我这个标准给我东西,然后你才能接入。

这就涉及到了适配器模式:

适配器模式(Adapter Pattern)是一种结构性设计模式,旨在解决不兼容接口之间的交互问题。适配器模式通过创建一个适配器类,使得原本由于接口不兼容而无法一起工作的类可以协同工作。

可以理解为生活中的转换头。

主要特点
  1. 接口转换:适配器模式允许将一个类的接口转换成客户端所期望的另一种接口。
  2. 解耦:客户端和被适配者之间的耦合度降低,客户端无需了解被适配者的具体实现。
  3. 增强系统的灵活性:通过适配器,系统可以灵活地引入新的类,而无需改动已有的代码。
适用场景
  • 当你想使用一些现有的类,而它们的接口不符合你的要求时。
  • 你想创建一个可复用的类,该类可以与其他不相关的类一起工作。
  • 系统需要与一些接口不兼容的类进行交互时。

我们的具体实现就是需要一个接口:

public interface DataSource<T> {
    public Page<T> doSearch(String searchText,long pageNum,long pageSize);
}

里面有一个方法doSearch方法

里面有三个参数:搜索内容,分页号,分页大小

项目中的每一个Service都需要实现这个接口,都需要向我传入这三个参数

这就是一个标准

我们根据上面的枚举,我们需要三个Service来实现这个接口

我们来看具体的代码实现:

@Service
@Slf4j
public class UserDataSource implements DataSource {

    @Autowired
    private UserService userService;


    @Override
    public Page doSearch(String searchText, long pageNum, long pageSize) {
        UserQueryRequest userQueryRequest = new UserQueryRequest();
        userQueryRequest.setUserName(searchText);
        userQueryRequest.setPageSize((int) pageSize);
        userQueryRequest.setCurrent((int) pageNum);
        Page<UserVO> userVOPage = userService.listUserVOByPage(userQueryRequest);
        return userVOPage;
    }
}

这里也有一个小技巧

我们这里的doSearch需要三个参数

但是UserService中查询用户的接口的参数是userQueryRequest

这和doSearch需要的参数差的有点大

所以我们可以在这个UserDataSource 里面调用UserService的listUserVOByPage方法,然后我们传入我们需要的参数即可。

@Service
@Slf4j
public class PostDataSource implements DataSource {

    @Autowired
    private PostService postService;

    @Autowired
    private HttpServletRequest request;

    @Override
    public Page doSearch(String searchText, long pageNum, long pageSize) {
        PostQueryRequest postQueryRequest = new PostQueryRequest();
        postQueryRequest.setSearchText(searchText);
        postQueryRequest.setPageSize((int) pageSize);
        postQueryRequest.setCurrent((int) pageNum);
        //todo HttpRequest的这个参数值是null
        Page<PostVO> postVOPage = postService.listPostVOByPage(postQueryRequest, request);
        return postVOPage;
    }
}

这里也有注意点,PostService的listPostVOByPage需要一个参数:HttpServletRequest request

(这个时候需要做出选择,看能不能通过其它办法解决,不能的话就需要考虑是否要把这个方法接入)

但是我们这里可以解决,就是在这个PostDataSource 中引入HttpServletRequest

这里就涉及到一个点:就是如何在不是controller层拿到request请求

这里有两种方法:

一种是直接依赖注入

    @Autowired
    private HttpServletRequest request;

还有一种是通过一个RequestContextHolder

ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attrs.getRequest();
@Service
public class PictureDataSource implements DataSource{
    @Override
    public Page doSearch(String searchText, long pageNum, long pageSize) {
        String url = String.format("https://cn.bing.com/images/search?q=%s&form=HDRSC2&first=%s",searchText,pageSize);
        Document doc = null;
        try {
            doc = Jsoup.connect(url).get();
        } catch (IOException e) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        Elements elements = doc.select(".iusc");
        final Elements elements1 = doc.select("a.inflnk");
        List<Picture> pictureList = new ArrayList<>();
        for (int i = 0; i < elements.size(); i++) {
            //获取图片
            final String m = elements.get(i).attr("m");
            Map<String,Object> map = JSONUtil.toBean(m,Map.class);
            final String murl = (String) map.get("murl");
            //获取标题
            final String title = elements1.get(i).attr("aria-label");
            Picture picture = new Picture();
            picture.setMurl(murl);
            picture.setTitle(title);
            pictureList.add(picture);
            if(pictureList.size()>=pageSize){
                break;
            }
        }
        Page<Picture> picturePage= new Page<>(pageNum,pageSize);
        picturePage.setRecords(pictureList);
        return picturePage;
    }
}

经过这个适配器模式之后else中的代码:

else {
            Map<String, DataSource> dataSourceMap = new HashMap<>();
            dataSourceMap.put("post",postDataSource);
            dataSourceMap.put("user",userDataSource);
            dataSourceMap.put("picture",pictureDataSource);
            DataSource dataSource = dataSourceMap.get(type);
            final Page<?> page = dataSource.doSearch(searchText, 1, 10);
            final List<?> records = page.getRecords();
            searchVO.setDatasourceList(page.getRecords());
        }

这里用了一个map来记录不同的适配器实现类。

注册模式:

注册模式:首先注册模式是单例模式的一个扩展

上面的代码还可以抽象:

我们根据上面写的map,创造一个datasourceregistry,进行初始化map,并且用@PostConstrucet注解来在所有的bean注册之后执行(因为map中的元素<String,Bean对象>)

@Component
@Slf4j
@RequiredArgsConstructor
public class DataSourceRegistry {

    private final UserDataSource userDataSource;
    private final PostDataSource postDataSource;
    private final PictureDataSource pictureDataSource;

    private Map<String,DataSource> dataSourceMap = new HashMap<>();

    @PostConstruct
    private void initMap(){
        dataSourceMap.put("post",postDataSource);
        dataSourceMap.put("user",userDataSource);
        dataSourceMap.put("picture",pictureDataSource);
    }

    public DataSource getDataSource(String type){
        return dataSourceMap.get(type);
    }
}
这里还用到了一个@PostConstruct注解

为什么呢

我们仔细看代码

这里的map收集的都是bean对象,所以我们肯定要先注册bean对象然后才能存到map里面去

@PostConstruct的注解作用就是这个:在bean对象注册完之后再执行。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值