1.单例模式

单例意思只包含⼀个对象被称为单例的特殊类
通过单例模式可以保证系统中,应⽤该模式的类只有⼀个对象实例

  • 使⽤场景

    • 业务系统全局只需要⼀个对象实例,⽐如发号器、redis连接对象等
    • Spring IOC容器中的bean默认就是单例
    • spring boot 中的controller、service、dao层中通过 @autowire的依赖注⼊对象默认都是单例的
  • 分类

    • 懒汉:就是所谓的懒加载,延迟创建对象
    • 饿汉:与懒汉相反,提前创建对象
  • 实现步骤

    • 私有化构造函数
    • 提供获取单例的⽅法

1.懒汉实现方式

  • 第一种方式(线程不安全) ```csharp public class SingletonLazy {

    private static SingletonLazy instance; /**

    • 构造函数私有化 */ private SingletonLazy(){}

      /**

    • 第一种方式
    • 对外暴露一个方法获取类的对象 *
    • 线程不安全,多线程下存在安全问题 / public static SingletonLazy getInstance(){ if(instance == null){
      1. instance = new SingletonLazy();
      } return instance; } }
  1. - 第二种方式(线程安全)
  2. ```csharp
  3. public class SingletonLazy {
  4. private static SingletonLazy instance;
  5. /**
  6. * 构造函数私有化
  7. */
  8. private SingletonLazy(){}
  9. /**
  10. * 第二种实现方式
  11. * 通过加锁 synchronized 保证单例
  12. *
  13. * 采用synchronized 对方法加锁有很大的性能开销
  14. *
  15. * 解决办法:锁粒度不要这么大
  16. *
  17. * @return
  18. */
  19. public static synchronized SingletonLazy getInstance(){
  20. if(instance == null){
  21. instance = new SingletonLazy();
  22. }
  23. return instance;
  24. }
  25. }
  • 第三种方式(DCL)(存在隐患) ```csharp public class SingletonLazy {

    private static SingletonLazy instance; /**

    • 构造函数私有化 */ private SingletonLazy(){}
  1. /**
  2. * 第三种实现方式
  3. *
  4. * DCL 双重检查锁定 (Double-Checked-Locking),在多线程情况下保持高性能
  5. *
  6. * 这是否安全,instance = new SingletonLazy(); 并不是原子性操作
  7. * 1、分配空间给对象
  8. * 2、在空间内创建对象
  9. * 3、将对象赋值给引用instance
  10. *
  11. * 假如线程 1-》3-》2顺序,会把值写会主内存,其他线程就会读取到instance最新的值,但是这个是不完全的对象
  12. * (指令重排)
  13. * @return
  14. */

public static SingletonLazy getInstance(){ if(instance == null){ // A、B synchronized (SingletonLazy.class){ if(instance == null) { instance = new SingletonLazy(); } } } return instance; } }

  1. - 第四种实现方式(DCL + volatile
  2. ```csharp
  3. public class SingletonLazy {
  4. //private static SingletonLazy instance;
  5. /**
  6. * 构造函数私有化
  7. */
  8. private SingletonLazy(){}
  9. /**
  10. * volatile是Java提供的关键字,它具有可见性和有序性,
  11. *
  12. * 指令重排序是JVM对语句执行的优化,只要语句间没有依赖,那JVM就有权对语句进行优化
  13. *
  14. * 禁止了指令重排
  15. */
  16. private static volatile SingletonLazy instance;
  17. public static SingletonLazy getInstance(){
  18. //第一重检查
  19. if(instance == null){
  20. // A、B ,锁定
  21. synchronized (SingletonLazy.class){
  22. //第二重检查
  23. if(instance == null) {
  24. instance = new SingletonLazy();
  25. }
  26. }
  27. }
  28. return instance;
  29. }
  30. }
  • 第五种实现方式(静态内部类方式)

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

  1. public class Singleton {
  2. //私有构造方法
  3. private Singleton() {}
  4. private static class SingletonHolder {
  5. private static final Singleton INSTANCE = new Singleton();
  6. }
  7. //对外提供静态方法获取该对象
  8. public static Singleton getInstance() {
  9. return SingletonHolder.INSTANCE;
  10. }
  11. }

第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

  • 第六种实现方式(枚举)
    1. public enum Singleton {
    2. INSTANCE;
    3. }
    枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

2.饿汉实现方式

  1. public class SingletonHungry {
  2. private static SingletonHungry instance = new SingletonHungry();
  3. private SingletonHungry(){}
  4. public static SingletonHungry getInstance(){
  5. return instance;
  6. }
  7. }

3.序列化破坏单例模式

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. //往文件中写对象
  4. //writeObject2File();
  5. //从文件中读取对象
  6. Singleton s1 = readObjectFromFile();
  7. Singleton s2 = readObjectFromFile();
  8. //判断两个反序列化后的对象是否是同一个对象
  9. System.out.println(s1 == s2);
  10. }
  11. private static Singleton readObjectFromFile() throws Exception {
  12. //创建对象输入流对象
  13. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
  14. //第一个读取Singleton对象
  15. Singleton instance = (Singleton) ois.readObject();
  16. return instance;
  17. }
  18. public static void writeObject2File() throws Exception {
  19. //获取Singleton类的对象
  20. Singleton instance = Singleton.getInstance();
  21. //创建对象输出流
  22. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
  23. //将instance对象写出到文件中
  24. oos.writeObject(instance);
  25. }
  26. }

上面代码运行结果是false,表明序列化和反序列化已经破坏了单例设计模式。

  • 解决方法

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

  1. public class Singleton implements Serializable {
  2. //私有构造方法
  3. private Singleton() {}
  4. private static class SingletonHolder {
  5. private static final Singleton INSTANCE = new Singleton();
  6. }
  7. //对外提供静态方法获取该对象
  8. public static Singleton getInstance() {
  9. return SingletonHolder.INSTANCE;
  10. }
  11. /**
  12. * 下面是为了解决序列化反序列化破解单例模式
  13. */
  14. private Object readResolve() {
  15. return SingletonHolder.INSTANCE;
  16. }
  17. }

4.反射破坏单例模式

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. //获取Singleton类的字节码对象
  4. Class clazz = Singleton.class;
  5. //获取Singleton类的私有无参构造方法对象
  6. Constructor constructor = clazz.getDeclaredConstructor();
  7. //取消访问检查
  8. constructor.setAccessible(true);
  9. //创建Singleton类的对象s1
  10. Singleton s1 = (Singleton) constructor.newInstance();
  11. //创建Singleton类的对象s2
  12. Singleton s2 = (Singleton) constructor.newInstance();
  13. //判断通过反射创建的两个Singleton对象是否是同一个对象
  14. System.out.println(s1 == s2);
  15. }
  16. }

上面代码运行结果是false,表明反射已经破坏了单例设计模式

  • 解决方法

当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。

  1. public class Singleton {
  2. //私有构造方法
  3. private Singleton() {
  4. /*
  5. 反射破解单例模式需要添加的代码
  6. */
  7. if(instance != null) {
  8. throw new RuntimeException();
  9. }
  10. }
  11. private static volatile Singleton instance;
  12. //对外提供静态方法获取该对象
  13. public static Singleton getInstance() {
  14. if(instance != null) {
  15. return instance;
  16. }
  17. synchronized (Singleton.class) {
  18. if(instance != null) {
  19. return instance;
  20. }
  21. instance = new Singleton();
  22. return instance;
  23. }
  24. }
  25. }

2.工厂模式

1.原⽣社会过渡-常⻅的⼯⼚设计模式

  • ⼯⼚模式介绍:
    • 它提供了⼀种创建对象的最佳⽅式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使⽤⼀个共同的接⼝来指向新创建的对象
  • 例⼦:
    • 需要购买⼀辆⻋,不⽤管⻋辆如何组装,且可以购买不同类型的⽐如轿⻋、SUV、跑⻋,直接去4s店购买就⾏(4s店就是⼯⼚)
    • ⼯⼚⽣产电脑,除了A品牌、还可以⽣产B、C、D品牌电脑
    • 业务开发中,⽀付很常⻅,⾥⾯有统⼀下单和⽀付接⼝,具体的⽀付实现可以微信、⽀付宝、银⾏卡等

截图_20223322083359.png

  • ⼯⼚模式有 3 种不同的实现⽅式

    • 简单⼯⼚模式:通过传⼊相关的类型来返回相应的类, 这种⽅式⽐较单⼀,可扩展性相对较差;
    • ⼯⼚⽅法模式:通过实现类实现相应的⽅法来决定相应的返回结果,这种⽅式的可扩展性⽐较强;
    • 抽象⼯⼚模式:基于上述两种模式的拓展,且⽀持细化产品
  • 应⽤场景:

    • 解耦:分离职责,把复杂对象的创建和使⽤的过程分开
    • 复⽤代码 降低维护成本:
      • 如果对象创建复杂且多处需⽤到,如果每处都进⾏编写,则很多重复代码,如果业务逻辑发⽣了改

变,需⽤四处修改;

  1. - 使⽤⼯⼚模式统⼀创建,则只要修改⼯⼚类即可,降低成本

2.电商⽀付应⽤案例-简单⼯⼚模式实践指南

  • 简单⼯⼚模式

    • ⼜称静态⼯⼚⽅法, 可以根据参数的不同返回不同类的 实例,专⻔定义⼀个类来负责创建其他类的实例,被创 建的实例通常都具有共同的⽗类
    • 由于⼯⼚⽅法是静态⽅法,可通过类名直接调⽤,⽽且 只需要传⼊简单的参数即可
  • 核⼼组成

    • Factory:⼯⼚类,简单⼯⼚模式的核⼼,它负责实现
    • 创建所有实例的内部逻辑
    • IProduct:抽象产品类,简单⼯⼚模式所创建的所有对 象的⽗类,描述所有实例所共有的公共接⼝
    • Product:具体产品类,是简单⼯⼚模式的创建⽬标
  • 实现步骤

    • 创建抽象产品类,⾥⾯有产品的抽象⽅法,由具体的产品类去实现
    • 创建具体产品类,继承了他们的⽗类,并实现具体⽅法
    • 创建⼯⼚类,提供了⼀个静态⽅法createXXX⽤来⽣产产品,只需要传⼊你想产品名称
  • 优点:

    • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
  • 缺点
    • ⼯⼚类的职责相对过重,增加新的产品需要修改⼯⼚类的判断逻辑,这⼀点与开闭原则是相违背
    • 即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进⾏拓展的时候,不能去修改原有的代码,实现⼀个热插拔的效果
    • 将会增加系统中类的个数,在⼀定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简 单对象就不⽤模式 ```java public interface Pay { /**
      • 统一下单 */ void unifiedorder();

}

  1. ```java
  2. public class AliPay implements Pay{
  3. @Override
  4. public void unifiedorder() {
  5. System.out.println("阿里统一下单接口");
  6. }
  7. }
  1. public class WechatPay implements Pay {
  2. @Override
  3. public void unifiedorder() {
  4. System.out.println("微信统一下单接口");
  5. }
  6. }
  1. public class SimplePayFactory {
  2. public static Pay createPay(String payType) {
  3. if (payType == null) {
  4. return null;
  5. } else if (payType.equals("WEI_XIN_PAY")) {
  6. return new WechatPay();
  7. } else if (payType.equals("ALI_PAY")) {
  8. return new AliPay();
  9. }
  10. return null;
  11. }
  12. }

3.⼯⼚设计模式实践指南-⼯⼚⽅法模式

  • ⼯⼚⽅法模式

    • ⼜称⼯⼚模式,是对简单⼯⼚模式的进⼀步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的 产品,即满⾜开闭原则
    • 通过⼯⼚⽗类定义负责创建产品的公共接⼝,通过⼦类来确定所需要创建的类型
    • 相⽐简单⼯⼚⽽⾔,此种⽅法具有更多的可扩展性和复⽤性,同时也增强了代码的可读性
    • 将类的实例化(具体产品的创建)延迟到⼯⼚类的⼦类 (具体⼯⼚)中完成,即由⼦类来决定应该实例化哪⼀个类。
  • 核⼼组成

    • IProduct:抽象产品类,描述所有实例所共有的公共接⼝
    • Product:具体产品类,实现抽象产品类的接⼝,⼯⼚类创建对象,如果有多个需要定义多个
    • IFactory:抽象⼯⼚类,描述具体⼯⼚的公共接⼝
    • Factory:具体⼯场类,实现创建产品类对象,实现抽象⼯⼚类的接⼝,如果有多个需要定义多个截图_20221422091443.png
      1. /**
      2. * 抽象⼯⼚⽅法
      3. */
      4. public interface PayFactory {
      5. Pay getPay();
      6. }
      1. /**
      2. * 具体产品⼯⼚
      3. */
      4. public class AliPayFactory implements PayFactory {
      5. @Override
      6. public Pay getPay() {
      7. return new AliPay();
      8. }
      9. }
      1. public class WechatPayFactory implements PayFactory {
      2. @Override
      3. public Pay getPay() {
      4. return new WechatPay();
      5. }
      6. }
      1. /**
      2. * 抽象产品
      3. */
      4. public interface Pay {
      5. /**
      6. * 统⼀下单
      7. */
      8. void unifiedorder();
      9. }
      1. /**
      2. * 具体产品
      3. */
      4. public class AliPay implements Pay {
      5. @Override
      6. public void unifiedorder() {
      7. System.out.println("⽀付宝⽀付 统⼀下单接⼝");
      8. }
      9. }
  • 优点:

    • 符合开闭原则,增加⼀个产品类,只需要实现其他具体的产品类和具体的⼯⼚类;
    • 符合单⼀职责原则,每个⼯⼚只负责⽣产对应的产品
    • 使⽤者只需要知道产品的抽象类,⽆须关⼼其他实现 类,满⾜迪⽶特法则、依赖倒置原则和⾥⽒替换原则
    • 迪⽶特法则:最少知道原则,实体应当尽量少地与 其他实体之间发⽣相互作⽤
    • 依赖倒置原则:针对接⼝编程,依赖于抽象⽽不依 赖于具体
    • ⾥⽒替换原则:俗称LSP, 任何基类可以出现的地 ⽅,⼦类⼀定可以出现, 对实现抽象化的具体步骤的规范
  • 缺点:

    • 增加⼀个产品,需要实现对应的具体⼯⼚类和具体产品 类;
    • 每个产品需要有对应的具体⼯⼚和具体产品类

4.⼯⼚设计模式实践指南- 抽象⼯⼚⽅法模式

  • ⼯⼚模式有 3 种不同的实现⽅式

    • 简单⼯⼚模式:通过传⼊相关的类型来返回相应的类,这种⽅式⽐较单 ⼀,可扩展性相对较差;
    • ⼯⼚⽅法模式:通过实现类实现相应的⽅法来决定相应的返回结果,这种⽅式的可扩展性⽐较强;
    • 抽象⼯⼚模式:基于上述两种模式的拓展,是⼯⼚⽅法 模式的升级版,当需要创建的产品有多个产品线时使⽤抽象⼯⼚模式是⽐较好的选择
    • 抽象⼯⼚模式在 Spring 中应⽤得最为⼴泛的⼀种设计模式
  • 背景

    • ⼯⼚⽅法模式引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责过重的问题
    • 但⼯⼚⽅法模式中每个⼯⼚只创建⼀类具体类的对象, 后续发展可能会导致⼯⼚类过多,因此将⼀些相关的具 体类组成⼀个“具体类族”,由同⼀个⼯⼚来统⼀⽣产, 强调的是⼀系列相关的产品对象!!!

截图_20223922093955.png
实现步骤

  1. 1、定义两个接⼝ PayRefund
  2. 2、创建具体的Pay产品、创建具体的Refund产品
  3. 3、创建抽象⼯⼚ OrderFactory 接⼝⾥⾯两个⽅法 createPay/createRefund
  4. 4、创建⽀付宝产品族AliOderFactory,实现OrderFactory抽象⼯⼚
  5. 5、创建微信⽀付产品族WechatOderFactory,实现OrderFactory抽象⼯⼚
  6. 6、定义⼀个超级⼯⼚创造器,通过传递参数获取对应的⼯⼚

截图_20224022094026.png

  1. /**
  2. * 超级⼯⼚,定义同个产品族的其他相关⼦⼯⼚
  3. */
  4. public interface OrderFactory {
  5. PayFactory createPay();
  6. RefundFactory createRefund();
  7. }
  1. //产品族⼯⼚的产品,可以不叫Factory,看公司团队规范,⽐如类名叫 IPay 也可以的
  2. public interface PayFactory {
  3. /**
  4. * 统⼀下单
  5. */
  6. void unifiedorder();
  7. }
  1. /**
  2. * 退款抽象接口
  3. */
  4. public interface RefundFactory {
  5. /**
  6. * 退款
  7. */
  8. void refund();
  9. }
  1. //产品族工厂
  2. public class AliOrderFactory implements OrderFactory {
  3. @Override
  4. public PayFactory createPay() {
  5. return new AliPay();
  6. }
  7. @Override
  8. public RefundFactory createRefund() {
  9. return new AliRefund();
  10. }
  11. }
  12. //具体产品
  13. public class AliPay implements PayFactory {
  14. @Override
  15. public void unifiedorder() {
  16. System.out.println("⽀付宝⽀付 统⼀下单接⼝");
  17. }
  18. }
  19. //具体产品
  20. public class AliRefund implements RefundFactory {
  21. @Override
  22. public void refund() {
  23. System.out.println("支付宝 退款");
  24. }
  25. }
  1. //产品族工厂
  2. public class WechatOrderFactory implements OrderFactory {
  3. @Override
  4. public PayFactory createPay() {
  5. return new WechatPay();
  6. }
  7. @Override
  8. public RefundFactory createRefund() {
  9. return new WechatRefund();
  10. }
  11. }
  12. //具体产品
  13. public class WechatPay implements PayFactory {
  14. @Override
  15. public void unifiedorder() {
  16. System.out.println("微信支付支付 统一下单接口");
  17. }
  18. }
  19. //具体产品
  20. public class WechatRefund implements RefundFactory {
  21. @Override
  22. public void refund() {
  23. System.out.println("微信支付 退款");
  24. }
  25. }
  1. //超级⼯⼚⽣产器,传参⽣产对应的⼦⼯⼚
  2. public class FactoryProducer {
  3. public static OrderFactory getFactory(String type) {
  4. if (type.equalsIgnoreCase("WECHAT")) {
  5. return new WechatOrderFactory();
  6. } else if (type.equalsIgnoreCase("ALI")) {
  7. return new AliOrderFactory();
  8. }
  9. return null;
  10. }
  11. }
  • Main函数使用 ```java

OrderFactory orderFactory = FactoryProducer.getFactory(“ALI”);

orderFactory.createPay().unifiedorder();

orderFactory.createRefund().refund();

  1. - ⼯⼚⽅法模式和抽象⼯⼚⽅法模式
  2. - 当抽象⼯⼚模式中每⼀个具体⼯⼚类只创建⼀个产品对象,抽象⼯⼚模式退化成⼯⼚⽅法模式
  3. - 优点
  4. - 当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能 保证使⽤⽅始终只使⽤同⼀个产品族中的对象
  5. - 产品等级结构扩展容易,如果需要增加多⼀个产品等级,只需要增加新的⼯⼚类和产品类即可, ⽐如增加银 ⾏⽀付、退款
  6. - 缺点
  7. - 产品族扩展困难,要增加⼀个系列的某⼀产品,既要在 抽象的⼯⼚和抽象产品⾥修改代码,不是很符合开闭原则
  8. - 增加了系统的抽象性和理解难度
  9. <a name="N5c6e"></a>
  10. # 3.Prototype原型模式
  11. - 原型设计模式Prototype
  12. - 是⼀种对象创建型模式,使⽤原型实例指定创建对象的 种类,并且通过拷⻉这些原型创建新的对象,主要⽤于 创建重复的对象,同时⼜能保证性能
  13. - ⼯作原理是将⼀个原型对象传给那个要发动创建的对 象,这个要发动创建的对象通过请求原型对象拷⻉⾃⼰ 来实现创建过程
  14. - 应该是最简单的设计模式了,实现⼀个接⼝,重写⼀个 ⽅法即完成了原型模式
  15. - 核⼼组成
  16. - Prototype: 声明克隆⽅法的接⼝,是所有具体原型类的公共⽗类,Cloneable接⼝
  17. - ConcretePrototype : 具体原型类
  18. - Client: 让⼀个原型对象克隆⾃身从⽽创建⼀个新的对象
  19. - 应⽤场景
  20. - 创建新对象成本较⼤,新的对象可以通过原型模式对已有对象进⾏复制来获得 如果系统要保存对象的状态,做备份使⽤
  21. - 比如新对象很大,你要重新去new一个,然后调用set方法去设置它的属性,开销大,
  22. - 使用克隆 ,可以直接复用。
  23. ```java
  24. @Data
  25. public class Person implements Cloneable {
  26. private String name;
  27. private int age;
  28. private List<String> list = new ArrayList<>();
  29. @Override
  30. protected Person clone() throws CloneNotSupportedException {
  31. return (Person) super.clone();
  32. }
  33. }
  34. public static void main(String [] args) throws CloneNotSupportedException {
  35. Person person1 = new Person();
  36. person1.setAge(10);
  37. person1.setName("小滴课堂-老王");
  38. person1.getList().add("aaa");
  39. Person person2 = person1.clone();
  40. person2.setName("Anna小姐姐");
  41. person2.getList().add("ccc");
  42. System.out.println("person1="+person1.getName()+", age="+person1.getAge());
  43. System.out.println("person2="+person2.getName()+", age="+person2.getAge());
  44. }
  • 遗留问题:
    • 通过对⼀个类进⾏实例化来构造新对象不同的是,原型模式是通过拷⻉⼀个现有对象⽣成新对象的
      • 浅拷⻉实现 Cloneable,深拷⻉是通过实现 Serializable 读取⼆进制流
    • 拓展
      • 浅拷⻉

如果原型对象的成员变量是基本数据类型(int、 double、byte、boolean、char等),将复制⼀份给 克隆对象;
如果原型对象的成员变量是引⽤类型,则将引⽤对象的地址复制⼀份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址
通过覆盖Object类的clone()⽅法可以实现浅克隆

  1. - 深拷⻉

⽆论原型对象的成员变量是基本数据类型还是引⽤类型, 都将复制⼀份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等⽅式来实现原型模式是内存⼆进制流的拷⻉,⽐new对象性能 ⾼很多,使⽤的时候记得注意是选择浅拷⻉还是深 拷⻉

  • 优点

    • 当创建新的对象实例较为复杂时,使⽤原型模式可以简 化对象的创建过程,可以提⾼新实例的创建效率
    • 可辅助实现撤销操作,使⽤深克隆的⽅式保存对象的状态,使⽤原型模式将对象复制⼀份并将其状态保存起 来,以便在需要的时候使⽤恢复到历史状态
  • 缺点

    • 需要为每⼀个类配备⼀个克隆⽅法,对已有的类进⾏改造时,需要修改源代码,违背了“开闭原则”
    • 在实现深克隆时需要编写较为复杂的代码,且当对象之 间存在多重的嵌套引⽤时,需要对每⼀层对象对应的类 都必须⽀持深克隆
  1. @Data
  2. public class Person implements Cloneable, Serializable {
  3. private String name;
  4. private int age;
  5. private List<String> list = new ArrayList<>();
  6. public Person() {
  7. System.out.println("构造函数调用");
  8. }
  9. @Override
  10. protected Person clone() throws CloneNotSupportedException {
  11. return (Person) super.clone();
  12. }
  13. /**
  14. * 深拷贝
  15. * @return
  16. */
  17. public Object deepClone() {
  18. try {
  19. //输出 序列化
  20. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  21. ObjectOutputStream oos = new ObjectOutputStream(baos);
  22. oos.writeObject(this);
  23. //输入 反序列化
  24. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  25. ObjectInputStream ois = new ObjectInputStream(bais);
  26. Person copyObj = (Person) ois.readObject();
  27. return copyObj;
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. return null;
  31. }
  32. }
  33. }
  1. public static void main(String [] args) throws CloneNotSupportedException {
  2. Person person1 = new Person();
  3. person1.setAge(10);
  4. person1.setName("小滴课堂-老王");
  5. person1.getList().add("aaa");
  6. //浅拷贝
  7. Person person2 = person1.clone();
  8. //深拷贝
  9. //Person person2 = (Person) person1.deepClone();
  10. person2.setName("Anna小姐姐");
  11. person2.getList().add("ccc");
  12. System.out.println("person1="+person1.getName()+", age="+person1.getAge());
  13. System.out.println("person2="+person2.getName()+", age="+person2.getAge());
  14. }

4.建造者模式

  • 建造者模式(Builder Pattern)

    • 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象, 将⼀个复杂对象的构建与它的表示分离,使得同样的构 建过程可以创建不同的表示
    • 允许⽤户只通过指定复杂对象的类型和内容就可以构建 它们,不需要知道内部的具体构建细节
  • 场景举例

    • KFC创建套餐:套餐是⼀个复杂对象,它⼀般包含主⻝ 如汉堡、烤翅等和饮料 如果汁、 可乐等组成部分,不同的套餐有不同的组合,⽽KFC的服务员可以根据顾客

的要求,⼀步⼀步装配这些组成部分,构造⼀份完整的套餐

  • 电脑有低配、⾼配,组装需要CPU、内存、电源、硬 盘、主板等

截图_20221127101110.png

  • 核⼼组成
    • Builder:抽象建造者,定义多个通⽤⽅法和构建⽅法
    • ConcreteBuilder:具体建造者,可以有多个
    • Director:指挥者,控制整个组合过程,将需求交给建造者,由建造者去创建对象
    • Product:产品⻆⾊
  1. @Data
  2. public class Computer {
  3. private String cpu;
  4. private String memory;
  5. private String mainboard;
  6. private String disk;
  7. private String power;
  8. }
  1. /**
  2. * 声明了建造者的公共方法
  3. */
  4. public interface Builder {
  5. /**
  6. *细节方法
  7. */
  8. void buildCpu();
  9. void buildMainboard();
  10. void buildDisk();
  11. void buildPower();
  12. void buildMemory();
  13. Computer createComputer();
  14. }
  1. public class LowComputerBuilder implements Builder{
  2. private Computer computer = new Computer();
  3. @Override
  4. public void buildCpu() {
  5. computer.setCpu("低配 CPU");
  6. }
  7. @Override
  8. public void buildMainboard() {
  9. computer.setMainboard("低配 主板");
  10. }
  11. @Override
  12. public void buildDisk() {
  13. computer.setDisk("低配 磁盘");
  14. }
  15. @Override
  16. public void buildPower() {
  17. computer.setPower("低配 电源");
  18. }
  19. @Override
  20. public void buildMemory() {
  21. computer.setMemory("低配 内存");
  22. }
  23. @Override
  24. public Computer createComputer() {
  25. return computer;
  26. }
  27. }
  28. public class HighComputerBuilder implements Builder{
  29. private Computer computer = new Computer();
  30. @Override
  31. public void buildCpu() {
  32. computer.setCpu("高配 CPU");
  33. }
  34. @Override
  35. public void buildMainboard() {
  36. computer.setMainboard("高配 主板");
  37. }
  38. @Override
  39. public void buildDisk() {
  40. computer.setDisk("高配 磁盘");
  41. }
  42. @Override
  43. public void buildPower() {
  44. computer.setPower("高配 电源");
  45. }
  46. @Override
  47. public void buildMemory() {
  48. computer.setMemory("高配 内存");
  49. }
  50. @Override
  51. public Computer createComputer() {
  52. return computer;
  53. }
  54. }
  1. public class Director {
  2. public Computer craete(Builder builder){
  3. builder.buildMemory();
  4. builder.buildCpu();
  5. builder.buildMainboard();
  6. builder.buildDisk();
  7. builder.buildPower();
  8. return builder.createComputer();
  9. }
  10. }
  11. public static void main(String[] args){
  12. Director director = new Director();
  13. director.create(new LowComputerBuilder());
  14. director.create(new HighComputerBuilder());
  15. }
  • 优点
    • 客户端不必知道产品内部组成的细节,将产品本身与产 品的创建过程解耦
    • 每⼀个具体建造者都相对独⽴,⽽与其他的具体建造者 ⽆关,更加精细地控制产品的创建过程
    • 增加新的具体建造者⽆须修改原有类库的代码,符合开 闭原则
    • 建造者模式结合链式编程来使⽤,代码上更加美观
  • 缺点

    • 建造者模式所创建的产品⼀般具有较多的共同点,如果产品差异⼤则不建议使⽤
  • JDK⾥⾯的应⽤

    • tcp传输协议 protobuf ⽣成的api、java中的 StringBuilder(不完全⼀样,思想⼀样)
  • 建造者模式与抽象⼯⼚模式的⽐较:

    • 建造者模式返回⼀个组装好的完整产品 , 抽象⼯⼚模 式返回⼀系列相关的产品,这些产品位于不同的产品等级结构,构成了⼀个产品族