• 【Java笔记+踩坑】设计模式——原型模式


    导航:

    【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/黑马旅游/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码-CSDN博客

    目录

    零、经典的克隆羊问题(复制10只属性相同的羊)

    一、传统方案:循环new对象

    1.1 实现方案

    1.2 优缺点和改进思路 

    二、原型模式(Prototype模式)

    2.1 基本介绍

    2.2 原理

    2.2.1 UML图:原型接口、具体原型类、客户端代码

    2.2.2 代码演示

    2.3 原型模式解决克隆羊问题

    2.4 优缺点和使用场景 

    2.4.1 优点

    2.4.2 缺点

    2.4.3 适用场景

    三、扩展

    3.1 Spring源码中的原型模式:ApplicationContext类的getBean()方法

    3.2 浅拷贝和深拷贝

    3.2.1 浅拷贝:引用类型变量拷贝引用

    3.2.2 深拷贝:引用类型变量拷贝值


    零、经典的克隆羊问题(复制10只属性相同的羊)

    问题描述:现在有一只羊,姓名为 Tom,年龄为 1,颜色为白色,请编写程序创建和 Tom 羊属性完全相同的 10 只羊。

    一、传统方案:循环new对象

    1.1 实现方案

    羊类:

    1. public class Sheep {
    2.     private String name;
    3.     private Integer age;
    4.     public Sheep(String name, Integer age) {
    5.         this.name = name;
    6.         this.age = age;
    7.     }
    8.     
    9.     public String getName() {
    10.         return name;
    11.     }
    12.     public void setName(String name) {
    13.         this.name = name;
    14.     }
    15.     public Integer getAge() {
    16.         return age;
    17.     }
    18.     public void setAge(Integer age) {
    19.         this.age = age;
    20.     }
    21. }

    克隆羊:

    1. public class Client {
    2.     public static void main(String[] args) {
    3.         for (int i = 0; i < 10; i++) {
    4.             Sheep sheep = new Sheep("Tom", 1, "白色");
    5.             System.out.println(sheep);
    6.         }
    7.     }
    8. }

    1.2 优缺点和改进思路 

    传统方法优点

    • 好理解,简单易操作

    缺点:

    • 每次获取再复制效率低:在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
    • 不灵活:总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活

    改进的思路分析:Object 类的 clone() 方法

    Object 类是所有类的根类,Object 类提供了一个 clone 方法,该方法可以将一个 Java 对象复制一份,但是对应的类必须实现Cloneable接口,该接口表示该类能够复制且具有复制的能力 ==> 原型模式

    二、原型模式(Prototype模式)

    2.1 基本介绍

    原型模式(Prototype 模式):原型实例指定创建对象种类,并通过拷贝原型创建新的对象

    原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

    原理:将一个原型对象传给要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()

    形象的理解:孙大圣拔出猴毛,变出其它孙大圣

    创建型设计模式:关注如何有效地创建对象,以满足不同的需求和情境。

    包括:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

    2.2 原理

    2.2.1 UML图:原型接口、具体原型类、客户端代码

    • Prototype:原型类。包含一个用于复制对象的克隆方法。可以使用Cloneable接口作为原型接口。
    • ConcretePrototype:具体原型类。实现原型接口、重写克隆方法clone()的具体类。
    • Client:让一个原型对象克隆自己,创建一个属性相同的对象

    2.2.2 代码演示

    原型接口: 可以是Cloneable接口也可以是自定义带clone()方法的接口

    1. // 步骤1:定义原型接口
    2. interface Prototype extends Cloneable {
    3. Prototype clone();
    4. }

    具体原型类: 

    1. // 步骤2:实现具体原型类
    2. class ConcretePrototype implements Prototype {
    3. @Override
    4. public Prototype clone() {
    5. try {
    6. return (Prototype) super.clone(); // 使用浅拷贝
    7. } catch (CloneNotSupportedException e) {
    8. e.printStackTrace();
    9. return null;
    10. }
    11. }
    12. }

    客户端代码: 通过clone()方法创建原型对象

    1. // 步骤3:客户端代码
    2. public class Client {
    3. public static void main(String[] args) {
    4. //创建具体类对象
    5. ConcretePrototype prototype = new ConcretePrototype();
    6. //通过clone方法创建对象
    7. ConcretePrototype clonedObject = (ConcretePrototype) prototype.clone();
    8. }
    9. }

    2.3 原型模式解决克隆羊问题

    问题回顾:

     现在有一只羊,姓名为 Tom,年龄为 1,颜色为白色,请编写程序创建和 Tom 羊属性完全相同的 10 只羊。

    UML 类图

    原型接口:Cloneable接口。

    具体原型类:实现Cloneable接口

    1. @Data
    2. public class Sheep implements Cloneable {
    3. private String name;
    4. private Integer age;
    5. private String color;
    6. public Sheep(String name, Integer age, String color) {
    7. this.name = name;
    8. this.age = age;
    9. this.color = color;
    10. }
    11. @Override
    12. protected Object clone() {
    13. Sheep sheep = null;
    14. try {
    15. sheep = (Sheep) super.clone();
    16. } catch (Exception e) {
    17. e.printStackTrace();
    18. }
    19. return sheep;
    20. }
    21. }

    客户端: 调用具体原型类的clone()方法创建10个对象

    1. public class Client {
    2. public static void main(String[] args) {
    3. Sheep sheep = new Sheep("Tom", 1, "白色");
    4. for (int i = 0; i < 10; i++) {
    5. Sheep sheep1 = (Sheep) sheep.clone();
    6. System.out.println(sheep1);
    7. }
    8. }
    9. }

    2.4 优缺点和使用场景 

    2.4.1 优点

    • 构造方法复杂时开销小:如果构造函数的逻辑很复杂,此时通过new创建该对象会比较耗时,那么就可以尝试使用克隆来生成对象。
    • 运行时动态创建对象:不用重新初始化对象,而是动态地获得对象运行时的状态
    • 开闭原则(OCP原则):如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需更改客户端代码。相反,如果使用new方式,就需要在客户端修改构造参数。这使得系统更加灵活和可维护。
    • 对象封装性:原型模式可以帮助保护对象的封装性,因为客户端代码无需了解对象的内部结构,只需知道如何克隆对象。
    • 多态性:原型模式支持多态性,因为克隆操作可以返回具体子类的对象,而客户端代码不需要关心对象的具体类。

    扩展:

    开闭原则(OCP原则):代码对修改关闭,对扩展开放。

    2.4.2 缺点

    • 构造方法简单时开销大:如果构造函数的逻辑很简单,原型模式的效率不如new,因为JVM对new做了相应的性能优化。
      1. //验证构造方法简单时,原型模式开销大
      2. long startTime = System.currentTimeMillis();
      3. Student student = new Student();
      4. //克隆循环十万次
      5. for (int i = 0; i < 100000; i++) {
      6. student.clone();
      7. }
      8. long midTime = System.currentTimeMillis();
      9. //20ms
      10. System.out.println("克隆生成对象耗费的时间:" + (midTime - startTime) + "ms");
      11. //new10万次
      12. for (int i = 0; i < 100000; i++) {
      13. new Student();
      14. }
      15. //5ms
      16. System.out.println("new生成对象耗费的时间:" + (System.currentTimeMillis() - midTime) + "ms");
    • 要注意深拷贝和浅拷贝问题:实现Cloneable接口时,如果具体原型类直接返回super.clone(),则是浅拷贝。克隆的对象里,引用类型变量只拷贝引用,依然指向旧的地址。
    • 代码复杂性:在实现深拷贝的时候可能需要比较复杂的代码。设计模式一般都是以代码复杂性为代价,提高可扩展性、可读性。

    2.4.3 适用场景

    1. 构造方法复杂:要创建的对象构造方法逻辑很复杂,即创建新的对象比较复杂时,使用原型模式会比直接new效率更高;
    2. 经常需要克隆:经常要创建一个和原对象属性相同的对象时,可以考虑原型模式。

    三、扩展

    3.1 Spring源码中的原型模式:ApplicationContext类的getBean()方法

    Spring 框架Bean的生命周期中,ApplicationContext类的getBean()方法中,有用到原型模式。

    获取Bean时会判断配置的Bean是单例还是原型,如果是原型,则用原型模式创建Bean。

    验证:bean指定原型模式后,getBean()获取到的多个Bean是不同对象。

    1. @Component("id01")
    2. @Scope("prototype")
    3. public class Monster {
    4. private String name;
    5. private int health;
    6. public Monster() {
    7. // 默认构造函数
    8. }
    9. public Monster(String name, int health) {
    10. this.name = name;
    11. this.health = health;
    12. }
    13. // 添加其他属性和方法
    14. }

    也可以用xml形式注册Bean:

    1. <bean id="id01" class="com.atquigu.spring.bean.Monster" scope="prototype"/>
    2. beans>

    测试: 

    1. public class ProtoType {
    2. public static void main(String[] args) {
    3. // TODO Auto-generated method stub
    4. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    5. // 通过ID获取Monster
    6. Object bean = applicationContext.getBean("id01");
    7. System.out.println("bean: " + bean); // 输出“牛魔王"....
    8. Object bean2 = applicationContext.getBean("id01");
    9. System.out.println("bean2: " + bean2); // 输出“牛魔王"....
    10. System.out.println(bean == bean2); // false
    11. }
    12. }

    注解方式是@Scope("prototype")。

    回顾:

    手写Spring源码(简化版)-CSDN博客

    3.2 浅拷贝和深拷贝

    3.2.1 浅拷贝:引用类型变量拷贝引用

    • 浅拷贝:拷贝后对象是新地址,基本类型变量拷贝值,引用类型变量拷贝引用。只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。
    • 深拷贝:拷贝后对象是新地址,基本类型变量拷贝值,引用类型变量拷贝克隆后的值。创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。反序列化创建对象是深拷贝。

    实现方案:具体原型类直接返回super.clone()

    实现Cloneable 接口,重写 clone()方法, 直接返回super.clone()。

    1. public class Person implements Cloneable { //虽然clone()是Object类的方法,但Java规定必须得实现一下这个接口
    2. public int age;//基本类型变量拷贝值
    3. public Person(int age) {
    4. this.age = age;
    5. }
    6. @Override
    7. public Person clone() {
    8. try {
    9. return (Person) super.clone();
    10. } catch (CloneNotSupportedException e) {
    11. return null;
    12. }
    13. }
    14. public static void main(String[] args) {
    15. Person p1 = new Person(18);
    16. Person p2 = p1.clone(); //p2将是p1浅拷贝的对象
    17. p2.age = 20;
    18. System.out.println(p1 == p2); // false。拷贝后对象是新地址
    19. System.out.println(p1.age); // 18。基本类型变量拷贝值
    20. }
    21. }

    3.2.2 深拷贝:引用类型变量拷贝值

    深拷贝:拷贝后对象是新地址,基本类型变量拷贝值,引用类型变量拷贝克隆后的值。创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。反序列化创建对象是深拷贝。 

    实现方案:具体原型类专门克隆引用类型变量

    实现Cloneable 接口,重写 clone()方法, 给super.clone()的引用类型成员变量也clone()一下,然后再返回克隆的对象。 

    1. public class Person implements Cloneable {
    2. public int age;//基本类型变量拷贝值
    3. public int[] arr = new int[] {1, 2, 3};
    4. public Person(int age) {
    5. this.age = age;
    6. }
    7. @Override
    8. public Person clone() {
    9. try {
    10. Person person = (Person) super.clone();
    11. person.arr = this.arr.clone(); // 用引用类型的 clone 方法,引用类型变量拷贝克隆后的值
    12. return person;
    13. } catch (CloneNotSupportedException e) {
    14. return null;
    15. }
    16. }
    17. }

  • 相关阅读:
    vue3前端开发-小兔鲜项目-添加购物车操作第一步
    Java笔记——控制台模拟“双色球”福利彩票游戏
    【从零开始学微服务】01.微服务的过去与现在
    Linux配置telnet服务端
    kile5上的一栏快捷键消失了,我手贱删了
    三次输错密码后,系统是怎么做到不让我继续尝试的?
    continue和break的区别与用法
    【教学类-19-02】20221127《ABCABC式-规律排序-A4竖版2份》(中班)
    66. SAP ABAP Function Module 的动态调用方式使用方式介绍
    计算机组装与维护实训室解决方案
  • 原文地址:https://blog.csdn.net/qq_40991313/article/details/133910728