1、GoF 的 23 种设计模式的分类和功能
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式3种。
- 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF中提供了单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
- 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF中提供了代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
- 行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11种行为型模式。
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
- 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF中除了以上4种,其他的都是对象模式。
2、单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。单例是保证一个类仅有一个实例,并提供一个访问它的全局访问点,主要是解决一个全局使用的类频繁地创建与销毁的问题,当您想控制实例数目,节省系统资源的时候就可以采用单例模式,即构造函数私有
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建
单例实现方式:饿汉式,懒汉式,懒汉式+Synchronized,双重校验,静态内部类,枚举(推荐方式)
- 饿汉式:非Lazy初始化,多线程安全,这种方式比较常用,但容易产生垃圾对象,优点是没有加锁,执行效率会提高,缺点是类加载时就初始化,浪费内存
它基于classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getinstance方法,但是也不能确定有其他的方式(或者其他的静态方法》导致类装载,这时候初始化instance 显然没有达到lazy loading的效果
- 懒汉式,线程不安全:是Lazy初始化,多线程不安全,这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程,因为没有加锁synchronized,所以严格意义上它并不算单例模式。这种方式lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
- 懒汉式,线程安全:是Lazy 初始化,线程安全,这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步,优点是第一次调用才初始化,避免内存浪费,缺点是必须加锁synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
- 双重校验锁:JDK1.5 起,是 Lazy初始化,多线程安全,实现较复杂,这种方式采用双锁机制,安全且在多线程情况下能保持高性能,getInstance()的性能对应用程序很关键。
- 登记式/静态内部类:是Lazy 初始化,多线程安全,实现难度一般,这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式 不同的是:饿汉式 只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。 - 枚举:JDK1.5 起,不是lazy初始,线程安全,实现难度易,这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。能通过 reflection attack 来调用私有构造方法。
https://zhuanlan.zhihu.com/p/248225977
https://www.runoob.com/design-pattern/singleton-pattern.html
3、工厂模式(开闭,依赖倒置,迪米特原则)
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
①简单工厂②工厂方法模式③抽象工厂
核心:实例化对象不使用new,用工厂方法代替,选择实现类,创建对象统一管理和控制从而将调用者跟我们的实现解耦合
4、建造者模式
建造者模式将复杂对象的构建与它的表示解耦合,使得同样的构建过程可以创建不同的表示,主要作用:在用户不知道对象对的建造过程和细节的情况下就可以直接创建复杂的对象,用户只需给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象,把内部的建造过程和细节隐藏起来
5、原型模式
object的clone()方法,cloneable接口
浅克隆,深克隆:(序列化或者反序列化)或改造克隆方法
在springbean中使用了单例模式,原型模式
6、适配器模式
类似于USB网线转换器,继承(类适配器,单继承)和组合(对象适配器,常用)
7、桥接模式
例如java语言通过java虚拟机实现了平台的无关性,jdbc也是桥接模式的应用之一
8、代理模式:类似于中介,springAOP的底层实现
代理模式分为静态代理和动态代理
静态代理:抽象角色会使用接口或抽象类解决,真实角色是被代理的角色,代理角色代理真实角色后会有一些附加的操作,好处是可以使真实角色操作更加纯粹,不用关注一些公共的业务,公共交给代理角色,实现业务的分工,公共业务在扩展时,方便集中管理,缺点是一个真实的角色会产生一个代理角色,代码量会翻倍
动态代理:底层是反射机制,动态和静态代理角色一样,动态代理的代理是动态生成的,不是我们直接写好的,分为基于接口的动态代理和基于类的动态代理
基于接口的动态代理:jdk动态代理,基于类的动态代理:cglib
一个动态代理类代理的是一个接口,一般就是对应的一类业务,一个动态代理类可以代理多个类,只要是实现了同一个接口即可!