zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大后端课程视频归档
  • 南航面试题
  • 并发编程

  • 性能调优

  • java8语法

  • lombok

  • 日志

  • 工具类

  • spring

  • mybatis

  • springboot

  • redis

  • zookeeper

  • springcloud

  • dubbo

  • netty

  • springsecurity

  • mq消息中间件

  • shiro

  • beetle

  • 模板引擎

  • jpa

  • 数据结构与算法

  • 数据库知识与设计

  • gradle

  • maven

  • bus

  • 定时任务

  • docker

  • centos

  • 加解密

  • biz业务

    • 公共业务

      • 单例模式
      • Untitled
      • 接口与抽象类的应用(包括各自设计模式)
        • 抽象类的实际应用--模板设计
          • 1、定义抽象类,作为模板,其也可以实现接口
          • 2、子类实现具体的功能
          • 3、调用抽象类或者接口
        • 接口作用
          • 1、feign远程接口
          • 2、业务异常接口
          • 3、有需要扩展的时候,优选考虑接口设计
        • 抽象类作用
          • 1、封装模板方法,模板方法模式中常用
          • 2、封装核心算法在抽象类,子类只需实现其特定的逻辑即可
      • SpringBoot使用ApplicationEvent&Listener完成业务解耦
      • 业务异常处理
      • ThreadLocal用法
      • 自定义RequestMappingHandlerMapping实现程序版本控制
    • 商城系统

    • 许愿池

    • 微信公众号

    • 企业微信

    • 分销返利系统

  • pigx项目

  • 开源项目

  • 品达通用权限项目-黑马

  • 货币交易项目coin-尚学堂

  • php

  • backend
  • biz业务
  • 公共业务
shollin
2021-06-21
目录

接口与抽象类的应用(包括各自设计模式)

# 抽象类的实际应用--模板设计

假设人分学生和工人,学生和工人都可以说话,但是学生和工人说的话内容不一样,也就是说

说话这个功能应该是具体功能,而说话的内容就要由学生或工人来决定了。所以此时可以使用抽象类实现这种场景。

  • 优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

  • **缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

  • 使用场景:1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

  • 注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

# 1、定义抽象类,作为模板,其也可以实现接口

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   //模板
   public final void play(){
 
      //初始化游戏
      initialize();
 
      //开始游戏
      startPlay();
 
      //结束游戏
      endPlay();
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2、子类实现具体的功能

public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}

public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Football Game Started. Enjoy the game!");
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 3、调用抽象类或者接口

public class TemplatePatternDemo {
   public static void main(String[] args) { 
      Game game = new Cricket();
      game.play();
      System.out.println();
      game = new Football();
      game.play();      
   }
}
1
2
3
4
5
6
7
8
9

# 接口作用

制定规范

# 1、feign远程接口

@FeignClient(
	value = AppConstant.APPLICATION_SYSTEM_NAME,
	fallback = IDictClientFallback.class
)
public interface IDictClient {

	String API_PREFIX = "/dict";

	/**
	 * 获取字典表对应值
	 *
	 * @param code    字典编号
	 * @param dictKey 字典序号
	 * @return
	 */
	@GetMapping(API_PREFIX + "/getValue")
	R<String> getValue(@RequestParam("code") String code, @RequestParam("dictKey") Integer dictKey);

	/**
	 * 获取字典表
	 *
	 * @param code 字典编号
	 * @return
	 */
	@GetMapping(API_PREFIX + "/getList")
	R<List<Dict>> getList(@RequestParam("code") String code);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * Feign失败配置
 *
 * @author Chill
 */
@Component
public class IDictClientFallback implements IDictClient {
	@Override
	public R<String> getValue(String code, Integer dictKey) {
		return R.fail("获取数据失败");
	}

	@Override
	public R<List<Dict>> getList(String code) {
		return R.fail("获取数据失败");
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 字典服务Feign实现类
 *
 * @author Chill
 */
@ApiIgnore
@RestController
@AllArgsConstructor
public class DictClient implements IDictClient {

	IDictService service;

	@Override
	@GetMapping(API_PREFIX + "/getValue")
	public R<String> getValue(String code, Integer dictKey) {
		return R.data(service.getValue(code, dictKey));
	}

	@Override
	@GetMapping(API_PREFIX + "/getList")
	public R<List<Dict>> getList(String code) {
		return R.data(service.getList(code));
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 2、业务异常接口

大部分项目都会用到,如Guns

# 3、有需要扩展的时候,优选考虑接口设计

如短信发送, 先定义一个接口,约定好参数。 具体的实现交给不同的sdk

/**
 * 短信发送服务
 *
 * @author fengshuonan
 * @date 2018-07-06-下午2:14
 */
public interface SmsSenderApi {

    /**
     * 发送短信
     * <p>
     * 如果是腾讯云,params要用LinkedHashMap,保证顺序
     *
     * @param phone        电话号码
     * @param templateCode 模板号码
     * @param params       模板里参数的集合
     * @author fengshuonan
     * @date 2018/7/6 下午2:32
     */
    void sendSms(String phone, String templateCode, Map<String, Object> params);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 阿里云短信发送服务
 *
 * @author fengshuonan
 * @date 2020/10/26 17:12
 */
@Slf4j
@RequiredArgsConstructor
public class AliyunSmsSender implements SmsSenderApi {

    private final MultiSignManager multiSignManager;

    private final AliyunSmsProperties aliyunSmsProperties;

    @Override
    public void sendSms(String phone, String templateCode, Map<String, Object> params) {

        log.info("开始发送阿里云短信,手机号是:" + phone + ",模板号是:" + templateCode + ",参数是:" + params);

        // 检验参数是否合法
        assertSendSmsParams(phone, templateCode, params, aliyunSmsProperties);

        // 初始化client profile
        IAcsClient iAcsClient = initClient();

        // 组装请求对象
        JSONObject smsRes = createSmsRequest(phone, templateCode, params, iAcsClient);

        // 如果返回ok则发送成功
        if (!AliyunSmsResultEnum.OK.getCode().equals(smsRes.getString("Code"))) {

            // 返回其他状态码根据情况抛出业务异常
            String code = AliyunSmsResultEnum.SYSTEM_ERROR.getCode();
            String errorMessage = AliyunSmsResultEnum.SYSTEM_ERROR.getMessage();
            for (AliyunSmsResultEnum smsExceptionEnum : AliyunSmsResultEnum.values()) {
                if (smsExceptionEnum.getCode().equals(smsRes.getString("Code"))) {
                    code = smsExceptionEnum.getCode();
                    errorMessage = smsExceptionEnum.getMessage();
                }
            }

            // 组装错误信息
            throw new SmsException(SmsExceptionEnum.ALIYUN_SMS_ERROR, code, errorMessage);
        }
    }

    /**
     * 初始化短信发送的客户端
     *
     * @author fengshuonan
     * @date 2018/7/6 下午3:57
     */
    private IAcsClient initClient() {
        final String accessKeyId = aliyunSmsProperties.getAccessKeyId();
        final String accessKeySecret = aliyunSmsProperties.getAccessKeySecret();

        // 创建DefaultAcsClient实例并初始化
        DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), accessKeyId, accessKeySecret);
        return new DefaultAcsClient(profile);
    }

    /**
     * 组装请求对象
     *
     * @author fengshuonan
     * @date 2018/7/6 下午3:00
     */
    private JSONObject createSmsRequest(String phoneNumber, String templateCode, Map<String, Object> params, IAcsClient acsClient) {
        CommonRequest request = new CommonRequest();
        request.setSysDomain(aliyunSmsProperties.getSmsDomain());
        request.setSysVersion(aliyunSmsProperties.getSmsVersion());
        request.setSysAction(aliyunSmsProperties.getSmsSendAction());

        // 接收短信的手机号码
        request.putQueryParameter("PhoneNumbers", phoneNumber);

        // 短信签名名称。请在控制台签名管理页面签名名称一列查看(必须是已添加、并通过审核的短信签名)。
        request.putQueryParameter("SignName", this.getSmsSign(phoneNumber));

        // 短信模板ID
        request.putQueryParameter("TemplateCode", templateCode);

        // 短信模板变量对应的实际值,JSON格式。
        request.putQueryParameter("TemplateParam", JSON.toJSONString(params));

        //请求失败这里会抛ClientException异常
        CommonResponse commonResponse;
        try {
            commonResponse = acsClient.getCommonResponse(request);
            String data = commonResponse.getData();
            String jsonResult = data.replaceAll("'\'", "");
            log.info("获取到发送短信的响应结果!{}", jsonResult);
            return JSON.parseObject(jsonResult);
        } catch (ClientException e) {
            log.error("初始化阿里云sms异常!可能是accessKey和secret错误!", e);

            // 组装错误信息
            throw new SmsException(SmsExceptionEnum.ALIYUN_SMS_KEY_ERROR, aliyunSmsProperties.getAccessKeyId());
        }
    }

    /**
     * 校验发送短信的参数是否正确
     *
     * @author fengshuonan
     * @date 2018/7/6 下午3:19
     */
    private void assertSendSmsParams(String phoneNumber, String templateCode, Map<String, Object> params,
                                     AliyunSmsProperties aliyunSmsProperties) {

        if (StrUtil.isBlank(phoneNumber)) {
            throw new SmsException(SEND_SMS_PARAM_NULL, "电话号码");
        }

        if (StrUtil.isBlank(templateCode)) {
            throw new SmsException(SEND_SMS_PARAM_NULL, "模板号templateCode");
        }

        if (ObjectUtil.isEmpty(params)) {
            throw new SmsException(SEND_SMS_PARAM_NULL, "模板参数");
        }

        if (ObjectUtil.isEmpty(aliyunSmsProperties)) {
            throw new SmsException(SEND_SMS_PARAM_NULL, "短信配置properties");
        }
    }

    /**
     * 获取sms发送的sign标识,参数phone是发送的手机号码
     *
     * @author stylefeng
     * @date 2018/8/13 21:23
     */
    private String getSmsSign(String phone) {
        String signName = aliyunSmsProperties.getSignName();

        // 如果是单个签名就用一个签名发
        if (!signName.contains(",")) {
            log.info("发送短信,签名为:" + signName + ",电话为:" + phone);
            return signName;
        } else {
            return multiSignManager.getSign(phone, signName);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

# 抽象类作用

封装业务

# 1、封装模板方法,模板方法模式中常用

// 以前版本是这种写法,新版的,见后面的接口--实现方式
public abstract class AbstractTreeBuildFactory<T> {
    public AbstractTreeBuildFactory() {
    }

    public List<T> doTreeBuild(List<T> nodes) {
        List<T> readyToBuild = this.beforeBuild(nodes);
        List<T> builded = this.executeBuilding(readyToBuild);
        return this.afterBuild(builded);
    }

    protected abstract List<T> beforeBuild(List<T> nodes);

    protected abstract List<T> executeBuilding(List<T> nodes);

    protected abstract List<T> afterBuild(List<T> nodes);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 树形节点的抽象接口
 *
 * @author fengshuonan
 * @date 2020/10/15 14:31
 */
public interface AbstractTreeNode<T> {

    /**
     * 获取节点id
     *
     * @return 节点的id标识
     * @author fengshuonan
     * @date 2020/10/15 15:28
     */
    String getNodeId();

    /**
     * 获取节点父id
     *
     * @return 父节点的id
     * @author fengshuonan
     * @date 2020/10/15 15:28
     */
    String getNodeParentId();

    /**
     * 设置children
     *
     * @param childrenNodes 设置节点的子节点
     * @author fengshuonan
     * @date 2020/10/15 15:28
     */
    void setChildrenNodes(List<T> childrenNodes);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 默认树节点的封装
 * <p>
 * 默认的根节点id是-1,名称是根节点
 *
 * @author fengshuonan
 * @date 2020/10/15 14:39
 */
@Data
public class DefaultTreeNode implements AbstractTreeNode<DefaultTreeNode> {

    /**
     * 节点id
     */
    private String id;

    /**
     * 父节点id
     */
    private String pId;

    /**
     * 节点名称
     */
    private String name;

    /**
     * 是否打开节点
     */
    private Boolean open;

    /**
     * 是否被选中
     */
    private Boolean checked = false;

    /**
     * 排序,越小越靠前
     */
    private BigDecimal sort;

    /**
     * 子节点
     */
    private List<DefaultTreeNode> children;

    @Override
    public String getNodeId() {
        return id;
    }

    @Override
    public String getNodeParentId() {
        return pId;
    }

    @Override
    public void setChildrenNodes(List<DefaultTreeNode> childrenNodes) {
        this.children = childrenNodes;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * 默认递归工具类,用于遍历有父子关系的节点,例如菜单树,字典树等等
 *
 * @author fengshuonan
 * @date 2018/7/25 下午5:59
 */
@Data
public class DefaultTreeBuildFactory<T extends AbstractTreeNode> implements AbstractTreeBuildFactory<T> {

    /**
     * 顶级节点的父节点id(默认-1)
     */
    private String rootParentId = "-1";

    public DefaultTreeBuildFactory() {

    }

    public DefaultTreeBuildFactory(String rootParentId) {
        this.rootParentId = rootParentId;
    }

    @Override
    public List<T> doTreeBuild(List<T> nodes) {

        // 将每个节点的构造一个子树
        for (T treeNode : nodes) {
            this.buildChildNodes(nodes, treeNode, new ArrayList<>());
        }

        // 只保留上级是根节点的节点,也就是只留下所有一级节点
        ArrayList<T> results = new ArrayList<>();
        for (T node : nodes) {
            if (node.getNodeParentId().equals(rootParentId)) {
                results.add(node);
            }
        }

        return results;
    }

    /**
     * 查询子节点的集合
     *
     * @param totalNodes     所有节点的集合
     * @param node           被查询节点的id
     * @param childNodeLists 被查询节点的子节点集合
     */
    private void buildChildNodes(List<T> totalNodes, T node, List<T> childNodeLists) {
        if (totalNodes == null || node == null) {
            return;
        }

        List<T> nodeSubLists = getSubChildsLevelOne(totalNodes, node);

        if (nodeSubLists.size() == 0) {

        } else {
            for (T nodeSubList : nodeSubLists) {
                buildChildNodes(totalNodes, nodeSubList, new ArrayList<>());
            }
        }

        childNodeLists.addAll(nodeSubLists);
        node.setChildrenNodes(childNodeLists);
    }

    /**
     * 获取子一级节点的集合
     *
     * @param list 所有节点的集合
     * @param node 被查询节点的model
     * @author fengshuonan
     */
    private List<T> getSubChildsLevelOne(List<T> list, T node) {
        List<T> nodeList = new ArrayList<>();
        for (T nodeItem : list) {
            if (nodeItem.getNodeParentId().equals(node.getNodeId())) {
                nodeList.add(nodeItem);
            }
        }
        return nodeList;
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * 树构建的抽象类,定义构建tree的基本步骤
 *
 * @author fengshuonan
 * @date 2018/7/25 下午5:59
 */
public interface AbstractTreeBuildFactory<T> {

    /**
     * 树节点构建整体过程
     *
     * @param nodes 被处理的节点集合
     * @return 被处理后的节点集合(带树形结构了)
     * @author fengshuonan
     * @date 2018/7/26 上午9:45
     */
    List<T> doTreeBuild(List<T> nodes);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2、封装核心算法在抽象类,子类只需实现其特定的逻辑即可

参考源码: guns BladeTool 对BaseController的封装

/**
 * 控制器查询结果的包装类基类
 *
 * @author fengshuonan
 * @date 2017年2月13日 下午10:49:36
 */
public abstract class BaseControllerWrapper {

    private Page<Map<String, Object>> page = null;

    private PageResult<Map<String, Object>> pageResult = null;

    private Map<String, Object> single = null;

    private List<Map<String, Object>> multi = null;

    public BaseControllerWrapper(Map<String, Object> single) {
        this.single = single;
    }

    public BaseControllerWrapper(List<Map<String, Object>> multi) {
        this.multi = multi;
    }

    public BaseControllerWrapper(Page<Map<String, Object>> page) {
        if (page != null && page.getRecords() != null) {
            this.page = page;
            this.multi = page.getRecords();
        }
    }

    public BaseControllerWrapper(PageResult<Map<String, Object>> pageResult) {
        if (pageResult != null && pageResult.getRows() != null) {
            this.pageResult = pageResult;
            this.multi = pageResult.getRows();
        }
    }

    @SuppressWarnings("unchecked")
    public <T> T wrap() {

        /**
         * 包装结果
         */
        if (single != null) {
            wrapTheMap(single);
        }
        if (multi != null) {
            for (Map<String, Object> map : multi) {
                wrapTheMap(map);
            }
        }

        /**
         * 根据请求的参数响应
         */
        if (page != null) {
            return (T) page;
        }
        if (pageResult != null) {
            return (T) pageResult;
        }
        if (single != null) {
            return (T) single;
        }
        if (multi != null) {
            return (T) multi;
        }

        return null;
    }

    protected abstract void wrapTheMap(Map<String, Object> map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * 用户管理的包装类
 *
 * @author fengshuonan
 * @date 2017年2月13日 下午10:47:03
 */
public class UserWrapper extends BaseControllerWrapper {

    public UserWrapper(Map<String, Object> single) {
        super(single);
    }

    public UserWrapper(List<Map<String, Object>> multi) {
        super(multi);
    }

    public UserWrapper(Page<Map<String, Object>> page) {
        super(page);
    }

    public UserWrapper(PageResult<Map<String, Object>> pageResult) {
        super(pageResult);
    }

    @Override
    protected void wrapTheMap(Map<String, Object> map) {
        map.put("sexName", ConstantFactory.me().getSexName((String) map.get("sex")));
        map.put("roleName", ConstantFactory.me().getRoleName((String) map.get("roleId")));
        map.put("deptName", ConstantFactory.me().getDeptName(DecimalUtil.getLong(map.get("deptId"))));
        map.put("statusName", ConstantFactory.me().getStatusName((String) map.get("status")));
        map.put("positionName", ConstantFactory.me().getPositionName(DecimalUtil.getLong(map.get("userId"))));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * 视图包装基类
 *
 * @author Chill BladeTool
 */
public abstract class BaseEntityWrapper<E, V> {

   /**
    * 单个实体类包装
    *
    * @param entity 实体类
    * @return V
    */
   public abstract V entityVO(E entity);

   /**
    * 实体类集合包装
    *
    * @param list 猎豹
    * @return List V
    */
   public List<V> listVO(List<E> list) {
      return list.stream().map(this::entityVO).collect(Collectors.toList());
   }

   /**
    * 分页实体类集合包装
    *
    * @param pages 分页
    * @return Page V
    */
   public IPage<V> pageVO(IPage<E> pages) {
      List<V> records = listVO(pages.getRecords());
      IPage<V> pageVo = new Page<>(pages.getCurrent(), pages.getSize(), pages.getTotal());
      pageVo.setRecords(records);
      return pageVo;
   }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * 包装类,返回视图层所需的字段
 *
 * @author Chill  BladeTool
 */
public class DictWrapper extends BaseEntityWrapper<Dict, DictVO> {

	private static IDictService dictService;

	static {
		dictService = SpringUtil.getBean(IDictService.class);
	}

	public static DictWrapper build() {
		return new DictWrapper();
	}

	@Override
	public DictVO entityVO(Dict dict) {
		DictVO dictVO = BeanUtil.copy(dict, DictVO.class);
		if (Func.equals(dict.getParentId(), CommonConstant.TOP_PARENT_ID)) {
			dictVO.setParentName(CommonConstant.TOP_PARENT_NAME);
		} else {
			Dict parent = dictService.getById(dict.getParentId());
			dictVO.setParentName(parent.getDictValue());
		}
		return dictVO;
	}

	public List<INode> listNodeVO(List<Dict> list) {
		List<INode> collect = list.stream().map(dict -> BeanUtil.copy(dict, DictVO.class)).collect(Collectors.toList());
		return ForestNodeMerger.merge(collect);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

参考文章:

接口与抽象类的应用(包括各自设计模式) (opens new window)

模板模式 | 菜鸟教程 (runoob.com) (opens new window)

#设计模式
Untitled
SpringBoot使用ApplicationEvent&Listener完成业务解耦

← Untitled SpringBoot使用ApplicationEvent&Listener完成业务解耦→

最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式