1.什么是设计模式
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
2.为什么要学习设计模式
看懂源代码:如果你不懂设计模式去看Jdk、Spring、SpringMVC、IO等等等等的源码,你会很迷茫,你会寸步难行
看看前辈的代码:你去个公司难道都是新项目让你接手?很有可能是接盘的,前辈的开发难道不用设计模式?
编写自己的理想中的好代码:我个人反正是这样的,对于我自己开发的项目我会很认真,我对他比对我女朋友还好,把项目当成自己的儿子一样(我目前没有儿子)
3.设计模式的分类
4.设计模式的六大原则
1.单一职责原则 SRP
实现类要职责单一:如果一段代码块(函数 类 模块)负责多个功能,那么当 A 功能需求发生改变的时候改动了代码,就有可能导致 B 功能出现问题,所以一段代码块只应该负责一个职责。
2.开放-封闭原则 OCP
要对扩展开放,对修改关闭:通过修改老代码来实现新功能可能导致老模块出现 BUG,所以我们应该通过开发新代码块来实现新功能。
3.里氏替换原则 LSP
不要破坏继承体系:程序中的子类应该可以替换父类出现的任何地方并保持预期不变。所以子类尽量不要改变父类方法的预期行为。
4.接口隔离原则 ISP
设计接口的时候要精简单一:当类 A 只需要接口 B 中的部分方法时,因为实现接口需要实现其所有的方法,于是就造成了类 A 多出了部分不需要的代码。这时应该将 B 接口拆分,将类A需要和不需要的方法隔离开来。
5.依赖倒置原则 DIP
面向接口编程:抽象不应该依赖细节,细节应该依赖于抽象。核心是面向接口编程,我们应该依赖于抽象接口,而不是具体的接口实现类或具体的对象。
注意:上面的 SOLID 又称为5大设计原则
6.最少知识原则(迪米特原则)LOD
降低耦合度:一个类或对象应该对其它对象保持最少的了解。只与直接的朋友(耦合)通信。
5.设计模式
创建型模式
1.单例模式
应用场景
处理资源访问冲突、用来创建全局唯一类。(枚举类)
解决方案
懒汉式:用到的时候才创建(场景:不一定需要用到、创建代价大、延迟加载、需要快速启动系统)。
饿汉式:系统启动时创建(场景:必定用到、临时创建影响响应速度)。
多例:同一类的固定个数相同实例。
2.1.简单工厂模式
简单工厂模式就是一个工厂,根据输入的不同参数,返回派生自同一个父类的实例对象。输入面条,就输出兰州拉面,输入鸡肉,就输出黄焖鸡,兰州拉面和黄焖鸡都是继承自food类。
2.2.工厂模式
工厂可能不止一个,比如有中国美食工厂和美国美食工厂,中国工厂可以制作兰州拉面和黄焖鸡,美国工厂可以制作意大利面和肯德基炸鸡,两个工厂的原材料都是面条和鸡肉,则我在输入面条和鸡肉之前就先要选定工厂,确定到底是要吃肯德基还是黄焖鸡,所以工厂模式比简单工厂多一步选工厂。
3.抽象工厂模式
吃个面条鸡肉这种简单的事情,用工厂或者简单工厂模式就可以解决了,但是涉及到造一台电脑这种事情,工厂已经不能满足了,因为电脑需要组装,不同工厂产出的cpu、主板可能不兼容。这就要用到抽象工厂概念,这里我们定义一个电脑工厂(苹果电脑工厂、联想电脑工厂。。。。。。),自己生产主板、cpu,这样我造出来的电脑一定是兼容的。缺点也很明显,如果某天电脑要外接一个显示器,那么每个电脑工厂内部(苹果电脑工厂、联想电脑工厂。。。。。。)都需要额外增加一个显示器工厂,这有点违反了对修改关闭,对扩展开放这个设计原则。
工厂模式总结:
工厂模式分为简单工厂、工厂方法、抽象工厂模式
简单工厂 :用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
工厂方法 :用来生产同一等级结构中的固定产品。(支持拓展增加产品)
抽象工厂 :用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
4.建造者模式
经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物。建造者模式其实有很多的变种,但是对于客户端来说,我们的使用通常都是一个模式的:
Food food = new FoodBuilder().a().b().c().build();
套路就是先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了。
说实话,建造者模式的链式写法很吸引人,但是,多写了很多“无用”的 builder 的代码,感觉这个模式没什么用。不过,当属性很多,而且有些必填,有些选填的时候,这个模式会使代码清晰很多。我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验,代码要优雅一些。
5.原型模式
简单的说,原型模式就是把现有的对象复制一份,类似于孙悟空拔一根毫毛变成好多孙悟空,分为深克隆和浅克隆两种情况,浅克隆即只克隆对象的八大基本类型属性和引用,类比于孙悟空变出来的猴子只是长得和孙悟空一样,孙悟空死了这些变出来的也就没了,深克隆则是完全复制了一个新的对象,新对象的行为不影响原对象,类似于用孙悟空变出一个六耳猕猴,完全一样,打死孙悟空六耳猕猴不会死。
应用场景
1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。这时我们就可以通过原型拷贝避免这些消耗。
2. 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
我们Spring框架中的多例就是使用原型。
原型模式的使用方式
1. 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
2. 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
原型模式分为浅复制和深复制
1. (浅复制)只是拷贝了基本类型的数据,而引用类型数据,只是拷贝了一份引用地址。
2. (深复制)在计算机中开辟了一块新的内存地址用于存放复制的对象。
创建型模式总结
简单工厂模式最简单;工厂模式在简单工厂模式的基础上增加了选择工厂的维度,需要第一步选择合适的工厂;抽象工厂模式有产品族的概念,如果各个产品是存在兼容性问题的,就要用抽象工厂模式。单例模式就不说了,为了保证全局使用的是同一对象,一方面是安全性考虑,一方面是为了节省资源;建造者模式专门对付属性很多的那种类,为了让代码更优美;原型模式用得最少,了解和 Object 类中的 clone() 方法相关的知识即可。
结构型模式
前面创建型模式介绍了创建对象的一些设计模式,这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。
6.代理模式
代理模式说白了就是方法的包装和方法增强,公司要去跟客户谈业务,一般老板不会去,排个代理去干就行了。对外暴露的接口就是这样,真实的实现不在接口里,而是在接口的实现里。
代理模式应用场景
Spring AOP、日志打印、异常处理、事务控制、权限控制等
代理的分类
静态代理(静态定义代理类)
动态代理(动态生成代理类,也称为Jdk自带动态代理)
Cglib 、javaassist(字节码操作库)
三种代理的区别
1. 静态代理:简单代理模式,是动态代理的理论基础。常见使用在代理模式
2. jdk动态代理:使用反射完成代理。需要有顶层接口才能使用,常见是mybatis的mapper文件是代理。
3. cglib动态代理:也是使用反射完成代理,可以直接代理类(jdk动态代理不行),使用字节码技术,不能对 final类进行继承。(需要导入jar包)
7.适配器模式
适配器模式换句话说就是中间商的中间商赚差价问题。甲公司有个大单子承包给了乙,乙是个皮包公司,啥也不干,丙是乙的子公司,但是丙干不了全部,只能接其中一部分,乙把其中丙能干的那一部分给了丙,就是对丙来说,去做自己能做的就是适配器模式。对应到代码层就是甲是一个对外暴露的接口,乙类实现了甲的接口,但是没有具体实现方法,丙继承了乙接口,实现自己需要的方法。
适配器模式总体来说分三种:默认适配器模式、对象适配器模式、类适配器模式。
适配器模式总结
类适配和对象适配的异同
一个采用继承,一个采用组合;
类适配属于静态实现,对象适配属于组合的动态实现,对象适配需要多实例化一个对象。
总体来说,对象适配用得比较多。
适配器模式和代理模式的异同
比较这两种模式,其实是比较对象适配器模式和代理模式,在代码结构上,它们很相似,都需要一个具体的实现类的实例。但是它们的目的不一样,代理模式做的是增强原方法的活;适配器做的是适配的活,为的是提供“把鸡包装成鸭,然后当做鸭来使用”,而鸡和鸭它们之间原本没有继承关系。
8.桥梁模式
java中的JDBC是最典型的桥梁模式,桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化,假设有圆形、长方形、正方形三种图形,有白、黑、红三种颜色,如何产生白色圆形?抽象一个图形、颜色的概念,用图形和颜色的概念去实现图形和颜色的组合就是桥接模式,好处是解耦,坏处是难以抽象两个完全不想关的需要组合的量。
优点
分离抽象接口及其实现部分。提高了比继承更好的解决方案。
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
实现细节对客户透明,可以对用户隐藏实现细节。
缺点
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
使用场景
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
模式总结
桥接模式实现了抽象化与实现化的脱耦。他们两个互相独立,不会影响到对方。
对于两个独立变化的维度,使用桥接模式再适合不过了。
对于"具体的抽象类"所做的改变,是不会影响到客户。
9.装饰模式
装饰模式其实就是个锦上添花的作用,假设蜜雪冰城的饮料分为红茶、绿茶、咖啡三大类,在此基础上又有金桔绿茶,金桔柠檬红茶,加糖咖啡,加奶咖啡等等,这些都是在三大类上的扩展装饰,没有这些也能喝,加上更好喝。定义基础饮料类,红茶、绿茶、咖啡类以及每一类的价格,再定义装饰类,装饰类依赖于三大基础类,比如金桔加在红茶里加2块钱,加在绿茶里加三块钱,调用类里只需要传入哪种饮料并加入哪种装饰配料就可以了。
10.门面模式(外观模式)
从字面意思理解即可,小区有个门卫,他知道小区里所有住户的名字,别的小区所有来小区找人的人,都要经过保安的传达,保安知道你去找谁后,通知1号楼二单元***的住户,让他出来接待。门卫就是门面模式。这样减少了系统之间的依赖,提高了灵活性,提高了安全性(外面的人窥探不到小区内部的情况,自然相当安全)。
门面模式(也叫外观模式,Facade Pattern)在许多源码中有使用,比如 slf4j 就可以理解为是门面模式的应用。
门面模式的优点显而易见,客户端不再需要关注实例化时应该使用哪个实现类,直接调用门面提供的方法就可以了,因为门面类提供的方法的方法名对于客户端来说已经很友好了。
11.组合模式
组合模式用于表示具有层次结构的数据,比如文件夹下有文件和子文件夹,一层层的嵌套,俄罗斯套娃,这时不管哪一级文件结构需要同样对待的时候就可以采取组合模式,这样就不用递归了。通常需要add(node)、remove(node)、getChildren() 这些方法。
组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性。
直接看一个例子吧,每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。
12.享元模式
享元模式简单理解就是共享对象,HashMap采用的就是这种模式,每次需要一个对象的时候,先看看HashMap中有没有,有就不再生成,没有的话先生成。
结构型模式总结
代理模式是做方法增强的,适配器模式是把鸡包装成鸭这种用来适配接口的,桥梁模式做到了很好的解耦,装饰模式从名字上就看得出来,适合于装饰类或者说是增强类的场景,门面模式的优点是客户端不需要关心实例化过程,只要调用需要的方法即可,组合模式用于描述具有层次结构的数据,享元模式是为了在特定的场景中缓存已经创建的对象,用于提高性能。
行为型模式
行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。
13.策略模式
什么是策略模式
定义了一系列的算法 或 逻辑 或 相同意义的操作,并将每一个算法、逻辑、操作封装起来,而且使它们还可以相互替换。(其实策略模式Java中用的非常非常广泛)
我觉得主要是为了 简化 if...else 所带来的复杂和难以维护。
策略模式应用场景
1. 例如:我要做一个不同会员打折力度不同的三种策略,初级会员,中级会员,高级会员(三种不同
的计算)。
2. 例如:我要一个支付模块,我要有微信支付、支付宝支付、银联支付等
策略模式的优点和缺点
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性非常良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
14.观察者模式
这个模式见名知意,观察者模式需要订阅自己关心的主题和主题有变化后通知我们。比如,我接到的通知是去监视张三,张三就是我订阅的主题,我不能没事闲的去监视李四,当张三从家里出来的时候我就得给组织汇报,目标出现。
观察者模式对于我们来说,真是再简单不过了。无外乎两个操作,观察者订阅自己关心的主题和主题有数据变化后通知观察者们。
模式的职责
观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。
实现有两种方式:
1. 推:每次都会把通知以广播的方式发送给所有观察者,所有的观察者只能被动接收。
2. 拉:观察者只要知道有情况即可,至于什么时候获取内容,获取什么内容,都可以自主决定。
观察者模式应用场景
1. 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。事件多级触发场景。
2. 跨系统的消息交换场景,如消息队列、事件总线的处理机制
15.责任链模式
适合于条件组合的场景,比如用户领取一个奖品,需要校验用户是否为新用户、今日参与人数是否有限额,奖品是不是发完了等等条件,此时就可以使用责任链方式构建规则链,自由的组合规则。(我觉得这种模式可以应用于领券场景)
16.模板方法模式
模板方法模式是通过把不变行为搬到超类,去除子类里面的重复代码提现它的优势,它提供了一个很好的代码复用平台。当不可变和可变的方法在子类中混合在一起的时候,不变的方法就会在子类中多次出现,这样如果摸个方法需要修改则需要修改很多个,虽然这个这个问题在设计之初就应该想好。这个时候模板方法模式就起到了作用了,通过模板方法模式把这些重复出现的方法搬到单一的地方,这样就可以帮助子类摆脱重复不变的纠缠。
什么是模板方法
模板方法模式:定义一个操作中的算法骨架(父类),而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构来重定义该算法的
什么时候使用模板方法
实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分需要改变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
实际开发中应用场景哪里用到了模板方法
其实很多框架中都有用到了模板方法模式
例如:数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等等
现实生活中的模板方法
去餐厅吃饭,餐厅给我们提供了一个模板就是:看菜单,点菜,吃饭,付款,走人 (这里 “点菜和付款” 是不确定的由子类来完成的,其他的则是一个模板。)
17.状态模式
适用于状态流转比较多的情况,便于扩展,但是一次性项目就别用了,代码量相当多
18.迭代器模式
应用场景
遍历集合对象。
19.访问者模式
应用场景
允许一个或多个操作应用到一组对象上,解耦操作和对象本身。
20.备忘录模式
应用场景
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
21.命令模式
应用场景
命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等。将命令的发起者和执行者解耦。
22.解释器模式
应用场景
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
24.中介模式
应用场景
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(依赖关系)转换成一对多(星状关系)。原本一个对象要跟n个对象交互,现在只需要跟一个中介对象交互,从而最小化对象间的交互关系,降低了代码复杂度,提高了代码的可读性和可维护性。