【项目实践】依赖注入用得好,设计模式随便搞

程序猿欧文 2021-01-21 10:12:38
项目 实践 依赖 注入 用得


在实际开发中运用策略模式

首图.png

以项目驱动学习,以实践检验真知

前言

设计模式是我们编程道路上绕不开的一环,用好了设计模式能够让代码拥有良好的维护性、可读性以及扩展性,它仿佛就是“优雅”的代名词,各个框架和库也都能见到它的身影。

正是因为它有种种好处,所以很多人在开发时总想将某个设计模式用到项目中来,然而往往会用得比较别扭。其中一部分原因是业务需求并不太符合所用的设计模式,还有一部分原因就是在Web项目中我们对象都是交由Spring框架的Ioc容器来管理,很多设计模式无法直接套用。那么在真正的项目开发中,我们就需要对设计模式做一个灵活的变通,让其能够和框架结合,在实际开发中发挥出真正的优势。

当项目引入IoC容器后,我们一般是通过依赖注入来使用各个对象,将设计模式和框架结合的关键点就在于此!本文会讲解如何通过依赖注入来完成以下三种设计模式:

  • 单例模式
  • 责任链模式
  • 策略模式

由浅入深,让你在了解几个设计模式的同时掌握依赖注入的一些妙用。

本文所有代码都放在Github上,克隆下来即可运行查看效果。

实战

单例模式

单例应该是很多人接触的第一个设计模式,相比其他设计模式来说单例的概念非常简单,即在一个进程中,某个类从始至终只有一个实例对象。不过概念就算再简单,还是需要一点编码才能实现,R 之前的文章 回字有四种写法,那你知道单例有五种写法吗 就有详细的讲解,这里对该设计模式就不过多介绍了,咱们直接来看看在实际开发中如何运用该模式。

交由Spring IoC容器管理的对象称之为Bean,每个Bean都有其作用域(scope),这个作用域可以理解为Spring控制Bean生命周期的方式。创建和销毁是生命周期中必不可少的节点,单例模式的重点自然是对象的创建。而Spring创建对象的过程对于我们来说是无感知的,即我们只需配置好Bean,然后通过依赖注入就可以使用对象了:

@Service //和@Component功能一样,将该类声明为Bean交由容器管理public class UserServiceImpl implements UserService{}@Controllerpublic class UserController { @Autowired // 依赖注入 private UserService userService;}

那这个对象的创建我们该如何控制呢?

其实,Bean默认的作用范围就是单例的,我们无需手写单例。要想验证Bean是否为单例很简单,我们在程序各个地方获取Bean后打印其hashCode就可以看是否为同一个对象了,比如两个不同的类中都注入了UserService

@Controllerpublic class UserController { @Autowired private UserService userService; public void test() { System.out.println(userService.hashCode()); }}@Controllerpublic class OtherController { @Autowired private UserService userService; public void test() { System.out.println(userService.hashCode()); }}

打印结果会是两个相同的hashCode

为什么Spring默认会用单例的形式来实例化Bean呢?这自然是因为单例可以节约资源,有很多类是没必要实例化多个对象的。

如果我们就是想每次获取Bean时都创建一个对象呢?我们可以在声明Bean的时候加上@Scope 注解来配置其作用域:

@Service@Scope("prototype")public class UserServiceImpl implements UserService{}

这样当你每次获取Bean时都会创建一个实例。

Bean的作用域有以下几种,我们可以根据需求配置,大多数情况下我们用默认的单例就好了:

名称 说明
singleton 默认作用范围。每个IoC容器只创建一个对象实例。
prototype 被定义为多个对象实例。
request 限定在HTTP请求的生命周期内。每个HTTP客户端请求都有自己的对象实例。
session 限定在HttpSession的生命周期内。
application 限定在ServletContext的生命周期内。
websocket 限定在WebSocket的生命周期内。

这里要额外注意一点,Bean的单例并不能完全算传统意义上的单例,因为其作用域只能保证在IoC容器内保证只有一个对象实例,但是不能保证一个进程内只有一个对象实例。也就是说,如果你不通过Spring提供的方式获取Bean,而是自己创建了一个对象,此时程序就会有多个对象存在了:

public void test() { // 自己new了一个对象 System.out.println(new UserServiceImpl().hashCode());}

这就是需要变通的地方,Spring可以说在我们日常开发中覆盖了每一个角落,只要自己不故意绕开Spring,那么保证IoC容器内的单例基本就等同于保证了整个程序内的单例。

责任链模式

概念比较简单的单例讲解完后,咱们再来看看责任链模式。

模式讲解

该模式并不复杂:一个请求可以被多个对象处理,这些对象连接成一条链并且沿着这条链传递请求,直到有对象处理它为止。该模式的好处是让请求者和接受者解耦,可以动态增删处理逻辑,让处理对象的职责拥有了非常高的灵活性。我们开发中常用的过滤器Filter和拦截器Interceptor就是运用了责任链模式。

光看介绍只会让人云里雾里,我们直接来看下该模式如何运用。

就拿工作中的请假审批来说,当我们发起一个请假申请的时候,一般会有多个审批者,每个审批者都代表着一个责任节点,都有自己的审批逻辑。我们假设有以下审批者:

组长Leader:只能审批不超过三天的请假;
经理Manger:只能审批不超过七天的请假;
老板Boss:能够审批任意天数。

咱们先定义一个请假审批的对象:

public class Request { /** * 请求人姓名 */ private String name; /** * 请假天数。为了演示就简单按整天来算,不弄什么小时了 */ private Integer day; public Request(String name, Integer day) { this.name = name; this.day = day; } // 省略get、set方法}

按照传统的写法是接受者收到这个对象后通过条件判断来进行相应的处理:

public class Handler { public void process(Request request) { System.out.println("---"); // Leader审批 if (request.getDay() <= 3) { System.out.println(String.format("Leader已审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); return; } System.out.println(String.format("Leader无法审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); // Manger审批 if (request.getDay() <= 7) { System.out.println(String.format("Manger已审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); return; } System.out.println(String.format("Manger无法审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); // Boss审批 System.out.println(String.format("Boss已审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); System.out.println("---"); }}

在客户端模拟审批流程:

public class App { public static void main( String[] args ) { Handler handler = new Handler(); handler.process(new Request("张三", 2)); handler.process(new Request("李四", 5)); handler.process(new Request("王五", 14)); }}

打印结果如下:

---Leader已审批【张三】的【2】天请假申请---Leader无法审批【李四】的【5】天请假申请Manger已审批【李四】的【5】天请假申请---Leader无法审批【王五】的【14】天请假申请Manger无法审批【王五】的【14】天请假申请Boss已审批【王五】的【14】天请假申请---

不难看出Handler类中的代码充满了坏味道!每个责任节点间的耦合度非常高,如果要增删某个节点,就要改动这一大段代码,很不灵活。而且这里演示的审批逻辑还只是打印一句话而已,在真实业务中处理逻辑可比这复杂多了,如果要改动起来简直就是灾难。

这时候我们的责任链模式就派上用场了!我们将每个责任节点封装成独立的对象,然后将这些对象组合起来变成一个链条,并通过统一入口挨个处理。

首先,我们要抽象出责任节点的接口,所有节点都实现该接口:

public interface Handler { /** * 返回值为true,则代表放行,交由下一个节点处理 * 返回值为false,则代表不放行 */ boolean process(Request request);}

以Leader节点为例,实现该接口:

public class LeaderHandler implements Handler{ @Override public boolean process(Request request) { if (request.getDay() <= 3) { System.out.println(String.format("Leader已审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); // 处理完毕,不放行 return false; } System.out.println(String.format("Leader无法审批【%s】的【%d】天请假申请", request.getName(), request.getDay())); // 放行 return true; }}

然后定义一个专门用来处理这些Handler的链条类:

public class HandlerChain { // 存放所有Handler private List<Handler> handlers = new LinkedList<>(); // 给外部提供一个增加Handler的入口 public void addHandler(Handler handler) { this.handlers.add(handler); } public void process(Request request) { // 依次调用Handler for (Handler handler : handlers) { // 如果返回为false,中止调用 if (!handler.process(request)) { break; } } } }

现在我们来看下使用责任链是怎样执行审批流程的:

public class App { public static void main( String[] args ) { // 构建责任链 HandlerChain chain = new HandlerChain(); chain.addHandler(new LeaderHandler()); chain.addHandler(new ManagerHandler()); chain.addHandler(new BossHandler()); // 执行多个流程 chain.process(new Request("张三", 2)); chain.process(new Request("李四", 5)); chain.process(new Request("王五", 14)); }}

打印结果和前面一致。

这样带来的好处是显而易见的,我们可以非常方便地增删责任节点,修改某个责任节点的逻辑也不会影响到其他的节点,每个节点只需关注自己的逻辑。并且责任链是按照固定顺序执行节点,按照自己想要的顺序添加各个对象即可方便地排列顺序。

此外责任链有很多变体,比如像Servlet的Filter执行下一个节点时还需要持有链条的引用:

public class MyFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { if (...) { // 通过链条引用来放行 chain.doFilter(req, resp); } else { // 如果没有调用chain的方法则代表中止往下传递 ... } }}

各责任链除了传递的方式不同,整体的链路逻辑也可以有所不同。

我们刚才演示的是将请求交由某一个节点进行处理,只要有一个处理了,后续就不用处理了。有些责任链目的不是找到某一个节点来处理,而是每个节点都做一些事,相当于一个流水线。

比如像刚才的审批流程,我们可以将逻辑改为一个请假申请需要每一个审批人都同意才算申请通过,Leader同意了后转给Manger审批,Manger同意了后转给Boss审批,只有Boss最终同意了才生效。

形式有多种,其核心概念是将请求对象链式传递,不脱离这一点就都可以算作责任链模式,无需太死守定义。

配合框架

责任链模式中,我们都是自己创建责任节点对象,然后将其添加到责任链条中。在实际开发中这样就会有一个问题,如果我们的责任节点里依赖注入了其它的Bean,那么手动创建对象的话则代表该对象就没有交由Spring管理,那些属性也就不会被依赖注入:

public class LeaderHandler implements Handler{ @Autowired // 手动创建LeaderHa.........
版权声明
本文为[程序猿欧文]所创,转载请带上原文链接,感谢
https://my.oschina.net/mikeowen/blog/4917256

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云