设计模式参考视频
设计模式最强专栏
练习Demo:design-patterns
[toc]
设计模式用于在特定的条件下为一些重复出现的软件设计问题提供合理的、有效的解决方案。
不属于GoF 23中设计i模式,但是使用频繁。
graph BT
ConCreateProductA--> Product
ConCreateProductB --> Product
Factory --> ConCreateProductA
Factory --> ConCreateProductB
简单工厂模式的弊端:工厂类职责较重,引入新类时需要修改工厂类代码,违背了“开闭原则”。为解决这个问题,工厂方法模式应运而生。
定义: 定义一个用于创建对象的接口,让子类决定将哪一个类实例化。即提供一个抽象工厂接口并声明抽象工厂方法,其子类具体实现工厂方法,创建产品对象。
优点:
缺点:
适用场景: 不需要知道类名,只需要知道对应的工厂即可。具体的类由具体的工厂创建;抽象工厂类通过子类来指定创建的对象,利用了面向对象的多态性和里氏替换原则。共容易扩展。
抽象工厂模式为创建一组对象提供了解决方案。与工厂方法模式相比,抽象工厂模式的具体工厂不只是创建一种产品,而是负责创建一个系列的产品。
定义: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
优点:
缺点: 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
定义: 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。三个要点:
优点:
缺点:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
/**
* 需要将 构造方法私有化,不允许外部调用构造方法创建本类
*/
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
实例:为了避免多线程调用getInstance()方法,在方法上使用了 synchronized 关键字。但是此时会降低系统性能。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
改进1:synchronized关键字用在方法中。但仍存在问题:多线程同时判断 instance=null,那么线程A获取锁,线程B等待,等线程A创建实例后,线程B获取锁再次创建了实例,就不符合单例了。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}
改进2:双重检查锁定(Double-Check Locking)
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
// 第一重 判断
if (instance == null) {
// 锁定代码块
synchronized (LazySingleton.class) {
// 第二重 判断
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
可以实现对象的延迟加载,并在保证线程安全的情况下使系统性能不受到影响。
静态单例对象 instance 没有作为 Singleton 的成员变量而直接实例化,因此类加载时不会实例化。在首次调用getInstance方法时,内部类HolderClass中定义了static类型的变量instance,此时会首先初始化这个成员变量,并且由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于没有任何线程锁定,因此不会对性能造成任何影响。
public class Singleton {
private Singleton() {}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
// 静态内部类 具备外部类的特性
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String[] args) {
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
在对象延迟加载的前提下,保证线程安全。枚举只会在类加载时装载一次,所以它是线程安全的(枚举类最终会被编译为final修饰的普通类,并且所有属性都会被 static 和 final 关键字修饰,所以枚举类在项目启动时就会被 JVM 加载并初始化,而这个执行过程是线程安全的,所以枚举类也是线程安全的类。)
public class EnumSingleton {
private enum Singleton {
INSTANCE;
private final EnumSingleton instance;
Singleton() {
instance = new EnumSingleton();
}
private EnumSingleton getInstance() {
return instance;
}
}
public static EnumSingleton getInstance() {
return Singleton.INSTANCE.getInstance();
}
}
通过一个原型对象,克隆出多个一模一样的对象。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ConCreatePrototype implements Cloneable {
private String attr;
@Override
public ConCreatePrototype clone() {
ConCreatePrototype prototype = null;
try {
prototype = (ConCreatePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
public static void main(String[] args) {
ConCreatePrototype obj1 = new ConCreatePrototype("原型模式");
ConCreatePrototype obj2 = obj1.clone();
System.out.println(obj2.getAttr());
}
}
如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
@Data
class Address {
private String add;
private String[] arr;
}
public class ShallowCopy implements Cloneable {
private Address address = new Address();
public void setAdd(String add) {
this.address.setAdd(add);
}
public String getAdd() {
return this.address.getAdd();
}
public void setArr(String[] arr) {
this.address.setArr(arr);
}
public String[] getArr() {
return this.address.getArr();
}
@Override
protected ShallowCopy clone() {
ShallowCopy shallow = null;
try {
shallow = (ShallowCopy) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return shallow;
}
public static void main(String[] args) {
ShallowCopy a = new ShallowCopy();
a.setAdd("北京市朝阳区");
a.setArr(new String[]{"Hello", "Java", "World"});
System.out.println(a.getAdd());
System.out.println(Arrays.asList(a.getArr()));
ShallowCopy b = a.clone();
System.out.println(b.getAdd());
System.out.println(Arrays.asList(b.getArr()));
System.out.println("======= 浅拷贝 拷贝的是 引用对象的地址 =======");
b.setArr(new String[]{"Php", "Guava", "Python"});
System.out.println(Arrays.asList(a.getArr()));
System.out.println(Arrays.asList(b.getArr()));
}
}
无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,即深克隆将原型对象的引用对象也复制一份给克隆对象。
@Data
@AllArgsConstructor
class Info implements Cloneable {
private String add;
private String[] arr;
@Override
protected Info clone() throws CloneNotSupportedException {
return (Info) super.clone();
}
}
@Data
public class DeepCopy implements Cloneable {
public Info info;
@Override
protected DeepCopy clone() throws CloneNotSupportedException {
DeepCopy deepCopy = (DeepCopy) super.clone();
deepCopy.info = info.clone();
return deepCopy;
}
public static void main(String[] args) throws CloneNotSupportedException {
DeepCopy a = new DeepCopy();
a.setInfo(new Info("上海市静安区", new String[]{"CSDN", "Byte", "BiliBili"}));
DeepCopy b = a.clone();
System.out.println(JSONObject.toJSONString(b));
b.getInfo().setAdd("天津市和平区");
b.getInfo().setArr(new String[]{"五大道", "小白楼"});
System.out.println("a= " + JSONObject.toJSONString(a));
System.out.println("b= " + JSONObject.toJSONString(b));
}
}
定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
优点:
缺点:
定义: 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
优点:
缺点: 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。
当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。
定义: 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般是在使用该职责链的客户端中创建职责链。
优点:
缺点:
适用场景:
定义: 将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
优点:
缺点: 使用命令模式可能会导致某些系统有过多的具体命令类。
定义: 定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
优点:
缺点:
适用场景:
定义: 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
优点:
缺点:
适用场景:
定义: 定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。
优点:
缺点:
适用场景:
定义: 定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点:
缺点: 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计。
适用场景:
定义: 提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
优点:
缺点:
适用场景: