从 Spring 理解设计模式
面试提问
以下对话来自几天前的一次电话面试:
面试官:了解多线程吗?
我:了解。
面试官:实现多线程有哪些方法?
我:
- 继承 Thread 类,重写 run() 方法。
- 实现 Runnable 接口,实现 run() 方法。
- 实现 Callable 接口。
面试官:继承 Thread 类和实现 Runnable 接口有什么相似之处吗?
我:「窃喜,还好之前看了 Thread 类的源码」Thread 类的底层也是实现了 Runnable 接口。
面试官:实现 Runnable 接口的话,该怎样开启一个新线程?
我:new Thread(implRunnable).start();
面试官:「邪魅一笑」知道这里面蕴含着什么设计模式吗?
我:「糟了,上了这老油条的套了」……
面试结束~
问题探究
new Thread(implRunnable).start();
这行代码里面到底蕴含了什么设计模式?我开始了探究之旅。
首先,代码完成实现 Runnable 接口开启多线程。
买火车票类
public class ImplRunnable implements Runnable {
private int tickets = 10;
@Override
public void run() {
while (true) {
if (tickets <= 0) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "购买了第-->" + tickets-- + "张火车票");
}
}
}
主方法
public class Test {
public static void main(String[] args) {
ImplRunnable runnable = new ImplRunnable();
new Thread(runnable,"张三").start();
new Thread(runnable,"李四").start();
new Thread(runnable,"王五").start();
}
}
由上文可知,Thread 类底层也是实现了 Runnable 接口
而在代码中,我所用到的 ImplRunnable 类也是实现了 Runnable 接口
在最后的开启多线程代码中:
runnable 是 ImplRunnable 类的实例化对象,
两个对象都实现了 Runnable 接口
new Thread(runnable,"张三").start();
new Thread(runnable,"李四").start();
new Thread(runnable,"王五").start();
思考结果
两个对象的类实现了同一个接口,Thread 类的对象持有 ImplRunnable 类的对象的引用,Thread 类「代理类」的对象在程序运行前就已经存在了。
这不就是 静态代理模式吗
!
静态代理模式介绍
用一个直观的例子来理解静态代理模式。
在这个例子里,你将要结婚了,但是你觉得结婚的业务太麻烦了,这时你便想到了婚庆公司,让他代你去处理这些结婚过程中的琐事,而你只需要按照婚庆公司的指示做你该做的事,你只需要专注于你自己的事就可以了,这便是一个静态代理模式的例子。
-
两个角色:
- 你,真实的结婚对象
- 婚庆公司,帮你实现结婚业务的对象
-
一个场景:
- 结婚,一个接口,里面包含了全部的结婚方法
结婚「接口」
public interface Marry {
void happyMarry();
}
你「结婚类」
public class You implements Marry{
@Override
public void happyMarry() {
System.out.println("!!!!!!!!!你要结婚啦!!!!!!!!!");
}
}
婚庆公司「代理类」
public class WeddingCompany implements Marry {
//结婚的目标对象
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
//结婚前
before();
this.target.happyMarry();
//结婚后
after();
}
private void before() {
System.out.println("婚庆公司在你结婚之前布置婚礼现场");
}
private void after() {
System.out.println("婚庆公司在你结婚之后要收尾款");
}
}
主方法
public class Test {
public static void main(String[] args) {
You you = new You();
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.happyMarry();
}
}
静态代理模式总结
-
代理模式的优点
-
代理类可以做被代理类做不了的事情。
-
被代理类只需要专注于自己的核心代码即可,剩下的交给代理类就可以了,使得代码更加简洁,分工明确。
-
-
静态代理模式的缺点:
- 一个真实角色就会产生一个代理角色,我们需要书写的代码量翻倍。
-
静态代理模式的构成
- 一个公共的接口
- 一个代理角色
- 一个被代理角色
-
静态代理模式要求
- 被代理角色和代理角色要实现同一个接口,代理角色要持有被代理角色的引用。
延申——动态代理
动态代理和静态代理相似,唯一不同的点就是:
动态代理的代理类是通过反射机制在程序运行时动态生成的,
静态代理的代理类是我们提前写好的「在程序运行前就已经存在」
在 Java 中动态代理分为两大类:
- 基于接口的动态代理 ----> JDK 动态代理
- 基于类的动态代理 ----> cglib
面试拓展
既然面试官喜欢问代码中使用了哪些设计模式,那么一统后端开发江湖的——Spring 框架使用了那些设计模式呢?
模板方法模式
- 定义
- 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 理解
- 定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。
在 Spring 中,模板方法模式几乎随处可见。
操作数据库的 JdbcTemplate、操作 Redis 的 RedisTemplate、操作 RabbitMQ 的 RabbitTemplate等
策略模式
- 定义
- 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改
- 理解
- 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
在 Spring 中在实例化对象的时候用到策略模式。
简单工厂模式
- 定义
- 创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
- 理解
- 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
在 Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
工厂方法模式
- 定义
- 工厂方法模式(FACTORY METHOD)是一种常用的类创建型设计模式,此模式的核心精神是封装类中变化的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂、具体工厂、抽象产品、具体产品。
- 理解
- 定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。
一般情况下,应用程序有自己的工厂对象来创建 Bean。
如果将应用程序自己的工厂对象交给 Spring 管理,那么 Spring 管理的就不是普通的 Bean,而是工厂 Bean。
单例模式
- 定义
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory 。但没有从构造器级别去控制单例,这是因为Spring 管理的是是任意的 Java 对象。Spring 下默认的 Bean 均为单例。
适配器模式
- 定义
- 将一个类的接口转换成客户希望的另外一个接口。
SpringAOP 模块对 BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的,
这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,
上述三种通知类型是 SpringAOP 模块定义的,它们是 AOP 联盟定义的 Advice 的子类型。
观察者模式
- 定义
- 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Spring 中 Observer 模式常用的地方是 Listener 的实现。如 ApplicationListener。
装饰模式
- 定义
- 允许向一个现有的对象添加新的功能,同时又不改变其结构。
Spring 中用到的包装器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator。
基本上都是动态地给一个对象添加一些额外的职责。
原型模式
- 定义
- 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
所谓原型模式,就是 Java 中的克隆。
以某个对象为原型,复制出新的对象。
显然新的对象具备原型对象的特点,效率高(避免了重新执行构造过程步骤)。
总结
个人以为,所谓「设计模式」就是将现实生活中常用的解决问题的经典思路或者巧妙思路总结了出来,再用编程的知识表达出来,运用在程序上。设计模式来源于生活,它只是定于的语句过于抽象,又或者结合到些许的编程思维,让初学者学起来一头雾水。如果从生活的角度去理解「设计模式」,相信你一定是豁然开朗。
一个健壮、高效的程序离不开各种设计模式。
在日常学习和工作中,我们可以从常用的优秀框架里去一步步解析它所使用的设计模式,来更深入的理解设计模式,训练自己的编程思维。
只要你善于发现和总结,你也可以设计模式。