设计模式(六)创建者模式之原型模式

概述

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

结构

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

类图

在这里插入图片描述

原型模式的克隆分为浅克隆和深克隆。
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆不再指向原有对象地址

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:

类图实现

浅克隆

UserPrototype(具体的原型类):

package com.lx.design.creator.prototype;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 15:11
 * 用户
 */
public class UserPrototype  implements Cloneable{
    public UserPrototype() {
        System.out.println("原型对象创建成功");
    }

    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected UserPrototype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功!");
        return (UserPrototype) super.clone();
    }
}

Client测试类

package com.lx.design.creator.prototype;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 15:29
 */
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        UserPrototype r1 = new UserPrototype();

        UserPrototype r2 = r1.clone();

        System.out.println(" 原型对象r1和克隆对象r2是否是同一个对象?" + (r1 == r2));
    }
}

输出结果为false,为什么呢?

当调用 r1.clone() 时,它会创建并返回一个新的对象,这个新对象是通过复制 r1 的字段而创建的。虽然这两个对象可能包含相同的数据,但它们在内存中是不同的对象实例。

如果希望 r1 == r2 返回 true

需要在 UserPrototype 类中重写 clone() 方法,确保实现深拷贝。深拷贝会递归地复制对象及其所有引用的对象,从而确保两个对象是彻底独立的,而不仅仅是引用地址不同。

r1.clone();是怎么创建对象的呢?

通过上面的案例。看出控制台只打印一次原型对象创建成功。说明clone()不是通过构造方法进行创建对象的。它是通过内存复制的方式直接创建对象。这也是为什么 r2 和 r1 是不同的对象,尽管它们的内容相同。

用原型模式生成“三好学生”奖状

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
在这里插入图片描述

Citation

package com.lx.design.creator.prototype.citation;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 16:38
 */
public class Citation implements Cloneable {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return (this.name);
    }

    public void show() {
        System.out.println(name + "同学: 被评为三好学生");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}

CitationTest

package com.lx.design.creator.prototype.citation;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 16:39
 */
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation c1 = new Citation();
        c1.setName("张三");

        //复制奖状
        Citation c2 = c1.clone();
        //将奖状的名字修改李四
        c2.setName("李四");

        c1.show();
        c2.show();
    }
}

输出结果为

张三同学: 被评为三好学生
李四同学: 被评为三好学生

问题 为什么c2.setName(“李四”);设置之后,c1和c2不应该都是李四吗?

对象的基本类型属性值是基于值传递的,而不是引用传递。即使在原型模式中通过克隆对象,对象的字段仍然是独立的,修改一个对象的字段值不会影响另一个对象相同字段的值。这就是为什么你看到的现象是 c1 的值没有因为 c2.setName(“李四”);而改变的原因。

让我们详细解释一下:
String 是不可变的:在Java中,String 对象是不可变的,也就是说一旦创建,它的值就不能被修改。当你调用 setName() 方法时,它实际上是创建了一个新的 String 对象,并将这个新对象的引用赋给了 name 字段。这不会影响原来 String 对象的值,因为它们是不可变的。

使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

深克隆

Student

package com.lx.design.creator.prototype.citation;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 17:52
 */
public class Student {
    private String name;
    private String address;

    public Student() {
    }

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Citation

package com.lx.design.creator.prototype.citation;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 16:38
 */
public class Citation implements Cloneable {
    private Student stu;
    private String age;

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
    void show() {
        System.out.println(stu.getName() + "同学:终于在"+age+"岁,进阶元婴");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}

CitationTest

package com.lx.design.creator.prototype.citation;

/**
 * TODO 添加描述
 *
 * @author wangLJ
 * @date 2024/6/17 16:39
 */
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {

        Citation c1 = new Citation();
        Student stu = new Student("张三", "西安");
        c1.setAge("450");
        c1.setStu(stu);

        //复制奖状
        Citation c2 = c1.clone();
        //获取c2奖状所属学生对象
        Student stu1 = c2.getStu();
        stu1.setName("李四");
        c2.setAge("360");

        //判断stu对象和stu1对象是否是同一个对象
        System.out.println("stu和stu1是同一个对象?" + (stu == stu1));

        c1.show();
        c2.show();
    }
}

输出结果为:

stu和stu1是同一个对象?true
李四同学:终于在450岁,进阶元婴
李四同学:终于在360岁,进阶元婴

问题分析

在这段代码中,虽然通过浅拷贝的方式复制了Citation对象 c1 生成了 c2,但是在复制过程中,只是简单地复制了基本数据类型和引用类型的地址,并没有对引用类型的对象进行深度复制。所以在修改 c2 中的学生对象名字时,实际上是修改了同一个学生对象的属性,因此导致最后输出的两个奖状对象的学生姓名都变成了 “李四”。
因此,尽管 c1c2 是不同的 Citation 对象,但它们引用学生对象仍然是同一个,所以 stu 和 stu1 是同一个对象。
c1和c2中的age 更改成功,是因为基本类型是直接值传递,只复制值,所以是不影响的。

实现深克隆

序列化
Citation 和Student 需要实现Serializable

 public static void main(String[] args) throws Exception {
        Citation c1 = new Citation();
        Student stu = new Student("张三", "西安");
        c1.setStu(stu);

        //创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\b.txt"));
        //将c1对象写出到文件中
        oos.writeObject(c1);
        oos.close();

        //创建对象出入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\b.txt"));
        //读取对象
        Citation c2 = (Citation) ois.readObject();
        //获取c2奖状所属学生对象
        Student stu1 = c2.getStu();
        stu1.setName("李四");

        //判断stu对象和stu1对象是否是同一个对象
        System.out.println("stu和stu1是同一个对象?" + (stu == stu1));

        c1.show();
        c2.show();
    }

从序列化和反序列化可以看出,深克隆就是把对象转换成文件从而再转换出来。那么我们也可以把对象转换成JSON, 在转换为对象,也是可以完成对象的深克隆, 知道理念之后。可以有很多种深克隆的办法

  • 使用 Apache Commons Lang 的 SerializationUtils
		Student stu = new Student("张三", "西安");
       Citation c1 = new Citation(stu);

       // 深克隆
       Citation c2 = SerializationUtils.clone(c1);
       Student stu1 = c2.getStu();
       stu1.setName("李四");

       System.out.println("stu和stu1是同一个对象?" + (stu == stu1));

       c1.show();
       c2.show();
  • 使用 Gson

      Gson gson = new Gson();
      Student stu = new Student("张三", "西安");
      Citation c1 = new Citation(stu);

      // 深克隆
      String json = gson.toJson(c1);
      Citation c2 = gson.fromJson(json, Citation.class);
      Student stu1 = c2.getStu();

总结
原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实例化类。这样可以避免耗时的实例化操作,提高性能。以下是原型模式的优缺点:

优点:

  • 减少对象的创建时间:通过复制现有对象来创建新对象,避免了耗时的实例化操作,提高了对象创建的效率。
  • 简化对象的创建过程:无需关心对象的具体创建过程,只需要根据现有对象进行复制即可,简化了对象的创建过程。
  • 动态添加或删除对象:可以动态地添加或删除原型,方便灵活地管理对象。

缺点:

  • 对象复杂时性能下降:如果对象比较复杂,包含很多属性和关联关系,复制对象可能会导致性能下降。
  • 深拷贝与浅拷贝问题:在复制对象时需要考虑深拷贝和浅拷贝的问题,确保复制的对象完整性。
    难以理解:原型模式相对于其他创建型模式(如工厂模式、建造者模式)更加抽象,可能会增加代码的复杂度和难度。
    总的来说,原型模式适用于需要频繁创建相似对象,并且对象的创建过程比较复杂或耗时的情况下,能够提高效率和简化代码。
  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值