框架源码系列一:设计模式(设计思想、设计原则、各种设计模式介绍、设计模式总结)
要分析常用框架spring、mybatis、springboot、springcloud等的源码,首先要了解各种设计模式,因为框架里面应用了各种设计模式
一、设计思想
学习设计模式最重要的是掌握设计思想和设计原则,理解了设计思想和设计原则并运用到平时的编码中是最重要的!!!
1. 我们先来看下面的问题:
天天加班编程,编程到底都做的是什么?
撸代码,加班撸代码,写接口、写类、写方法
用设计模式或做设计的作用是什么?
指导、规定该如何撸代码,如何来写接口、写类、写方法
为什么要做设计、用设计模式?
代码会变,为应对变化,为了以后方便扩展。做到以不变应万变,做一个会偷懒的程序员!
2.下面来看一下什么是设计思想
首先是从现实出发理清现实,在写代码之前先从实际分析,然后就开始写代码,写代码时要区分出不变的代码和会变化的代码,会变得代码会怎么变,使用者如何隔绝这种变化,所谓的隔绝这种变化就是不让调用者感知到内部的变化,只需要很简单的方式就能使用不必关心内部的逻辑,这样的话就要用到各种设计模式。不同的变化方式对应不同的设计模式。
设计的最终体现:如何来定义类、接口、方法
3. 设计思想—OOP
3.1 OOP中的几个元素
1)类是做什么用的?
模拟现实,封装数据与代码
2)接口是做什么用的?
定义相接的口子
定义功能使用者和功能提供者间的接口
3)为什么要有接口?
隔离变化
4)抽象类是做什么用的?
包容不变与变的
3.2 OOP的三大特性
1)多态为我们提供了什么?
一种实现变化的方式
3.3 类与类之间的关系有哪些?
二、设计原则
1. 找出变化,分开变化和不变的
隔离,封装变化的部分,让其他部分不受它的影响。
2. 面向接口编程 ——隔离变化的方式
使用者使用接口,提供者实现接口。“接口”可以是超类!
3. 依赖倒置原则(里氏替换原则)——隔离变化的方式
依赖抽象,不要依赖具体类!
4. 对修改闭合,对扩展开放——隔离变化的方式
可以继承一个类或者接口扩展功能,但是不能修改类或者接口的原有功能
5. 多用组合,少用继承——灵活变化的方式
“有一个”可能比“是一个”更好。
6. 单一职责原则——方法设计的原则
每个方法只负责一个功能,不要把很多功能写在一个方法里面
三、各种设计模式介绍
应用设计模式的目的:
易扩展,易维护
少改代码,不改代码
1.策略模式
示例:
京东、天猫双十一促销,各种商品有多种不同的促销活动:
满减:满400减50
每满减:每满100减20
数量折扣:买两件8折、三件7折
数量减:满三件减最低价的一件
……
顾客下单时可选择多种促销活动的其中一种来下单
后端代码中如何来灵活应对订单金额的计算?以后还会有很多的促销活动出现!
1.1 该如何来实现订单金额的计算?
控制器OrderController.java
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 计算订单的促销金额 */ @RequestMapping("prepare") public Order prepareOrder(Order order, String promotion) { …….. return this.orderService.prepareOrder(order, promotion); } }
OrderService.java改怎么来写呢
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { // 该如何写 return order; } }
1.2 这样可以吗?
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促销1的算法 …… break; case "promotion-2": // 促销2的算法 …… break; case "promotion-3": // 促销3的算法 …… break; …… } return order; } }
营销活动有很多,这个switch会变得很庞大,不利于维护,并且很容易引入新的问题
1.3 改进一下,这样是不是好些了?
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促销1的算法 return calPromotion1(order); case "promotion-2": // 促销2的算法 return calPromotion2(order); case "promotion-3": // 促销3的算法 return calPromotion3(order); …… } return order; } private Order calPromotion1(Order order) { System.out.println("促销1计算.............................."); return order; } ……. }
把每个促销算法单独抽出一个方法,新加入一个促销活动只需要新增一个方法和case就可以了
这里利用了设计原则的方法设计原则:单一职责原则
但是这样写还会存在如下问题:
营销活动经常变,这个switch就得经常改,还得不断加促销的算法方法…….
改代码是bug的源泉,我们希望少改动OrderService!!!
分析:这里变的是什么?
促销的金额的算法!同一行为的不同算法!
我们不希望OrderService被算法代码爆炸!
1.4 再次改进
同一行为的不同算法实现,我们可以用接口来定义行为,不同的算法分别去实现接口。
这里利用了设计原则:对修改关闭,对扩展开放!
这就是策略模式的应用!
策略模式的的定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的用户而独立变化。
1.5 使用策略模式再次改进后的OrderService
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促销1的算法 return new Promotion1Calculation().calculate(order); case "promotion-2": // 促销2的算法 return new Promotion2Calculation().calculate(order); case "promotion-3": // 促销3的算法 return new Promotion3Calculation().calculate(order); ...... } } }
但是switch中的代码还是会不断变!!!switch中需要知道所有的实现!
如何让OrderService的代码不要改变?
把变的部分移出去!改怎么移呢?
1.6 通过一个工厂来专门负责创建各种促销计算实现,就把变化移出来了!
@Component public class PromotionCalculationFactory { public PromotionCalculation getPromotionCalculation(String promotion) { switch (promotion) { case "promotion-1": // 促销1的算法 return new Promotion1Calculation(); case "promotion-2": // 促销2的算法 return new Promotion2Calculation(); case "promotion-3": // 促销3的算法 return new Promotion3Calculation(); ...... } } }
这是简单工厂模式:所有产品由一个工厂创建
@Service public class OrderService { @Autowired private PromotionCalculationFactory promotionCalculationFactory; public Order prepareOrder(Order order, String promotion) { return promotionCalculationFactory.getPromotionCalculation(promotion).calculate(order); } }
想要工厂中的代码也不要随促销的变化而变化,你觉得该怎么办?
方式一:promotion = beanName
把各种促销算法的实现交给spring容器来管理,用户选择的促销活动promotion 作为bean的名字,在PromotionCalculationFactory 工厂里面通过getBean("promotion")就能拿到各种促销算法的实现了
方式一的伪代码实现:
spring里面的bean配置:
<bean id="promotion1" calss="Promotion1Calculation"> <bean id="promotion2" calss="Promotion2Calculation"> <bean id="promotion3" calss="Promotion3Calculation">
PromotionCalculationFactory 工厂改写:
@Component public class PromotionCalculationFactory { public PromotionCalculation getPromotionCalculation(String promotion) { return getBean("promotion1/promotion2/promotion3"); } } }
方式二: 配置promotion与实现类的对应关系
把用户选择的促销活动promotion和对应的促销算法的实现类放到map里面,或者存到数据库里面,在PromotionCalculationFactory 工厂里面通过map.get("promotion"),或者从数据库里面获取对应促销算法的实现类路径通过Class.forName("促销算法的实现类路径")就能拿到各种促销算法的实现了
方式二的伪代码实现:
PromotionCalculationFactory 工厂改写:
package com.study.design.mode.service; import java.util.Map; import org.springframework.stereotype.Component; @Component public class PromotionCalculationFactory { private Map<String, PromotionCalculation> maps; public PromotionCalculation getPromotionCalculation(String promotion) { PromotionCalculation prom = maps.get(promotion); if (prom == null) { // 从配置的地方加载 prom = getFromDb(promotion); if (prom != null) maps.put(promotion, prom); } return prom; } public void init() { // 第一次将所有的促销策略都加载到Map中 } private PromotionCalculation getFromDb(String promotion) { // 从数据库中取到对应的类名 //配置的格式: promotion1=com.study.dn.promotion.calculation.Promotion1 String className = 从数据库(或其他配置源)中获得; // Class c = Class.forName(className); // 实例化 // 返回 } }
2. 工厂模式
2.1 简单工厂模式
一个工厂负责创建所有实例。比如上面的策略模式中使用的就是简单工厂模式
根据传入的工厂类型参数String创建对应的实例(产品)
2.2 工厂方法模式
父类中定义工厂方法,各子类在+factoryMethod():Product方法里面实现具体的实例创建
使用者持有具体的工厂ChildAClass、ChildBClass、ChildCClass,传入对应的工厂ChildAClass、ChildBClass、ChildCClass创建对应的工厂实例
2.3 抽象工厂模式
定义一个工厂接口,所有具体工厂实现工厂接口
使用者调用FactoryProducer的getFactory(type)方法传入type,type为AFactory、BFactory、CFactory对应的类型,就会返回对应的工厂AFactory、BFactory、CFactory,不需要传入AFactory、BFactory、CFactory,因为type已经跟AFactory、BFactory、CFactory绑定了。
3. 装饰者模式
示例:促销活动可多重叠加,该如何灵活实现订单金额计算?
OrderController
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 计算订单的促销金额,促销按给入的顺从叠加 */ @RequestMapping("prepare") public Order prepareOrder(Order order, String... promotion) { return this.orderService.prepareOrder(order, promotion); } }
OrderService
@Service public class OrderService { @Autowired private PromotionCalculationFactory promotionCalculationFactory; public Order prepareOrder(Order order, String... promotion) { for (String p : promotion) { order = promotionCalculationFactory. getPromotionCalculation(p).calculate(order); } return order; } }
装饰者模式的定义:以装饰的方式,动态地将责任附加到对象上。
说明:
不改变具体类代码(被装饰者ConcreteComponent),动态叠加增强行为功能。
若要扩展功能,装饰者提供了比继承更有弹性的替代方案
相较于前面的for循环,有何区别?
当需要对一个类的多个方法进行增强,使用者会随意使用被增强方法时,for循环就不够灵活了。
责任链和装饰者模式完成的是相同的事情。
装饰者模式-代码示例:
共同的需装饰的行为定义成接口
public interface Component { String methodA(); int methodB(); }
被装饰者实现接口Component
public class ConcreteComponent implements Component { public String methodA() { return "concrete-object"; } public int methodB() { return 100; } }
装饰者实现接口Component
public class Decorator implements Component { //装饰者包含被装饰者(被装饰者实现的接口) protected Component component; public Decorator(Component component) { super(); this.component = component; } public String methodA() { return this.component.methodA(); } public int methodB() { return this.component.methodB(); } }
装饰者派生出的装饰者
public class DecoratorA extends Decorator { public DecoratorA(Component component) { super(component); } public String methodA() { //在这里可以进行前置增强,实现要处理的逻辑 return this.component.methodA() + " + A"; //在这里可以进行后置增强,实现要处理的逻辑 } public int methodB() { //在这里可以进行前置增强,实现要处理的逻辑 return this.component.methodB() + 10; //在这里可以进行后置增强,实现要处理的逻辑 } }
调用示例:
public class DecoratorSample { public static void main(String[] args) { //创建一个被装饰者 Component cc = new ConcreteComponent(); //创建一个派生的装饰者,同时把被装饰者传入装饰者里面,即说的装饰者包含被装饰者 cc = new DecoratorA(cc); //方法调用 System.out.println(cc.methodA()); System.out.println(cc.methodB()); } }
输出结果:
concrete-object + A
110
4. 代理模式
4.1 定义
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
作用:不改变原类的代码,而增强原类对象的功能,可选择前置、后置、环绕、异常处理增强
代理模式的类图:
类图与装饰者模式一样,那么代理模式和装饰者模式有什么区别呢?
代理模式意在在代理中控制使用者对目标对象的访问,以及进行功能增强。装饰者模式意在对功能的叠加,比如对多种促销活动的叠加
4.3 代理模式的实现方式
代理模式有两种实现方式:
静态代理:由程序员创建或由特定工具自动生成代理类源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:代理类在程序运行时,运用反射机制动态创建而成。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
4.3.1 静态代理:
有一个土豪要找苍老师约会,他不能直接和苍老师约,需要经过一个中间代理Tony
需要被代理控制增强的行为定义成接口或者超类
public interface Girl { boolean dating(float length); }
代理和被代理的目标对象都要实现接口Girl
代理:
package com.study.design.mode.samples.proxy; /** * * @Description: 代理类实现Girl * @author leeSamll * @date 2018年11月24日 * */ public class Tony implements Girl { //代理类持有被代理的目标对象TeacherCang(目标对象实现的超类或者接口) private Girl girl; public Girl getGirl() { return girl; } public void setGirl(Girl girl) { this.girl = girl; } //代理:控制、增强被代理对象的行为 public boolean dating(float length) { // 前置增强 doSomethingBefore(); boolean res = this.girl.dating(length); // 后置增强 doSomethingAfter(); return res; } private void doSomethingBefore() { System.out.println("老板,这个我试过了,很不错,推荐给你!"); } private void doSomethingAfter() { System.out.println("老板,你觉得怎样,欢迎下次再约!"); } }
被代理的目标对象
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目标对象实现Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以约!"); return true; } System.out.println("身高不可以,不可约!"); return false; } }
土豪使用者
package com.study.design.mode.samples.proxy; /** * * @Description: 使用者 * @author leeSamll * @date 2018年11月24日 * */ public class TuHao { private float length; public TuHao(float length) { super(); this.length = length; } public float getLength() { return length; } public void setLength(float length) { this.length = length; } //约会 public void dating(Girl g) { g.dating(length); } }
调用示例:
package com.study.design.mode.samples.proxy; /** * * @Description: 调用示例 * @author leeSamll * @date 2018年11月24日 * */ public class PlayGame { public static void main(String[] args) { //创建土豪(使用者)、苍老师(目标对象)、tony(代理)三个对象 TuHao th = new TuHao(1.7F); Girl tc = new TeacherCang(); Tony tony = new Tony(); //tony对苍老师进行代理 tony.setGirl(tc); //土豪和tony约 th.dating(tony); }
输出结果:
老板,这个我试过了,很不错,推荐给你!
身高可以,可以约!
老板,你觉得怎样,欢迎下次再约!
静态代理缺点:
扩展能力差
横向扩展:代理更多的类
纵向扩展:增强更多的方法
可维护性差
由于静态代理的扩展能力差、可维护性差,这就需要使用动态代理了!!!
4.3.2 动态代理
在运行时,动态为不同类的对象创建代理,增强功能。灵活扩展,易维护!
动态代理的实现方式:
JDK动态代理:只可对接口创建代理
CGLIB动态代理:可对接口、类创建代理
(1) JDK动态代理
在运行时,对接口创建代理对象
生成代理类$Proxy0的方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数说明:
ClassLoader loader:类加载器
Class<?>[] interfaces:需要被代理的目标对象实现的接口,可以传入多个
InvocationHandler h:功能增强的接口
功能增强的接口:
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
参数说明:
Object proxy:被代理的目标对象(接口)
Method method:要调用的目标对象的方法
Object[] args:要调用的目标对象的方法的参数
eg1:JDK动态代理-代码示例
被代理控制增强的行为生成接口:
Girl
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增强的行为生成接口Girl * @author leeSamll * @date 2018年11月24日 * */ public interface Girl { boolean dating(float length); }
Boy
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增强的行为生成接口Boy * @author leeSamll * @date 2018年11月24日 * */ public interface Boy { boolean dating(char cup); void show(); }
被代理的目标对象
TeacherCang
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目标对象TeacherCang实现Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以约!"); return true; } System.out.println("身高不可以,不可约!"); return false; } }
TeacherChen
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目标对象TeacherChen实现Boy * @author leeSamll * @date 2018年11月24日 * */ public class TeacherChen implements Boy { public boolean dating(char cup) { if (cup == 'E') { System.out.println("这个女老板品德正好,可以约!"); return true; } System.out.println("这个女老板品德不行,不可以约!"); return false; } public void show() { System.out.println("开始进入拍摄模式。。。。。。。。"); } }
JDK动态代理
package com.study.design.mode.samples.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * * @Description: JDK动态代理 * @author leeSamll * @date 2018年11月24日 * */ public class TonyCompany { //动态生成代理对象 传入target的是被代理的类 public static Object proxy(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvationHandler(target)); } //特定的功能增强实现 private static class MyInvationHandler implements InvocationHandler { //被被代理的目标对象 private Object target; public MyInvationHandler(Object target) { super(); this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 doSomethingBefore(); // 调用被代理对象的方法 Object res = method.invoke(target, args); // 后置增强 doSomethingAfter(); return res; } private void doSomethingAfter() { System.out.println("老板,你觉得怎样,欢迎下次再约!"); } private void doSomethingBefore() { System.out.println("老板,这个我试过了,很不错,推荐给你!"); } } }
调用示例:
package com.study.design.mode.samples.proxy; /** * * @Description: 调用示例 * @author leeSamll * @date 2018年11月24日 * */ public class PlayGame { public static void main(String[] args) { System.out.println("----------------1.静态代理TeacherCang-----------------------"); //创建土豪(使用者)、苍老师(目标对象)、tony(代理)三个对象 TuHao th = new TuHao(1.7F); Girl tc = new TeacherCang(); Tony tony = new Tony(); //tony对苍老师进行代理 tony.setGirl(tc); //土豪和tony约 th.dating(tony); System.out.println("----------------2.JDK动态代理TeacherCang-----------------------"); //生成代理类$Proxy0 Girl tony1 = (Girl) TonyCompany.proxy(tc); //土豪直接和代理tony约 th.dating(tony1); System.out.println("----------------3.JDK动态代理TeacherChen,横向纵向扩展:代理更多的类和方法-----------------------"); //代理另外一个目标对象TeacherChen Boy tcc = new TeacherChen(); //生成代理类$Proxy0 Boy tony2 = (Boy) TonyCompany.proxy(tcc); //tony2约TeacherChen 纵向扩展:增强更多的方法 System.out.println("----------------3.1 JDK动态代理TeacherChen,调用TeacherChen的dating方法-----------------------"); tony2.dating('E'); System.out.println("----------------3.2 JDK动态代理TeacherChen,调用TeacherChen的show方法-----------------------"); tony2.show(); } }
输出结果:
----------------1.静态代理TeacherCang----------------------- 老板,这个我试过了,很不错,推荐给你! 身高可以,可以约! 老板,你觉得怎样,欢迎下次再约! ----------------2.JDK动态代理TeacherCang----------------------- 老板,这个我试过了,很不错,推荐给你! 身高可以,可以约! 老板,你觉得怎样,欢迎下次再约! ----------------3.JDK动态代理TeacherChen,横向纵向扩展:代理更多的类和方法----------------------- ----------------3.1 JDK动态代理TeacherChen,调用TeacherChen的dating方法----------------------- 老板,这个我试过了,很不错,推荐给你! 这个女老板品德正好,可以约! 老板,你觉得怎样,欢迎下次再约! ----------------3.2 JDK动态代理TeacherChen,调用TeacherChen的show方法----------------------- 老板,这个我试过了,很不错,推荐给你! 开始进入拍摄模式。。。。。。。。 老板,你觉得怎样,欢迎下次再约!
(2) cglib动态代理
cglib是什么?
cglib( Byte Code Generation Library),一个高层次的java字节码生成和转换的api库.
ASM:一个低层次的字节码操作库
它的主要用途
在运行期为类、接口生成动态代理对象。 以达到不改动原类代码而实现功能增强的目的
常在哪里用它?
常在 AOP、test、orm框架中用来生成动态代理对象、拦截属性访问
如何使用它?
1)引入它的jar
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.6</version> </dependency>
2)学习它的API
https://github.com/cglib/cglib/wiki
cglib动态代理-类图和API
说明:
实现思想和前面的JDK动态代理一样,只是使用了不同的API。
代理类由Enhancer生成,代理类实现被代理的类或者接口,特定的功能增强的实现MyMethodInterceptor实现MethodInterceptor接口,特定的功能增强实现MyMethodInterceptor里面持有被代理的类或者接口target
eg2:cglib动态代理-代码示例
被代理对象的接口:
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增强的行为生成接口Girl * @author leeSamll * @date 2018年11月24日 * */ public interface Girl { boolean dating(float length); }
被代理对象:
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目标对象TeacherCang实现Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以约!"); return true; } System.out.println("身高不可以,不可约!"); return false; } }
cglib动态代理主类:
package com.study.design.mode.samples.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * * @Description: cglib动态代理 * @author leeSamll * @date 2018年11月24日 * */ public class CglibDemo { // 特定的功能增强的实现 static class MyMethodInterceptor implements MethodInterceptor { //特定的功能增强实现MyMethodInterceptor里面持有被代理的类或者接口target private Object target; public MyMethodInterceptor(Object target) { this.target = target; } //在intercept方法进行调用被代理类或者接口的方法之前进行拦截实现前置、后置、环绕、异常处理等功能的增强 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("**************** " + method.getName()); // 前置增强 doSomethingBefore(); // 返回值 Object res = null; // 这里可以调用父类的该方法,当是生成接口的代理时不可调用。 // Object res = methodProxy.invokeSuper(proxy, args); // 通过method来调用被代理对象的方法 if (this.target != null) { res = method.invoke(target, args); } // 后置增强 doSomethingAfter(); return res; } private void doSomethingBefore() { System.out.println("老板你好,这个我试过了,很不错,推荐给你!"); } private void doSomethingAfter() { System.out.println("老板你觉得怎样? 欢迎下次....."); } }; public static void main(String[] args) { //创建Enhancer对象用来生成代理类 Enhancer e = new Enhancer(); //创建需要被代理的类TeacherCang TeacherCang tc = new TeacherCang(); // 设置增强回调 e.setCallback(new MyMethodInterceptor(tc)); //对接口生成代理对象 System.out.println("--------------------cglib动态代理:对接口Girl进行代理----------------------"); //设置要代理的接口 e.setInterfaces(new Class[] { Girl.class }); //生成代理的接口的动态代理对象 Girl g = (Girl) e.create(); //调用被代理的接口的dating方法 g.dating(1.8f); // 对类生成代理对象 System.out.println("--------------------cglib动态代理:对类TeacherCang进行代理----------------------"); //设置要代理的类 e.setSuperclass(TeacherCang.class); //把前面的设置的接口Girl置为空 e.setInterfaces(null); //当有多个callback时,需要通过callbackFilter来指定被代理方法使用第几个callback /* e.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { return 0; } });*/ //生成代理的类TeacherCang的动态代理对象 TeacherCang proxy = (TeacherCang) e.create(); //调用代理的类TeacherCang的dating方法 proxy.dating(1.8f); } }
输出结果:
--------------------cglib动态代理:对接口Girl进行代理---------------------- **************** dating 老板你好,这个我试过了,很不错,推荐给你! 身高可以,可以约! 老板你觉得怎样? 欢迎下次..... --------------------cglib动态代理:对类TeacherCang进行代理---------------------- **************** dating 老板你好,这个我试过了,很不错,推荐给你! 身高可以,可以约! 老板你觉得怎样? 欢迎下次.....
5.责任链模式
5.1 应用场景
http web请求处理,请求过来后将经过转码、解析、参数封装、鉴权......一系列的处理(责任),而且要经过多少处理是可以灵活调整的。
将所有的处理都写在一个类中可否?
不行
分成多个类如何灵活组合在一起?
责任链:所有的处理者都加入到这个链式,一个处理完后,转给下一个。
责任链模式具体实现步骤:
1)抽象出责任接口,具体责任逻辑实现责任接口。
2)根据处理过程需要,将具体责任实现逻辑组合成链
3)使用者使用链
典型代表:Filter(过滤器)、Intercept(拦截器)
责任链模式类图:
和装饰者模式的区别在哪里?
装饰者模式意在功能的叠加,责任链模式意在链式的处理
eg:责任链模式代码示例
抽象出责任接口:
/** * * @Description: 责任接口 * @author leeSamll * @date 2018年11月25日 * */ public interface Responsibility { void process(Request request, ResponsibilityChain chain); }
具体的责任逻辑实现责任接口:
ResponsibilityA
package com.study.design.mode.samples.responsibility; /** * * @Description: 具体的责任逻辑实现 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityA implements Responsibility { @Override public void process(Request request, ResponsibilityChain chain) { //前置增强 System.out.println("Before Responsibility-A done something..."); //ResponsibilityA处理完以后调用ResponsibilityChain的process方法交给下一个责任逻辑处理 chain.process(request); //后置增强 } }
ResponsibilityB
package com.study.design.mode.samples.responsibility; /** * * @Description: 具体的责任逻辑实现 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityB implements Responsibility { @Override public void process(Request request, ResponsibilityChain chain) { //前置增强 System.out.println("Before Responsibility-B done something..."); //ResponsibilityB处理完以后调用ResponsibilityChain的process方法交给下一个责任逻辑处理 chain.process(request); //后置增强 } }
责任链:
ResponsibilityChain
package com.study.design.mode.samples.responsibility; import java.util.ArrayList; import java.util.List; /** * * @Description: 责任链,所有的责任加到责任链里面进行处理 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityChain { //存放具体的责任逻辑 private List<Responsibility> responsibilitys; private int index = 0; public ResponsibilityChain() { this.responsibilitys = new ArrayList<>(); } //顺序调用加入的责任逻辑,一个处理完以后交给下一个继续处理,下一个处理完以后会通过this回调process看是否有下一个继续处理 public void process(Request request) { if (this.index < this.responsibilitys.size()) { this.responsibilitys.get(index++).process(request, this); } } //加入具体的责任逻辑 public void register(Responsibility res) { this.responsibilitys.add(res); } }
请求接口:
package com.study.design.mode.samples.responsibility; /** * * @Description: 请求接口 * @author leeSamll * @date 2018年11月25日 * */ public interface Request { }
调用者调用示例
package com.study.design.mode.samples.responsibility; /** * * @Description: 调用者调用示例 * @author leeSamll * @date 2018年11月25日 * */ public class PlayGame { public static void main(String[] args) { //创建一个责任链 ResponsibilityChain chain = new ResponsibilityChain(); //往责任链里面加入具体的责任逻辑 chain.register(new ResponsibilityA()); chain.register(new ResponsibilityB()); //开始处理 chain.process(new Request() { }); } }
输出结果:
Before Responsibility-A done something...
Before Responsibility-B done something...
6. 适配器模式
6.1 应用场景
使用者依赖的接口与提供者的接口不匹配时,就加一层适配,而不改两端的代码。
适配器模式类图:
说明:
使用者使用Target接口,但是提供者Provider又没有实现Target接口,这个时候就需要加一层适配Adaper,Adaper里面持有Provider,在Adapter的methodA()方法里面调用Provider的methodB方法
和代理、装饰的区别在哪里?
适配器模式不进行功能增强
7. 外观(门面)模式
7.1 应用场景
使用方要完成一个功能,需要调用提供方的多个接口、方法,调用过程复杂时,我们可以再提供一个高层接口(新的外观),将复杂的调用过程向使用方隐藏。
外观(门面)模式类图:
这里使用了设计原则:最少知识原则(迪米特原则)
8. 观察者模式
8.1 示例:微信公众号,关注就可以收到推送的消息,取消关注,就不会再收到。
观察者模式类图:
说明:
主题Subject面向观察者接口Observer编程,主题里面可以添加、删除和通知观察者Observer;
注意每个观察者都有一个回调方法update,如果有变化就会在主题的notifyObservers()方法里面调用update方法,把最新的变化给到观察者
变化之处:观察者会变,观察者的数量会变。
不变:主题的代码要不受观察者变化的影响。
观察者模式定义:
定义了对象之间一对多的依赖关系,当一端对象改变状态时,它的所有依赖者都会收到通知并自动更新(被调用更新方法)。也称为:监听模式、发布订阅模式。提供一种对象之间松耦合的设计方式。
设计原则:为了交互对象之间的松耦合设计而努力!
8.2 Java中为我们提供了观察者模式的通用实现
Java.util. Observable 可被观察的(主题),具体主题扩展它。
java.util.Observer 观察者接口,具体观察者实现该接口。
主题Observable:
观察者接口Observer
使用代码示例:
package com.study.design.mode.samples; /** * * @Description: java中提供的观察者设计模式 * @author leeSamll * @date 2018年11月25日 * */ import java.util.Observable; import java.util.Observer; public class ObserverSample { public static void main(String[] args) { //创建主题 Observable subject1 = new Observable() { //通知观察者变化的数据data public synchronized void notifyObservers(Object data) { //设置 java.util.Observable.changed = true表示发生了改变 setChanged(); //调用父类的notifyObservers方法通知观察者发生变化 //调用链java.util.Observable.notifyObservers(Object)->java.util.Observer.update(Observable, Object) super.notifyObservers(data); } }; //添加观察者 subject1.addObserver(new Observer() { //主题回调观察者的update方法通知改变 @Override public void update(Observable o, Object arg) { System.out.println("观察者1收到通知被更新了..." + arg); } }); //添加观察者 subject1.addObserver(new Observer() { //主题回调观察者的update方法通知改变 @Override public void update(Observable o, Object arg) { System.out.println("观察者2收到通知被更新了..." + arg); } }); //通知改变 subject1.notifyObservers("change1"); subject1.notifyObservers("change2"); } }
输出结果:
观察者2收到通知被更新了...change1
观察者1收到通知被更新了...change1
观察者2收到通知被更新了...change2
观察者1收到通知被更新了...change2
9. 命令模式
示例:
请为你的系统设计一个命令行界面,用户可输入命令来执行某项功能。
系统的功能会不断增加,命令也会不断的增加。
如何将一项一项的加入到这个命令行界面?
如何让我们的命令程序写好以后,不因为功能的添加而修改,又可灵活的加入命令、功能。
命令模式类图:
命令模式的定义:
以命令的方式,解耦调用者与功能的具体实现者,降低系统耦合度,提供了灵活性。
适用场景:Servlet、Controller、线程池
命令模式伪代码示例:
package com.study.design.mode.samples.command; /** * * @Description: 命令模式 * @author liguangsheng * @date 2018年11月25日 * */ public class Receiver { //存放具体的命令实现 private Map<String,Command> commands; //把具体的命令和对应的实现加入commands public void register(String strCommand,Command command) { commands.put(strCommand,command); } //使用者调用receive方法传入命令去执行 public void receive(String command) { Command commandObj = commands.get(command); if(null != commandObj) { commandObj.exceute(); return; } System.out.println("不支持此命令" + command); } }
命令模式与策略模式的区别:
命令模式类图:
策略模式类图:
区别:
策略模式侧重的是一个行为的多个算法的实现,可互换算法。
命令模式侧重的是为多个行为提供灵活的执行方式
10. 状态模式
示例:一个类对外提供了多个行为,同时该类对象有多种状态,不同的状态下对外的行为表现不同,我们该如何来设计该类,让它对状态可以灵活扩展?
如请为无人自动咖啡售卖机开发一个控制程序。
说明:用户可以在咖啡机上进行支付、退款、购买、取咖啡等操作
咖啡机状态转换图:
说明:
不同的状态下这四种操作将有不同的表现。如在没有支付的状态下,用户在咖啡机上点退款、购买、取咖啡,和在已支付的状态下做这三个操作。
普通实现:
package com.study.design.mode.samples.state; /** * * @Description: 普通的咖啡机: 没有使用状态模式的咖啡机 * @author liguangsheng * @date 2018年11月25日 * */ public class CoffeeMachine { final static int NO_PAY = 0; final static int PAY = 1; final static int SOLD = 2; final static int SOLD_OUT = 4; private int state = SOLD_OUT; private int store; public CoffeeMachine(int store) { this.store = store; if (this.store > 0) { this.state = NO_PAY; } } public void pay() { switch (this.state) { case NO_PAY: System.out.println("支付成功,请确定购买咖啡。"); this.state = PAY; break; case PAY: System.out.println("已支付成功,请确定购买咖啡。"); break; case SOLD: System.out.println("待取咖啡中,请稍后购买!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可购买!"); } } public void refund() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,请不要乱按!"); break; case PAY: System.out.println("退款成功!"); this.state = NO_PAY; break; case SOLD: System.out.println("已购买,请取用!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可购买!"); } } // 购买 public void buy() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,请不要乱按!"); break; case PAY: System.out.println("购买成功,请取用!"); this.state = SOLD; break; case SOLD: System.out.println("已购买,请取用!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可购买!"); } } // 取coffee public void getCoffee() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,请不要乱按!"); break; case PAY: System.out.println("已购买,请取用!"); break; case SOLD: System.out.println("请放好杯子,3秒后将出咖啡!"); this.store--; if (this.store == 0) { this.state = SOLD_OUT; } else { this.state = NO_PAY; } break; case SOLD_OUT: System.out.println("咖啡已售罄,不可购买!"); } } }
如何让状态可以灵活扩展?
从分析可以看出,变化的是状态,同时不同的状态同一个行为的表现不同,这样的话就可以把变化的状态抽象生成接口,然后不同的状态行为实现状态接口做该状态下的具体行为。这里可以采用状态模式
状态模式类图:
状态模式代码示例:
把变化的状态抽象生成接口State,里面含有不同状态下的行为方法
package com.study.design.mode.samples.state; /** * * @Description: 状态接口 * @author liguangsheng * @date 2018年11月25日 * */ public interface State { void pay(); void refund(); void buy(); void getCoffee(); }
不同的状态实现状态接口State
没有支付状态
package com.study.design.mode.samples.state; /** * * @Description: 没有支付状态 * @author liguangsheng * @date 2018年11月25日 * */ public class NoPayState implements State { private NewCoffeeMachine machine; public NoPayState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("支付成功,请去确定购买咖啡。"); this.machine.state = this.machine.PAY; } @Override public void refund() { System.out.println("你尚未支付,请不要乱按!"); } @Override public void buy() { System.out.println("你尚未支付,请不要乱按!"); } @Override public void getCoffee() { System.out.println("你尚未支付,请不要乱按!"); } }
已支付状态
package com.study.design.mode.samples.state; /** * * @Description: 已支付状态 * @author liguangsheng * @date 2018年11月25日 * */ public class PayState implements State { private NewCoffeeMachine machine; public PayState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("您已支付,请去确定购买!"); } @Override public void refund() { System.out.println("退款成功,请收好!"); this.machine.state = this.machine.NO_PAY; } @Override public void buy() { System.out.println("购买成功,请取用"); this.machine.state = this.machine.SOLD; } @Override public void getCoffee() { System.out.println("请先确定购买!"); } }
售出状态
package com.study.design.mode.samples.state; /** * * @Description: 售出状态 * @author liguangsheng * @date 2018年11月25日 * */ public class SoldOutState implements State { private NewCoffeeMachine machine; public SoldOutState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("当前状态为售出,请取咖啡!"); } @Override public void refund() { System.out.println("当前状态为售出,不能退款!"); } @Override public void buy() { System.out.println("当前状态为售出,请取咖啡!"); } @Override public void getCoffee() { System.out.println("咖啡已出,请取咖啡!"); } }
售罄状态
package com.study.design.mode.samples.state; /** * * @Description: 售罄状态 * @author liguangsheng * @date 2018年11月25日 * */ public class SoldState implements State { private NewCoffeeMachine machine; public SoldState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("咖啡已卖完,不能支付!"); } @Override public void refund() { System.out.println("不能退款!"); } @Override public void buy() { System.out.println("咖啡已卖完,不能购买!"); } @Override public void getCoffee() { System.out.println("咖啡已卖完!"); } }
使用了状态模式的咖啡机
package com.study.design.mode.samples.state; /** * * @Description: 使用了状态模式的咖啡机 * @author liguangsheng * @date 2018年11月25日 * */ public class NewCoffeeMachine { final State NO_PAY, PAY, SOLD, SOLD_OUT; State state; int store; //初始化状态 public NewCoffeeMachine(int store) { NO_PAY = new NoPayState(this); PAY = new PayState(this); SOLD = new SoldState(this); SOLD_OUT = new SoldOutState(this); this.store = store; if (this.store > 0) { this.state = NO_PAY; } } //支付行为委托给当前状态实例 public void pay() { this.state.pay(); } //退款行为委托给当前状态实例 public void refund() { this.state.refund(); } //买咖啡行为委托给当前状态实例 public void buy() { this.state.buy(); } //取咖啡行为委托给当前状态实例 public void getCoffee() { this.state.getCoffee(); } }
状态模式、命令模式、策略模式的区别
状态模式类图:
命令模式类图:
策略模式类图:
区别:
状态模式应用于状态机的情况
策略模式侧重的是一个行为的多个算法的实现,可互换算法。
命令模式侧重的是为多个行为提供灵活的执行方式
11. 桥接模式
11.1 示例:
请开发一个画图程序,可以画各种颜色不同形状的图像,请用面向对象的思想设计图像
分析:
1)比如有红、黄、蓝三种颜色
2)形状有方形、圆、三角形
3)圆可以是红圆、黄圆、蓝圆
变化:
会从两个维度发生变化:形状、颜色
任其在这两个维度各自变化,为这两个维度搭个桥,让他们可以融合在一起:桥接模式
桥接模式的实现步骤:
1)抽象:分别对各自的维度进行抽象,将共同部分抽取出来
2)组合:将抽象组合在一起(桥接)
桥接模式的定义:将多个维度的变化以抽象的方式组合在一起。使用者面向抽象。个维度间解耦,可自由变化。
12. 单例模式
12.1 饥汉式——可用
饥汉式1——可用
package com.study.design.mode.service; public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } }
饥汉式2——可用
package com.study.design.mode.service; public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return instance; } }
12.2 懒汉式
懒汉式1——不可用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
当两个线程同时进入if里面时就会创建两个实例,不是单例,线程不安全,所以不可用
懒汉式2——不推荐使用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
线程安全,但不推荐使用。缺点是实例化后就不应该再同步了,效率低
懒汉式3——不可用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
当两个线程同时进入if里面时就会产生两个实例,做不到单例
懒汉式4——双重检查——推荐使用
package com.study.design.mode.service; public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
注意:volatile关键字修饰很关键,保证可见性,一个线程先创建了,其他线程就就会看到这个改变,不会再创建,如果没有这个关键字还是不能保证单例。
优点:线程安全;延迟加载;效率较高
懒汉式5——静态内部类方式——推荐使用
package com.study.design.mode.service; public class Singleton { private Singleton() { } private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
优点:避免了线程不安全,延迟加载,效率高
原理:类的静态属性只会在第一次加载类的时候初始化。在这里,JVM的加载机制帮助我们保证了线程安全性,在类进行初始化时,别的线程是无法进入的
懒汉式6——用枚举——推荐使用
package com.study.design.mode.service; public enum Singleton { INSTANCE; public void whateverMethod() { } }
13. 模板方法设计模式
示例:
当我们设计一个类时,我们能明确它对外提供的某个方法的内部执行步骤,但一些步骤,不同的子类有不同的行为时,我们该如何来设计该类?
可以用模板方法设计模式
优点:
1)封装不变的部分,扩展可变的部分
2)提取公共代码,便于维护。
3)行为由父控制,子类实现。
适用场景:
1)有多个子类共有的方法,且逻辑相同
2)重要的、复杂的方法,可以考虑作为模板方法
模板方法设计模式代码示例:
package com.study.design.mode.service; public abstract class Game { protected abstract void initialize(); protected abstract void startPlay(); protected abstract void endPlay(); // 模板方法 public final void play() { // 初始化游戏 initialize(); // 开始游戏 startPlay(); // 结束游戏 endPlay(); } }
四、总结
设计模式总结
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
行为型模式
这些设计模式特别关注对象之间的通信。
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
创建型模式:主要用来创建实例的,创建实例时不要跟具体类捆绑,而是通过工厂等来创建,从而使用者就和具体类解耦了
结构型模式:主要说的是如何来组合利用,把多个实例组合在一起
行为型模式:主要是多种行为、多种功能的的变化,怎么把多个行为功能组合在一起
设计原则总结
1. 变化隔离原则:找出变化,分开变化和不变的
隔离,封装变化的部分,让其他部分不受它的影响。
2. 面向接口编程 ——隔离变化的方式
使用者使用接口,提供者实现接口。“接口”可以是超类!
3. 依赖倒置原则(里氏替换原则)——隔离变化的方式
依赖抽象,不要依赖具体类!
4. 开闭原则:对修改闭合,对扩展开放——隔离变化的方式
可以继承一个类或者接口扩展功能,但是不能修改类或者接口的原有功能
5. 最少知道原则,又称迪米特法则
6. 多用组合,少用继承——灵活变化的方式
“有一个”可能比“是一个”更好。
7. 单一职责原则——方法设计的原则
每个方法只负责一个功能,不要把很多功能写在一个方法里面
最后,如果都忘记了,请一定要记住这三条
说明:如果前面的设计思想和设计原则都忘记了,就要找出变化,区分出不变的和变化的,把变化的部分独立出接口,或者使用组合
示例代码获取地址:
https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/design-mode-study
参考文章: