设计模式再总结

愿景

话术可能有一些抽象对入门不是特别友好,主要针对已经在后端业务开发过多年,有设计模式应用的意识,对依赖倒置有一些使用经验的同学。

旨在精简每一种设计模式的场景和方法,抛弃教科书式的解释方式,力求言简意赅。反复体悟、备忘,增强设计模式的思想,在开发中做到灵活运用而增强代码易扩展易维护的目的。

1.类之间的关系(uml类图)及耦合强弱比较

依赖(狭义上的,指局部变量、方法的参数或者对静态方法的调用)<关联(聚合、组合)<接口实现=继承泛化(抽象类继承、普通类继承)

广义上的依赖上述都是

 

2.设计模式的原则

单一原则、接口隔离、依赖(具象到构造函数、setter方法、方法参数传递三种方式)倒置(多态)、里氏替换、开闭原则、迪米特原则、合成复用原则

从uml类关系的角度分析,其实依赖倒置也能称之为关联倒置 

3.设计模式的分类及一句话概括

创建型类


3.1.单例模式

使用场景:希望一个类在当前的jvm中只有一个实例

实现方式:静态变量、静态代码块、懒加载线程不安全、懒加载+同步锁、懒加载+同步锁+volatile(推荐3🌟)、内部静态类(推荐4🌟)、枚举(推荐5🌟)

 

3.2.工厂模式

使用场景:希望一群可以倒置的类集合,可以方便的拿到想要的具体类

实现方法:简单工程、工厂方法、抽象工厂。类倒置,工厂也可以继续倒置。

 

3.3.原型模式

使用场景:希望快速复刻一个实例的当前状态

实现方法:jvm的cloneable浅复制 或序列化深度复制

 

3.4.建造者模式

使用场景:希望一个有几种固定的处理方式的类,把处理方式集中到建造类

实现方法:建造类关联一个目标新实例,在建造类中对新实例做处理;建筑类倒置可以选择很多不同的建筑类;记得最后return处理完成的目标实例对象

 

结构型类

 

3.5.适配模式(定义规范)

使用场景1:调用的目标方法不支持修改 但又要适配使用者

实现方法:类适配模式、对象适配模式;adapter继承或者关联调用的目标,再新增的方法内使用目标方法并增加自己的适配逻辑;adapter设计成倒置可以选择很多不同的适配类;

使用场景2: 我们在设计之初便想好了接口规范,让使用者可以灵活自己实现匿名内部类来自定义处理逻辑

实现方法:adapter作为抽象类实现定义的接口并空实现,adapter便可以随意重写任意方法(不还是倒置吗,只不过比单纯的倒置多了可选择实现的小技巧)

启发:通过继承抽象类,抽象类空实现接口的方式,可以任意选择重写方法。

 

3.6.桥接模式(接口的打散重组)

使用场景:在实现一个接口时,发现有部分的方法和具体实现类本身没有必然一对一关系(即不是新增一个实现类就必须重新实现所有新的方法,可能之前有一部分是可以复用的),考虑将这部分方法拆分成新的接口,按需组合。

实现方法:此时新接口的实现类通过关联的方式放入原来的接口,原来的接口就要通过抽象类的方式去倒置和接收新接口能力。如果“这部分接口”是多个集合那可以拆成多个。在某种情况下,这部分接口只有一个集合,且只有base跟other两种场景那么可以直接用抽象类直接完成该需求。

 

3.7.装饰器模式(递归加包递归拆包)

在说装饰器之前先感受一下类的两种递归。

public  class A {
    private A a;
    private String name;

    public A(A a, String name) {
        this.a = a;
        this.name = name;
    }
}

public class B {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
}


public class C extends B {
    private B b;

    private String decorate;

    public C(B b, String decorate) {
        this.b = b;
        this.decorate = decorate;
    }
}
public class Test {
    public static void main(String[] args) {


        A pathA = new A(new A(new A(new A(null, "小麦"), "爸爸"), "爷爷"), "祖宗");

        B b = new B();
        b.setName("小麦");

        C pathC = new C(new C(new C(new C(b, "学习数学知识"), "学习物理知识"), "学习化学知识"), "学习历史知识");

    }
}
 

此时如果A变成了抽象类进行倒置,能带来什么?此时如果B,C变成抽象类,能带来什么?B、C的结构很容易联想到B是一个等待装饰的对象,而C就是装饰的具体内容,C一层一层的装饰B。将B、C改成抽象类倒置之后,能力就能扩展、按需分配

使用场景:所以当一个类,需要对它进行不确定几次递归的信息附件添加时,就非常适合使用装饰模式。或者叫它递归加包模式怎么样?

实现方法:参考上述B、C的代码

 

3.8.组合模式(统一递归树)

依然还是上述两种递归中的A类,举一个例子,日常开发过程中的数据库部门表的树形关系一般直接用部门包含List<部门>;如果把A类变成抽象类或者接口做倒装处理,那么具有相同类型的类集合就可以统一成树形结构。比如建筑-楼层-区域三张表的关系建筑聚合楼层list 楼层聚合区域list的关系统一成父节点聚合子节点的关系。其实还是依赖倒装。

使用场景:当有一群类,具有相似的属性并存在上下级树形关系

实现方法:抽象一个抽象父类,并聚合自己的集合,间接得省去了对不同层级的策略模式

 

3.9.外观模式(内聚同层业务类)

使用场景:让类的使用者和被使用者有更明确的上下级分层关系,其实这在平时开发中都无意中用到了,controller-service-dao就是很典型的facade模式

实现方法:将下级的类关联到一起,在这个上级类种统一组装方法的调用,而使用者只要调用这个上级类即可。

 

3.10.享元模式(实例化对象集合池的管理)

使用场景:对实例化较为消耗资源的类,将它的实例做集合管理,池的概念。至于管理逻辑可以自由定义(1.jvm的String、几种基础包装类的池化技术 2.jdbc链接池 核心思想就是集中管理不需要重复实例化)

实现方法:用一个管理类维护一个类的实例化对象集合

 

3.11.代理模式(切片编程)

使用场景1:当一个接口已经被一个实现类具体实现,想要对具体实现类的某些方法执行的前、后 织入一些代码

实现方法:新建一个同样实现了这个接口的代理类,组合该实现类,手动的对每一个方法重写,其中就是 前 + 实现类方法 + 后;缺陷:每遇到一次这种情况就要写一个代理类;

使用场景2:类似场景1的接口有很多,不希望写很多的代理类,能根据传入的接口class信息动态生成代理类

实现方法:jdk的newProxyInstance,原理其实就是使用场景1的实现方法,只是根据接口类的反射信息动态帮你生成了代理类;缺陷:类必须要实现某一个接口,也就是说只能代理某接口的某实现类;

使用场景3:想要对任意一个类的方法执行的前、后 织入一些代码

实现方法:cglib包通过继承的关系去做代理,而不是通过接口+组合的关系,没有了接口的限制,但是有了继承的限制,简单来说被代理的对象不能是final不可继承的 方法也不能是final不可重写的。spring aop就是用这个方法来做的,想想日志的切片编程,是不是任意类都能切入。

 

行为型类

 不要被行为型设计模式的名字吓到!不要被行为型设计模式的名字吓到!不要被行为型设计模式的名字吓到!行为型到底还是依赖倒置,只是小小的加了一点继承、聚合、引用。理解以后,根据类的需求关系出发也能直接使用到相关的设计模式,甚至你自己都不知道已经在用了,当然ocp思想在,具体的实现略有差别很正常。


3.12.模版模式(定义接口规范及模版方法)

使用场景:使用场景和适配模式场景2非常的相似,除了定义好规范之外,还要对已定义好的方法做好组合关系,提供模板方法,这就是模版模式,当然这里的倒置用抽象类。

实现方法:定义好方法的抽象类,并在抽象类种定义好方法之间的调用顺序的模版方法。实现者只需要实现方法,调用模版方法即可。

3.13.命令模式(定义命令接口规范)

使用场景:定义命令的接口规范,对一批实现了该接口的命令实现类做统一管理

实现方法:对命令类做依赖倒置

3.14.访问者模式(定义匿名内部类的接口规范并引用当前宿主)

使用场景:使用场景依然和适配模式场景2非常的相似,此时宿主也是继承抽象类的倒置设计,而定义的规范方法需要根据宿主的不同实现随之改变

实现方法:其实还是依赖倒置,将接口或者抽象类通过方法引用的方式注入,并引用当前宿主实现类的对象,即this,然后就可以在匿名内部类操作this的方法!

启发:匿名内部类的构造函数引用宿主实现关联;


3.15.迭代器模式(定义匿名内部类的接口规范并引用当前宿主)

使用场景:有几个类,都聚合了自己的业务对象的集合(或者数组)想要统一这几个类的递归的能力

实现方法:简单的可以将这几个类都实现Iterator接口;优雅的可以再新增一个MyIterator去实现Iterator接口,然后聚合到宿主类,在MyIterator构造函数中加入那些集合(或者数组),这样宿主就只需要聚合自己的MyIterator,接口的实现放在MyIterator,层级更清晰,java.util.collection里的集合也是这么做的。

启发:当类想要实现某些方法而不想在本类种增加代码量时,可以通过聚合新的类,让新的类来实现方法。有点桥接模式的味道。此新类的构造函数可能引用到宿主类下的成员变量,又有点访问者模式的味道。


3.16.观察者模式(定义接受通知的接口规范)

使用场景:当一个类的方法需要遍历调用几个依赖倒置的实现类时,可以通过集合来管理并遍历请求

实现方法:可以继承jdk提供的Observerable类,管理几个实现了Observer的实现类,通知遍历调用实现类的update方法

启发:其实跟命令模式很像,都是管理几个依赖倒置的实现类,只不过业务规则不太一样,管理的逻辑不一样;类之间的关系是一样的。


3.17.中介者模式(定义被调用者的接口规范)

使用场景:当一个类的方法需要有选择的调用几个类时,可以通过集合来管理并请求;

实现方法:将这几个被调用的类做依赖导致继承抽象类,抽象类引入这个类对象,初始化构造函数就放入这个类对象的集合管理中;通过取集合对象强转具体实现类的自定义方法来完成业务调用(一对多);因为聚合的宿主对象,也可以在这些实现类中调用宿主方法,此时可以把这个方法抽象到抽象类中,宿主根据instance判断是谁的请求再做对应的操作(一对一)。

启发:依然还是对依赖导致类的集合管理,这里有一个引入宿主对象,在构造函数时放入宿主读对象内部集合的小技巧,完全可以应用在命令模式、观察者模式中

3.18.备忘录模式

使用场景:当需要记录一个类的历史状态时

实现方法:聚合并管理该类的多个状态的集合

3.19.解释器模式(统一递归树)

使用场景:(((1-2)*3)/(4+5))=? 可以把(left+action_right)当成一个express对象层层递归,其实就是一颗二叉树,叶子节点是数字,父节点是运算符。这里我们的重点不是去理解运算过程的栈的先进后出,递归运算的时候实际上完成了先进后出,因为我们肯定是从节点一步一步向上传递结果的。

实现方法:简单的,通过node类聚合本身实现递归加减乘除,更优雅的,将node做成抽象类聚合本身,然后依赖倒置出加减乘除四个node实现类和纯数字类,将运算逻辑也内聚。

启发:抽象类的意思除了依赖倒置,有时候还是递归的好帮手,装饰器和组合模式也能看得出来。

引申:继续引申,这个树可以不局限于二叉树,通过集合管理left right & other,将运算或者业务规则依赖倒置,递归出结果。哈哈是不是回到了组合模式。action不就是各自的业务策略吗。

3.20.状态模式(定义状态的接口规范)

使用场景:当一个对象的状态机出现较为复杂的调度时,可以引入该设计模式

实现方法:通过依赖倒置并管理每一种状态的实现类,通过引入中间对象context上下文,即包含当前状态,又可以调用当前状态的方法,通过传递context在调用状态方法时再修改当前状态

启发:特殊的实现了抽象类的上下文的并聚合抽象类的小技巧,可以方便的管理currentobject,还能符合定义的接口规范

3.21.策略模式(定义策略的接口规范)

使用场景:依赖倒置就是策略模式

实现方法:单调的,通过组合或聚合一个接口的实现类(适配模式匿名内部类没有什么本质的区别),现实中,一般还会增加一个管理类去分流选择实现类,各个实现类聚合到管理类中去。

3.22.责任链模式(定义处理方法的接口规范)

使用场景:当一个请求要经过特定的处理流程时,需要将处理流程内聚解耦

实现方法:和组合模式一样,将处理流程依赖倒置,父子聚合递归,将请求层层传递处理。

引申:处理流程可以是固定的不需要判断是否扔出给下一个而是全部执行,请求内容也可以不停变化。处理流程也不一定是单链的,父可以聚合实现类集合,判断流转给哪一个处理流程的实现类。

4.总结:

a.通过继承泛化抽象类、实现抽象接口的多态特性实现倒置,通过依赖、关联的方式实现注入; 这里的“倒”指抽象类或者接口跟具体实现类的反向;

b.特别强调,spring的ioc(控制反转)容器可以帮助我们更优雅的实现依赖、关联注入;这里的“反”指的宿主对象和关联注入的对象,从原来的宿主主动new变成了被动接受关联注入对象的反向;

c.在依赖关系中,普通类继承的耦合要大于关联,尽量避免使用普通类继承(不是说倒置场景中的多态抽象类继承);

d.可以灵活使用泛型和反射

posted @ 2022-11-01 13:12  大背头  阅读(45)  评论(0编辑  收藏  举报