你好,我是Qiuner. 为记录自己编程学习过程和帮助别人少走弯路而写博客
这是我的 github https://github.com/Qiuner ⭐️
gitee https://gitee.com/Qiuner 🌹
如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 😄 (^ ~ ^)
想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎
设计模式学习心得 五种创建模式
这里黑马程序员视频设计的抽象工厂案例不确切,有误导性,我将其换成了我的代码 相信您看了我的代码能更好的理解抽象工厂模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建型模式分为:
- 单例模式
- 工厂方法模式
- 抽象工程模式
- 原型模式
- 建造者模式
单例设计模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的结构
单例模式的主要有以下角色:
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式
- 饿汉式-方式1(静态变量方式)
/** * 饿汉式 * 静态变量创建类的对象 */ public class Singleton { //私有构造方法 private Singleton() {} //在成员位置创建该类的对象 private static Singleton instance = new Singleton(); //对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } }
- 说明: 该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
- 这种实现方式是线程安全的,因为在多线程环境下,由于只有一个
singleton
实例,不会出现并发访问的问题。
- 饿汉式-方式2(静态代码块方式)
/** * 恶汉式 * 在静态代码块中创建该类对象 */ public class Singleton { //私有构造方法 private Singleton() {} //在成员位置创建该类的对象 private static Singleton instance; static { instance = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return instance; } }
- 说明:
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。 - 延迟加载:
- 这种方式是一种简单的延迟加载(懒汉式)方式。在类加载的时候,并没有立即创建
singleton
对象,而是在静态代码块中进行了初始化,这样可以实现在首次调用getSingleton()
方法时才创建对象的效果,从而达到延迟加载的目的。
- 线程安全性:
- 这种方式并没有考虑到线程安全性,当多个线程同时调用
getSingleton()
方法时,可能会导致多个对象被创建,不符合单例模式的要求。因此,这种方式并不是线程安全的。
- 资源浪费问题:
- 虽然实现了延迟加载,但是在多线程环境下可能会出现资源浪费的问题。如果多个线程同时调用
getSingleton()
方法,可能会导致多个对象被创建,造成资源的浪费。
懒汉式
- 懒汉式-方式1(线程不安全)
/** * 懒汉式 * 线程不安全 */ public class Singleton { //私有构造方法 private Singleton() {} //在成员位置创建该类的对象 private static Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
- 说明:
从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。 - 懒汉式-方式2(线程安全)
/** * 懒汉式 * 线程安全 */ public class Singleton { //私有构造方法 private Singleton() {} //在成员位置创建该类的对象 private static Singleton instance; //对外提供静态方法获取该对象 public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
- 说明:
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。 - 懒汉式-方式3(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于getInstance()
方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
/** * 双重检查方式 */ public class Singleton { //私有构造方法 private Singleton() {} private static Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例 if(instance == null) { synchronized (Singleton.class) { //抢到锁之后再次判断是否为null if(instance == null) { instance = new Singleton(); } } } return instance; } }
- 双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile
关键字,volatile
关键字可以保证可见性和有序性。
/** * 双重检查方式 */ public class Singleton { //私有构造方法 private Singleton() {} private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际 if(instance == null) { synchronized (Singleton.class) { //抢到锁之后再次判断是否为空 if(instance == null) { instance = new Singleton(); } } } return instance; } }
- 小结:
添加volatile
关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
volatile
是 Java 中的一个关键字,主要用于修饰变量。它的作用是告诉编译器,该变量可能会被多个线程同时访问,因此不要将该变量的访问操作缓存到线程的本地内存中,而应该直接从主内存中读取和写入。
- 懒汉式-方式4(静态内部类方式)静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被
static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
/** * 静态内部类方式 */ public class Singleton { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
- 说明: 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。小结: 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
- 推荐使用双重检查锁和静态内部类
- 枚举方式枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
/** * 枚举方式 */ public enum Singleton { INSTANCE; }
- 说明: 枚举方式属于恶汉式方式。
- 不考虑浪费内存空间的首选
以上单例模式 存在的问题
问题演示
破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。
- 序列化反序列化
Singleton类:
public class Singleton implements Serializable { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
- Test类:
package com.pattern.singleton.LazyInitialization.StaticInnerClass; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * @Description * @Author Qiu * @Date 2024/4/19 */ public class Client2 { public static void main(String[] args) throws Exception { // writeObject2File(); // 这样打印出来的地址不一样 readObjectFromFile(); readObjectFromFile(); } public static void writeObject2File() throws Exception { //获取Singleton类的对象 Singleton singleton = Singleton.getSingleton(); //创建对象输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\a.txt")); //将instance对象写出到文件中 oos.writeObject(singleton); } private static Singleton readObjectFromFile() throws Exception { //创建对象输入流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\a.txt")); //第一个读取Singleton对象 Singleton singleton = (Singleton) ois.readObject(); System.out.println(singleton); ois.close(); return singleton; } }
上面代码运行结果是
false
,表明序列化和反序列化已经破坏了单例设计模式。
- 反射
Singleton类:
public class Singleton { //私有构造方法 private Singleton() {} private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instan ce != null) { return instance; } instance = new Singleton(); return instance; } } }
- Test类:
public class Test { public static void main(String[] args) throws Exception { //获取Singleton类的字节码对象 Class clazz = Singleton.class; //获取Singleton类的私有无参构造方法对象 Constructor constructor = clazz.getDeclaredConstructor(); //取消访问检查 constructor.setAccessible(true); //创建Singleton类的对象s1 Singleton s1 = (Singleton) constructor.newInstance(); //创建Singleton类的对象s2 Singleton s2 = (Singleton) constructor.newInstance(); //判断通过反射创建的两个Singleton对象是否是同一个对象 System.out.println(s1 == s2); } }
上面代码运行结果是
false
,表明序列化和反序列化已经破坏了单例设计模式注意:枚举方式不会出现这两个问题。
Class clazz = Singleton.class;
:获取了 Singleton 类的字节码对象。这个对象包含了 Singleton 类的结构信息,可以用来获取构造方法、字段等信息。Constructor constructor = clazz.getDeclaredConstructor();
:通过字节码对象获取 Singleton 类的私有无参构造方法对象。getDeclaredConstructor()
方法用于获取指定参数类型的构造方法,传入空参数表示获取无参构造方法。constructor.setAccessible(true);
:取消私有构造方法的访问检查,这样可以在后续代码中调用私有构造方法。Singleton s1 = (Singleton) constructor.newInstance();
:使用反射机制调用私有构造方法创建 Singleton 类的对象 s1。newInstance()
方法会调用构造方法来创建对象,并返回一个 Object 类型的实例,需要进行类型转换为 Singleton 类型。Singleton s2 = (Singleton) constructor.newInstance();
:同样地,使用反射机制调用私有构造方法创建 Singleton 类的对象 s2。System.out.println(s1 == s2);
:判断通过反射创建的两个 Singleton 对象是否是同一个对象,输出结果。
问题的解决
- 序列化、反序列方式破坏单例模式的解决方法
在Singleton类中添加readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
Singleton类:
public class Singleton implements Serializable { //私有构造方法 private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } /** * 下面是为了解决序列化反序列化破解单例模式 */ private Object readResolve() { return SingletonHolder.INSTANCE; } }
- 源码解析:
ObjectInputStream类
public final Object readObject() throws IOException, ClassNotFoundException{ ... // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(false);//重点查看readObject0方法 ..... } private Object readObject0(boolean unshared) throws IOException { ... try { switch (tc) { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法 ... } } finally { depth--; bin.setBlockDataMode(oldMode); } } private Object readOrdinaryObject(boolean unshared) throws IOException { ... //isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类, obj = desc.isInstantiable() ? desc.newInstance() : null; ... // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量 // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。 Object rep = desc.invokeReadResolve(obj); ... } return obj; }
- 反射方式破解单例的解决方法
public class Singleton { //私有构造方法 private Singleton() { /* 反射破解单例模式需要添加的代码 */ if(instance != null) { throw new RuntimeException(); } } private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
- 说明:
这种方式比较好理解。当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。
JDK源码解析-Runtime类
Runtime类就是使用的单例设计模式。
- 通过源代码查看使用的是哪儿种单例模式
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
- 从上面源代码中可以看出Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。
- 使用Runtime类中的方法
public class RuntimeDemo { public static void main(String[] args) throws IOException { //获取Runtime类对象 Runtime runtime = Runtime.getRuntime(); //返回 Java 虚拟机中的内存总量。 System.out.println(runtime.totalMemory()); //返回 Java 虚拟机试图使用的最大内存量。 System.out.println(runtime.maxMemory()); //创建一个新的进程执行指定的字符串命令,返回进程对象 Process process = runtime.exec("ipconfig"); //获取命令执行后的结果,通过输入流获取 InputStream inputStream = process.getInputStream(); byte[] arr = new byte[1024 * 1024* 100]; int b = inputStream.read(arr); System.out.println(new String(arr,0,b,"gbk")); } }
创建型模式
工厂模式
概述
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体类的设计如下:
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
- 就像用户需要买电脑时,不是买部件,而是直接买电脑
在本教程中会介绍三种工厂的使用
- 简单工厂模式(不属于GOF的23种经典设计模式)
- 工厂方法模式
- 抽象工厂模式
简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯。
结构
简单工厂包含如下角色:
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
实现
现在使用简单工厂对上面案例进行改进,类图如下:
工厂类代码如下:
public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffee; } }
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
- 可能有咖啡店一号 咖啡店二号,修改时候需要将每个咖啡店都修改
优缺点
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
感悟与代码
- 原本的咖啡店有创建咖啡和对创建咖啡两种操作,其实这两种操作都是可以扩展的 都应该创建工厂来进行处理 毕竟有的人可能希望自己的咖啡里面放红糖冰糖奶油
- 简单工厂是开闭原则的实践。
- 本质就是将咖啡店变成了组装店,只需要将合适的东西摆在合适的位置,而合适的东西由一个类生产、派发给其他组装店
- 从中也可以得到: 简单工厂不是降低耦合 而是将耦合转移到了别处
package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 */ public class AmericanCoffee extends Coffee { @Override public String getName() { return "美式咖啡"; } } package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 */ public class Client { public static void main(String[] args) { // 咖啡店类 CoffeeStore store=new CoffeeStore(); Coffee coffee=store.orderCoffee("美式"); System.out.println(coffee.getName()); } } package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 */ public abstract class Coffee { public abstract String getName(); // 加糖 public void addSugar(){ System.out.println("加糖"); } // 加奶 public void addMilk(){ System.out.println("加奶"); } } package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 */ public class CoffeeStore { public Coffee orderCoffee(String type){ SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory(); Coffee coffee=simpleCoffeeFactory.createCoffee(type); // 配料 coffee.addMilk(); coffee.addSugar(); return coffee; } } package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 */ public class LatteCoffee extends Coffee { @Override public String getName() { return "拿铁咖啡"; } } package com.pattern.factory.sinple_factory; /** * @Description * @Author Qiu * @Date 2024/4/20 * 咖啡工厂类 用来生产咖啡 */ public class SimpleCoffeeFactory { public Coffee createCoffee(String type){ Coffee coffee=null; if ("美式".equals(type)){ coffee=new AmericanCoffee(); }else if("拿铁".equals(type)){ coffee =new LatteCoffee(); }else { throw new RuntimeException("没有这种咖啡"); } return coffee; } }
扩展
静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下:
public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffe; } }
- 静态工厂可以直接通过工厂名创建类
设计模式学习心得之五种创建者模式(2):https://developer.aliyun.com/article/1548574