设计模式
分类
创建型:就是创建对象的模式,抽象了实例化的过程,对创建对象进行了封装
工厂、单例、建造者
结构型:为解决怎样组装现有的类,设计它们的交互方式,从而达到实现一定的功能目的。包容了对很多问题的解决(扩展性、封装)
适配器、装饰器、代理模式
行为型:行为型模式涉及到算法和对象间职责的分配,以及它们之间的通信模式
策略、模板方法模式、观察者模式、责任链模式
- 创建型模式为其他两种模式使用提供了环境。
- 结构型模式侧重于接口的使用,它做的一切工作都是对象或是类之间的交互,提供一个门。
- 行为型模式顾名思义,侧重于具体行为,所以概念中才会出现职责分配和算法通信等内容。
参考:设计模式分类(创建型模式、结构型模式、行为模式) · Issue #2 · jiayisheji/blog · GitHub
单例模式(看dcl 及静态内部类的实现)
单例模式:确保一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例。
单例模式的视频:https://www.bilibili.com/video/BV1Ta4y1Y7af
分为懒汉、饿汉、双重检查锁模式。
双重检查锁模式的成员变量要加volatile 关键字进行修饰,为了防止指令重排序。
原因:
new 一个对象的时候,分为三个步骤:分配内存;初始化对象;指向刚分配的地址。
如果发生指令重排序,那么第二步和第三步可能会变化,就导致对象没有初始化成功。
单例模式三个步骤
1. 一个成员对象
2. 私有构造方法
3. get 函数(可自定义各种逻辑)
几种类型的单例模式,参考:https://www.cnblogs.com/javastack/p/12579198.html
一、懒汉:使用的时候才创建实例
不同种的懒汉单例模式的特点:
1. 声明一个实例
2. 私有构造方法
3. get 函数
实现方式1
public class Singleton {
private static Singleton instance;
// 私有构造方法,使用的时候不可以使用new 来创建对象
private Singleton() {}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
线程不安全,不可用
实现方式2
public class Singleton {
private static Singleton instance;
// 私有构造方法,使用的时候不可以使用new 来创建对象
private Singleton() {}
public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
get 方法添加synchronized 关键字,线程安全,效率低,不推荐
实现方式3
public class Singleton {
private static Singleton instance;
// 私有构造方法,使用的时候不可以使用new 来创建对象
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
线程不安全,会产生多个实例,不可用
二、饿汉模式:无线程安全问题,不能延迟加载,影响系统性能
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
无线程安全问题,不能延迟加载,影响系统性能
三:双重校验锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
// 只有一个线程能进到这里面
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
由于jvm 存在乱序执行的功能,DCL 也会存在线程不安全的情况,分析:
对于instance = new Singleton();
jvm 中执行分为三个步骤
1. 堆内存开辟内存空间
2. 堆内存中实例化SingleTon 各个参数
3. 把对象指向堆内存空间
可能存在先执行了3,再执行2。
如果此时再被切换到线程B 上,由于执行了3,instance 已经非空,那么就会被直接拿出来用。
这样就会出现异常,这就是著名的DCL 失效问题。
然后对于成员变量加了volatile 之后,确保instance 每次都在主内存中读取,牺牲一些效率。
加了volatile 是为了禁止指令重排序
DCL,线程安全,推荐使用
四、静态内部类
public class Singleton {
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
private Singleton () {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
线程安全,主动调用的时候才会被实例化,延迟加载效率高,推荐使用
实现原理,参考:https://blog.csdn.net/qq_43279637/article/details/84982874
可能博文有错误,但是能看清楚原理
https://blog.csdn.net/mnb65482/article/details/80458571
这个文章原理写的清楚,但是没看。
问题:这种情况如何保证线程安全
总结:是虚拟机保证的,如果多线程同时去初始化一个类,内么只有一个线程会执行<clinit> 方法,其他都要阻塞。
五、枚举单例
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
很少用,枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例。我们可直接以
SingleTon.INSTANCE
的方式调用。
Q9:单例模式有哪些实现?
饿汉式:在类加载时就初始化创建单例对象,线程安全,但不管是否使⽤都创建对象可能会浪费内存。
懒汉式:在外部调⽤时才会加载,线程不安全,可以加锁保证线程安全但效率低。
双重检查锁:使⽤ volatile 以及多重检查来减⼩锁范围,提升效率。
静态内部类:同时解决饿汉式的内存浪费问题和懒汉式的线程安全问题。
枚举:《Effective Java》提倡的⽅式,不仅能避免线程安全问题,还能防⽌反序列化重新创建新的对象,绝对防⽌多次实例化,也能防⽌反射破解单例的问题。
工厂模式(三种)
让创建对象变得简单而且修改对象时能很方便
工厂模式解耦的意思是:
举例如果一个类中,使用了某个对象,要new 一个这个对象,那么这个类和这个依赖的对象对应的类就是“耦合”的关系。
如果一个类,使用了工厂
- 简单工厂模式
(工厂是个类,负责创建具体的产品;)
(工厂中的方法返回的也是产品)
(产品肯定有统一的接口,每个具体的产品实现产品接口) - 工厂方法模式
(工厂是个接口,具体产品工厂实现工厂接口)
(工厂接口中的方法返回的是产品)
(产品肯定也是有个统一的接口,每个产品实现产品接口,由具体产品工厂创建) - 抽象工厂模式
看这个链接也不错:简单工厂模式、工厂方法模式和抽象工厂模式有何区别? - 知乎
简单工厂模式,又称静态工厂方法模式。此模式中,可以根据参数的不同,返回不同的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,创建出来的类通常具有共同的父类。
这个专门定义的工厂类处于对产品进行实例化的中心位置,即它需要知道产品类的具体实现细节,并决定何时实例化哪一个产品类。
每当有新产品加入时,必须要修改工厂类。
参考视频:五分钟学设计模式.02.简单工厂模式_哔哩哔哩_bilibili
简单工厂模式分为几个内容:
- 一个实体的工厂类,里面有个create 方法,根据方法传参,决定返回那个具体的角色类
- 各个角色的统一接口
- 各个角色的具体实现类
工厂方法模式,定义一个用于创建对象的接口,让这个接口的子类决定实例化哪个产品类。工厂方法使一个类的实例化延迟到子类。即新增一个产品的时候,不需要再去修改工厂类,不需要再去修改if else 了。
相对于简单工厂模式,之前的核心工厂变成了一个抽象接口,负责给出工厂应该实现的方法,不再负责所有产品的创建,将具体的产品创建工作交给子类去做。这样就诞生了具体的子工厂,即子类负责生产具体的产品对象。这样做可以将产品类的实例化操作延迟到工厂子类中完成。即通过工厂子类来确定究竟该实例化哪个具体实例类。(开闭原则)
这是工厂类的实现代码,但是只能创建一个大类的产品
当创建逻辑比较复杂,不只是简单地new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,推荐使用“工厂方法模式”,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而简单工厂模式是把所有创建逻辑都放到同一个类中,会导致这个工厂类变得很复杂。
工厂方法分为几个内容:
- 工厂类的接口,里面有个方法,如“创建动物”
- 各个具体角色的类工厂,如猫工厂、狗工厂,返回猫类、狗类
- 角色的接口,如Animal
- 各个角色的具体实现类
工厂方法模式参考这个图就好
抽象工厂模式
打破了上述工厂与产品的一对一的关系,而这个是一个具体的工厂类可以生产多个大类的产品。
扩展了具体工厂的功能,使其可以生产多个大类。
从类图可以看出,新增一个产品体系,就必须要对工厂类进行修改。也就是说一旦要新增一个产品体系的话,就必须要修改原有的工厂逻辑。包括抽象接口以及所有具体工厂。
对比于“工厂方法模式”的区别就是
工厂方法模式,如果有很多个角色,就要有很多个具体角色的工厂类
抽象工厂模式就是把各个具体角色的工厂的共性抽出来,比如说猫、狗,不是公的就是母的
抽象工厂模式分为如下的内容:
- 工厂的接口,里面定义的是创建各个角色的接口,每个接口返回一个角色(如猫、狗)
- 抽象出来的属性的具体工厂,如抽象出来动物的性别,所有就是“母动物工厂具体实现类”,“公动物具体工厂实现类”
- 角色的接口(抽象类),接口中的方法是每个角色都具有的共性行为(如吃的动作,返回性别 两个接口)
- 对于角色的大类分别,对于每种类型的角色,如“猫”,吃的动作返回“吃鱼”,这是所有猫角色共同的
- 对于角色的另外一个分别,比如说分为“公猫”,“母猫”
个人理解:
抽象工厂模式就是抽象出来“产品族”的概念,将原来一个产品一个工厂类的情况改成一类产品一个工厂实现类。
可以参考看看这个:https://www.zhihu.com/question/27125796/answer/688147580
装饰器模式
给一个类或者对象增加新的功能有两种方式
一种称为“继承机制”,在子类扩展功能;第二种称为“关联机制”,把一个类的对象嵌入到另一个类的对象中。
对于继承,是静态的,必须要实现子类,对类的层级进行扩展。
对于装饰器模式,是动态的,拿到一个对象就可以对其进行扩展,不需要修改原有类逻辑。
其中,Decorator 也是Component 的实现;ConcreteComponent 也是Component 的具体实现。
java IO 流用了装饰器模式。
责任链模式(因为Filter 使用了)
参考视频:五分钟学设计模式.12.责任链模式_哔哩哔哩_bilibili
客户请求:
优点,将请求和处理分开。请求者不需要知道谁去处理,处理者不需要知道请求全貌。可以提高系统灵活性,新增一个处理器,到系统中代价是非常小的。
缺点,会降低系统性能。如果某个请求,直接找boss 就好。请求需要从链头走到链尾。
模板方法模式
参考视频:五分钟学设计模式.13.模板方法模式_哔哩哔哩_bilibili
最重要的特点就是,行为步骤由父类去控制,子类去负责实现 。
父类中封装了不变的部分,子类通过扩展父类, 扩展具体的实现,实现更进一步的操作。
缺点:增加系统复杂度(无大碍)。
建造者模式
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
public class StudentQueryCondition {
/**
* 必填字段
*/
private Long id;
/**
* 选填字段
*/
private String name;
private Integer age;
private String address;
private String school;
private StudentQueryCondition(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
this.school = builder.school;
}
public static Builder builder(Long id) {
return new Builder(id);
}
public static class Builder {
// 必填字段
private Long id;
// 选填字段
private String name;
private Integer age;
private String address;
private String school;
private Builder(Long id) {
this.id = id;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(Integer age) {
this.age = age;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder school(String school) {
this.school = school;
return this;
}
public StudentQueryCondition build() {
return new StudentQueryCondition(this);
}
}
}
代理模式
- 静态代理
client 调用静态代理对象的方法。创建一个静态代理类,将被代理对象(目标对象)传入,然后创建需要增强的方法
- 动态代理
和静态代理本质一样,只不过静态代理采用硬编码的方式,在程序运行前就创建好代理类,
设计模式相关可以参考的链接:https://blog.csdn.net/mengchuan6666/article/details/119924760