框架源码系列一:设计模式(设计思想、设计原则、各种设计模式介绍、设计模式总结)
要分析常用框架spring、mybatis、springboot、springcloud等的源码,首先要了解各种设计模式,因为框架里面应用了各种设计模式
一、设计思想学习设计模式最重要的是掌握设计思想和设计原则,理解了设计思想和设计原则并运用到平时的编码中是最重要的!!!
1. 我们先来看下面的问题:天天加班编程,编程到底都做的是什么?
撸代码,加班撸代码,写接口、写类、写方法
用设计模式或做设计的作用是什么?指导、规定该如何撸代码,如何来写接口、写类、写方法
为什么要做设计、用设计模式?
代码会变,为应对变化,为了以后方便扩展。做到以不变应万变,做一个会偷懒的程序员!
2.下面来看一下什么是设计思想
首先是从现实出发理清现实,在写代码之前先从实际分析,然后就开始写代码,写代码时要区分出不变的代码和会变化的代码,会变得代码会怎么变,使用者如何隔绝这种变化,所谓的隔绝这种变化就是不让调用者感知到内部的变化,只需要很简单的方式就能使用不必关心内部的逻辑,这样的话就要用到各种设计模式。不同的变化方式对应不同的设计模式。
设计的最终体现:如何来定义类、接口、方法
3. 设计思想—OOP3.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改怎么来写呢
@Servicepublic class OrderService { public Order prepareOrder(Order order, String promotion) { // 该如何写 return order; }}1.2 这样可以吗?@Servicepublic 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 改进一下,这样是不是好些了?@Servicepublic 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@Servicepublic 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 通过一个工厂来专门负责创建各种促销计算实现,就把变化移出来了!@Componentpublic 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(); ...... } }}这是简单工厂模式:所有产品由一个工厂创建
@Servicepublic 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配置:
PromotionCalculationFactory 工厂改写:
@Componentpublic 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;@Componentpublic class PromotionCalculationFactory { private Map 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
@Servicepublic 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);}输出结果:
老板,这个我试过了,很不错,推荐给你!身高可以,可以约!老板,你觉得怎样,欢迎下次再约!
静态代理缺点:
扩展能力差
横向扩展:代理