设计模式(二)
第一类模式:创建者模式
创建型模式的主要关注点是“怎么样创建对象?”,它的主要关注点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注 对象的创建细节。
创建型模式分为:
单例模式
工厂方法模式
抽象工程模式
原型模式
建造者模式
单例设计模式
单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的结构
单例模式主要有以下角色:
单例类。只能创建一个实例的类
访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式-方式1(静态成员变量)
1 package com.itheima.pattern.singleton.demo1; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:静态成员变量 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、在本类中创建本类对象 13 private static Singleton singleton = new Singleton(); 14 15 // 3、提供一个公共的访问方式,让外界获取该对象 16 public static Singleton getInstance() { 17 return singleton; 18 } 19 }
1 package com.itheima.pattern.singleton.demo1; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:静态成员变量 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 // 创建Singleton类对象 10 Singleton singleton1 = Singleton.getInstance(); 11 Singleton singleton2 = Singleton.getInstance(); 12 // 判断获取到的两个对象是否是同一个对象 13 System.out.println(singleton1 == singleton2); 14 } 15 }
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象singleton。singleton对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
饿汉式-方式2(静态代码块方式)
1 package com.itheima.pattern.singleton.demo2; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:静态代码块 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、声明Singleton类型的变量 13 private static Singleton singleton; 14 15 // 3、在静态代码块中进行赋值 16 static { 17 singleton = new Singleton(); 18 } 19 20 // 4、提供一个公共的访问方式,让外界获取该对象 21 public static Singleton getInstance() { 22 return singleton; 23 } 24 }
1 package com.itheima.pattern.singleton.demo2; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:静态代码块 6 * 测试类 7 */ 8 public class Client { 9 public static void main(String[] args) { 10 // 创建Singleton类对象 11 Singleton singleton1 = Singleton.getInstance(); 12 Singleton singleton2 = Singleton.getInstance(); 13 // 判断获取到的两个对象是否是同一个对象 14 System.out.println(singleton1 == singleton2); 15 } 16 }
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。所以和饿汉式的方式1基本一样,当然该方式也存在内存浪费问题。
懒汉式-方式1(线程不安全)
1 package com.itheima.pattern.singleton.demo3; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(线程不安全) 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、声明一个Singleton类型的变量singleton 13 private static Singleton singleton; 14 15 // 3、对外提供访问方式 16 public static Singleton getInstance() { 17 if (singleton == null) { 18 singleton = new Singleton(); 19 } 20 return singleton; 21 } 22 23 }
1 package com.itheima.pattern.singleton.demo3; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(线程不安全) 6 * 测试类 7 */ 8 public class Client { 9 public static void main(String[] args) { 10 Singleton singleton1 = Singleton.getInstance(); 11 Singleton singleton2 = Singleton.getInstance(); 12 System.out.println(singleton1 == singleton2); 13 } 14 }
从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
懒汉式-方式2(线程安全)
1 package com.itheima.pattern.singleton.demo3; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(线程安全) 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、声明一个Singleton类型的变量singleton 13 private static Singleton singleton; 14 15 // 3、对外提供访问方式(给方法加锁,此时线程安全) 16 public static synchronized Singleton getInstance() { 17 if (singleton == null) { 18 singleton = new Singleton(); 19 } 20 return singleton; 21 } 22 23 }
改进:给getInstance()方法加锁,此时线程就是安全的
懒汉式-方式3(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此产生了一种新的实现模式:双重检查锁模式
1 package com.itheima.pattern.singleton.demo4; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(双重检查锁) 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、声明一个Singleton类型的变量singleton 13 private static Singleton singleton; 14 15 // 3、对外提供访问方式 16 public static synchronized Singleton getInstance() { 17 // 第一个判断,如果singleton的值不为null,不需要抢占锁,直接返回对象 18 if (singleton == null) { 19 synchronized (Singleton.class) { 20 // 第二次判断 21 if (singleton == null) { 22 singleton = new Singleton(); 23 }guan 24 } 25 } 26 return singleton; 27 } 28 }
双重检查锁是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile关键字可以保证可见性和有序性(加在singleton变量上)。
1 package com.itheima.pattern.singleton.demo4; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(双重检查锁) 6 */ 7 public class Singleton { 8 9 // 1、私有构造方法 10 private Singleton() {} 11 12 // 2、声明一个Singleton类型的变量singleton 13 private static volatile Singleton singleton; 14 15 // 3、对外提供访问方式 16 public static synchronized Singleton getInstance() { 17 // 第一个判断,如果singleton的值不为null,不需要抢占锁,直接返回对象 18 if (singleton == null) { 19 synchronized (Singleton.class) { 20 // 第二次判断 21 if (singleton == null) { 22 singleton = new Singleton(); 23 } 24 } 25 } 26 return singleton; 27 } 28 }
小结:添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题(推荐使用双重检查锁模式)。
懒汉式-方式4(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
1 package com.itheima.pattern.singleton.demo5; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(静态内部类) 6 */ 7 public class Singleton { 8 // 私有构造方法 9 private Singleton() {} 10 11 // 定义一个静态内部类 12 private static class SingletonHolder { 13 // 在内部类中声明并初始化外部类的对象 14 private static final Singleton SINGLETON = new Singleton(); 15 } 16 17 // 提供公共的访问方式 18 public static Singleton getInstance() { 19 return SingletonHolder.SINGLETON; 20 } 21 }
1 package com.itheima.pattern.singleton.demo5; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 懒汉式:(静态内部类) 6 * 测试类 7 */ 8 public class Client { 9 public static void main(String[] args) { 10 Singleton singleton1 = Singleton.getInstance(); 11 Singleton singleton2 = Singleton.getInstance(); 12 System.out.println(singleton1 == singleton2); 13 } 14 }
说明:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间浪费。
枚举方式
枚举方式实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会转载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破环的单例实现模式。
1 package com.itheima.pattern.singleton.demo6; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:枚举实现方式 6 */ 7 public enum Singleton { 8 INSTANCE; 9 }
1 package com.itheima.pattern.singleton.demo6; 2 3 /** 4 * 创建者模式-单例设计模式 5 * 饿汉式:枚举实现方式 6 * 测试类 7 */ 8 public class Client { 9 public static void main(String[] args) { 10 Singleton singleton1 = Singleton.INSTANCE; 11 Singleton singleton2 = Singleton.INSTANCE; 12 System.out.println(singleton1 == singleton2); 13 } 14 }
说明:枚举方式属于饿汉式方式
存在问题
破坏单例模式:使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。
序列化反序列化
1 package com.itheima.pattern.singleton.demo7; 2 3 import java.io.*; 4 5 /** 6 * 创建者模式-单例设计模式 7 * 测试使用序列化和反序列化破环单例模式 8 */ 9 public class Client { 10 public static void main(String[] args) throws Exception { 11 readObjectFromFile(); 12 readObjectFromFile(); 13 } 14 15 // 从文件读取数据(对象) 16 public static void readObjectFromFile() throws IOException, ClassNotFoundException { 17 // 创建对象输入流对象 18 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\MCL\\Desktop\\a.txt")); 19 // 读取对象 20 Singleton instance = (Singleton) ois.readObject(); 21 System.out.println(instance); 22 // 释放资源 23 ois.close(); 24 } 25 26 // 向文件中写数据(对象) 27 public static void writeObjectToFile() throws Exception { 28 // 获取 Singleton 对象 29 Singleton instance = Singleton.getInstance(); 30 // 创建对象输出流 31 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\MCL\\Desktop\\a.txt")); 32 // 写对象 33 oos.writeObject(instance); 34 // 释放资源 35 oos.close(); 36 } 37 38 }
反射
1 package com.itheima.pattern.singleton.demo8; 2 3 import java.lang.reflect.Constructor; 4 5 /** 6 * 创建者模式-单例设计模式 7 * 测试使用反射破环单例模式 8 */ 9 public class Client { 10 public static void main(String[] args) throws Exception { 11 // 获取 Singleton 字节码对象 12 Class<Singleton> clazz = Singleton.class; 13 // 获取无参构造方法对象 14 Constructor<Singleton> cons = clazz.getDeclaredConstructor(); 15 // 取消访问检查 16 cons.setAccessible(true); 17 // 通过反射的方式创建Singleton对象 18 Singleton singleton1 = cons.newInstance(); 19 Singleton singleton2 = cons.newInstance(); 20 System.out.println(singleton1); 21 System.out.println(singleton2); 22 System.out.println(singleton1 == singleton2); 23 } 24 }
序列化、反序列化方式破坏单例模式的解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
1 package com.itheima.pattern.singleton.demo7; 2 3 import java.io.Serializable; 4 5 /** 6 * 创建者模式-单例设计模式 7 * 懒汉式:(静态内部类) 8 */ 9 public class Singleton implements Serializable { 10 // 私有构造方法 11 private Singleton() {} 12 13 // 定义一个静态内部类 14 private static class SingletonHolder { 15 // 在内部类中声明并初始化外部类的对象 16 private static final Singleton SINGLETON = new Singleton(); 17 } 18 19 // 提供公共的访问方式 20 public static Singleton getInstance() { 21 return SingletonHolder.SINGLETON; 22 } 23 24 // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回 25 public Object readResolve() { 26 return SingletonHolder.SINGLETON; 27 } 28 }
反射方式破解单例的解决方式
1 package com.itheima.pattern.singleton.demo8; 2 3 import java.io.Serializable; 4 5 /** 6 * 创建者模式-单例设计模式 7 * 懒汉式:(静态内部类) 8 */ 9 public class Singleton implements Serializable { 10 11 private static boolean flag = false; 12 13 // 私有构造方法 14 private Singleton() { 15 synchronized (Singleton.class) { 16 // 判断flag的值是否是true,如果是true,说明非第一个访问 17 if (flag) { 18 throw new RuntimeException("不能创建多个对象"); 19 } 20 // 将flag的值设置为true 21 flag = true; 22 } 23 } 24 25 // 定义一个静态内部类 26 private static class SingletonHolder { 27 // 在内部类中声明并初始化外部类的对象 28 private static final Singleton SINGLETON = new Singleton(); 29 } 30 31 // 提供公共的访问方式 32 public static Singleton getInstance() { 33 return SingletonHolder.SINGLETON; 34 } 35 }
Runtime类就是使用的单例设计模式,Runtime类使用的是饿汉式(静态属性)方式来实现单例模式的。
工厂模式
概述:需求,设计一个咖啡点餐系统
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店(CoffeeStore),咖啡店具有点咖啡的功能。
在java中,万物皆对象,这些对象都需要创建,如果创建的时候都需要new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
下面会介绍3种工厂设计模式
简单工厂模式(不属于GOF的23种经典设计模式),在开发中也会有很多人去使用。
工厂方法模式
抽象工厂模式
简单工厂模式
简单工厂模式不是一种设计模式,反而比较像一种编程习惯。
结构
简单工厂包含如下角色:
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
具体产品:实现或者继承抽象产品的子类。
具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品。
现在使用简单工厂对上面案例进行改进
工厂(factory)处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了Coffee对象的耦合,同时又产生了新的耦合,CoffeeStore对象和SImpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。
后期如果再加新品种的咖啡,我们势必要需求修改SImpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。
优缺
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑分开,这样以后就避免了修改客户端代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
扩展
静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式种的,代码如下:
/** * 简单咖啡工厂类,用来生产咖啡[静态工厂模式] */ public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { // 声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象 Coffee coffee = null; if ("american".equals(type)) { coffee = new AmericanCoffee(); } else if ("latte".equals(type)) { coffee = new LatteCoffee(); } else { throw new RuntimeException("对不起,您所点的咖啡没有!"); } return coffee; } }
工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美解决,完全遵循开闭原则。
概念
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
结构
工厂方法模式的主要角色
抽象工厂(Abstract Factory):提供了创建产品的接口,提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂(ConcreateFactory):主要实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreateProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
/** * 测试类 */ public class Client { public static void main(String[] args) { CoffeeStore store = new CoffeeStore(); // 创建对象 // CoffeeFactory factory = new AmericanCoffeeFactory(); // CoffeeFactory factory = new LatteCoffeeFactory(); CoffeeFactory factory = new RuiXinCoffeeFactory(); store.setFactory(factory); // 点咖啡 Coffee coffee = store.orderCoffee(); System.out.println(coffee.getName()); } }
从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂的优点,而且克服了它的缺点。
优缺点
优点
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点
每怎加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂模式
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机场只生成电视机、传智播客只培养计算机软件专业的学生等。
这些工厂只生产同类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型工厂,能生产多等级(种类)的产品,如电器厂既生产电视机又生产洗衣机或者空调,大学既有软件专业又有生物专业等。
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下