文章目录
0. 基础
1. 工厂模式
- 缺点:扩展性差。产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码
2. 策略模式
-
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
-
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露
-
使用场景:1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现
3. 状态模式
- 使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者
- 对比:策略模式 VS 状态模式
4. 装饰器模式
-
说明;这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能
-
使用场景:学生、课代表、老师登陆成绩管理系统
- 学生:基本的成绩管理功能:只能查看
- 课代表:增加添加成绩的功能
- 老师:增加修改、删除成绩的功能
-
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
-
缺点:多层装饰比较复杂。
-
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
5. 单例模式
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
单例的实现主要是通过以下两个步骤:
- 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
- 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
- 单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
优点:
- 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
- 减少new关键字的使用,降低系统内存的使用频率,同时减轻GC工作
- 避免了资源的多重使用
缺点:
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
- 不可继承,没有接口
使用场景:
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
- 工具类对象;
- 频繁访问数据库或文件的对象。
代码实现:
指令重排,以双重检查为例:
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;
}
}
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。 也就是说,JVM为了执行效率会将指令进行重新排序,但是这种重新排序不会对单线程程序产生影响。
由于run方法中的代码被指令重排后,执行的结果并不会对当前线程产生不同的结果,所以即使它符合了happens-before规则,还是被重排了。
由于singletonTest = new SingletonTest()操作并不是一个原子性指令,会被分为多个指令
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象
若有A线程进行完重排后的第二步,且未执行初始化对象。此时B线程来取singletonTest时,发现singletonTest不为空,于是便返回该值,但由于没有初始化完该对象,此时返回的对象是有问题的。这也就是为什么说看似稳的一逼的代码,实则不堪一击
happens-before规则
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
Spring中的单例模式的应用
Spring 中加载单例的过程都是在BeanFactory接口中定义的getBean()这个方法中定义的,实现默认是在AbstractBeanFactory中,主要代码功能两点
- 从缓存中获取单例bean
- 从bean的实例中获取对象
6. 适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。比如中国充电器是220V,到了美国110V,不能用,需要一个转换器将220V转换成110V
7. 代理模式
- 目的:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层
- 静态代理:使用时,需要定义接口和父类,被代理对象和代理对象一起实现相同接口或继承相同父类
- JDK代理(动态代理)
- CGLIB代理(动态代理)
Spring AOP中的JDK和CGLib动态代理哪个效率更高?
使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。
- 适配器的特点在于兼容,从代码上的特点来说,适配类与原有的类具有相同的接口,并且持有新的目标对象 —— 将一个类(a)通过某种方式转换成另一个类(b)
- 装饰器模式特点在于增强,他的特点是被装饰类和所有的装饰类必须实现同一个接口,而且必须持有被装饰的对象,可以无限装饰 —— 一个原有类(a)的基础之上增加了某些新的功能变成另一个类(b)
- 代理模式的特点在于隔离,隔离调用类和被调用类的关系,通过一个代理类去调用 —— 将一个类(a)转换成具体的操作类(b)
8. 组合模式
树结构的实体层次结构
- 学校
- 主校
- 后勤部
- 保卫部
- 教务处
- 分校
- 后勤部
- 保卫部
- 教务处
- 主校
9. 责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
外部控制模式:链的每个节点只需要专注于各自的逻辑即可,而当前节点调用完成之后是否继续调用下一个节点,这个则由外部控制逻辑进行
节点控制模式:比如在执行到某个节点之后需要动态的判断是否执行下一个节点,或者说要执行某些分叉的节点等等。这个时候我们就需要将链节点的传递工作交由各个节点进行
- Pipeline则主要是用于控制整体的流程调用的,比如对于任务的执行,其有任务的查询,任务的过滤和执行任务等等流程,这些流程整体的逻辑控制就是由Pipeline来控制的
- 在每个流程中又包含了一系列的子流程,这些子流程则是由一个个的HandlerContext和Handler进行梳理的
- Pipeline负责管理任务
- HandlerContext负责依据任务情况invoke暴露服务
使用场景:
- 请假的OA申请
- 请假天数如果是半天到1天,直接主管批准;
- 如果是1到3天的假期,部门经理批准;
- 如果是3天到30天,则需要总经理审批;
- 大于30天,正常不会批准
10. 命令模式
使用场景:
- 在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?
- 将一个请求封装成一个对象
11. 观察者模式
12. 建造者模式
建造者模式又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同的表现(属性)的对象
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节
使用场景:一个类过于复杂
四个角色
- Product (产品角色):一个具体的产品对象
- Builder(抽象建造者):创建一个 Product 对象的各个部件指定的接口 / 抽象类
- ConcreteBuilder (具体建造者):实现接口,构建和装配各个部件
- Director(指挥者):构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一个是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程
13. 备忘录模式
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。
14. 原型模式
- 定义
原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
- 使用场景
例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用
实际应用
- 做仓库管理系统的商品入库,多种入库方式可以用适配器模式,或者代理模式,或者工厂模式
- 适配器模式,桥接模式:返回类型封装,错误码异常类封装,通用service,自己写简单的中间件
- Spring用到哪些设计模式
- Mybatis用到哪些设计模式
- Tomcat用到哪些设计模式