瑞_23种设计模式_原型模式

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的原型模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《瑞_23种设计模式_装饰者模式》
  桥接模式:《瑞_23种设计模式_桥接模式》
  外观模式:《瑞_23种设计模式_外观模式》
  组合模式:《瑞_23种设计模式_组合模式》
  享元模式:《瑞_23种设计模式_享元模式》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《瑞_23种设计模式_模板方法模式》
  策略模式:《瑞_23种设计模式_策略模式》
  命令模式:《瑞_23种设计模式_命令模式》
 职责链模式:《瑞_23种设计模式_职责链模式》
  状态模式:《瑞_23种设计模式_状态模式》
 观察者模式:《瑞_23种设计模式_观察者模式》
 中介者模式:《瑞_23种设计模式_中介者模式》
 迭代器模式:《瑞_23种设计模式_迭代器模式》
 访问者模式:《瑞_23种设计模式_访问者模式》
 备忘录模式:《瑞_23种设计模式_备忘录模式》
 解释器模式:《瑞_23种设计模式_解释器模式》

在这里插入图片描述

1 原型模式(Prototype Pattern)

  原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。

  这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

瑞:原型模式就是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。所以本质上就是深浅克隆

1.1 介绍

  • 意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

  • 主要解决:在运行期建立和删除原型。

  • 何时使用
      1️⃣ 当一个系统应该独立于它的产品创建,构成和表示时。
      2️⃣ 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
      3️⃣ 为了避免创建一个与产品类层次平行的工厂类层次时。
      4️⃣ 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

  • 如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

  • 关键代码
      1️⃣ 实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
      2️⃣ 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

  • 应用实例
      1️⃣ 细胞分裂。
      2️⃣ JAVA 中的 Object clone() 方法。

  • 优点
      1️⃣ 性能提高。
      2️⃣ 逃避构造函数的约束。

  • 缺点
      1️⃣ 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
      2️⃣ 必须实现 Cloneable 接口。

  • 使用场景
      1️⃣ 资源优化场景。
      2️⃣ 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
      3️⃣ 性能和安全要求的场景。
      4️⃣ 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
      5️⃣ 一个对象多个修改者的场景。
      6️⃣ 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
      7️⃣ 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

  • 注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

1.2 原型模式的结构

  • 原型模式包含如下角色:
      1️⃣ 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
      2️⃣ 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
      3️⃣ 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

  接口类图如下:

在这里插入图片描述




2 实现(浅克隆和深克隆)

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

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

Realizetype(具体的原型类)
public class RealizeType implements Cloneable {

    public RealizeType() {
        System.out.println("具体的原型对象创建完成!");
    }

    @Override
    public RealizeType clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功!");
        return (RealizeType) super.clone();
    }
}
PrototypeTest(测试访问类)
public class PrototypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建一个原型类对象
        RealizeType realizetype = new RealizeType();

        // 调用Realizetype类中的clone方法进行对象的克隆
        RealizeType clone = realizetype.clone();

        System.out.println("原型对象和克隆出来的是否是同一个对象?" + (realizetype == clone));
    }
}

  运行结果如下:

	具体的原型对象创建完成!
	具体原型复制成功!
	原型对象和克隆出来的是否是同一个对象?false



3 案例

3.1 需求

【案例】用原型模式生成“三好学生”奖状

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

3.2 设计

  类图如下:

在这里插入图片描述

3.3 代码实现

3.3.1 浅克隆代码实现
  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
学生类(类)

/**
 * 学生类 —— 浅克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class Student {
    private String name;
    private String address;

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

    public Student() {
    }

    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;
    }
}
奖状类(类)
import java.time.LocalDate;

/**
 * 奖状类 —— 浅克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class Citation implements Cloneable {
    private Student student;

    public Student getStudent() {
        return student;
    }

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

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

    public void show() {
        System.out.println(student.getName() + "同学,地址[" + student.getAddress() + "]:在[" + LocalDate.now().getYear() + "]学年中表现优秀,被Ray评为三好学生。特发此状!");
    }
}

测试类
/**
 * 测试类 —— 浅克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1.创建原型对象
        Citation citation = new Citation();
        // 创建张三学生对象
        Student stu = new Student();
        stu.setName("张三");
        stu.setAddress("深圳");
        citation.setStudent(stu);

        // 2.克隆奖状对象
        Citation citation1 = citation.clone();
        Student stu1 = citation1.getStudent();
        stu1.setName("李四");
        stu1.setAddress("成都");

        // 3.调用show方法展示
        citation.show();
        citation1.show();

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

        // 浅克隆效果进一步演示
        stu.setName("Ray");
        citation.show();
        citation1.show();
    }
}

  运行结果如下:

	李四同学,地址[成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	李四同学,地址[成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	stu和stu1是同一个对象?true
	Ray同学,地址[成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	Ray同学,地址[成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!

瑞:stu对象stu1对象是同一个对象,就会产生将stu1对象中name属性值改为“李四”,两个Citation(奖状)对象中显示的都是李四的现象。继续验证,对stu对象中name属性值改为“Ray”,两个Citation(奖状)对象中显示的都是Ray。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。如果不希望这种情况出现,需要使用深克隆,进行深克隆需要使用对象流

3.3.2 深克隆代码实现
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。深拷贝是通过实现 Serializable 接口读取二进制流
学生类(类)
import java.io.Serializable;

/**
 * 学生类 —— 深克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class Student implements Serializable {
    private String name;
    private String address;

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

    public Student() {
    }

    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;
    }

}

瑞:相较于浅克隆,学生类实现了Serializable 接口

奖状类(类)

import java.io.Serializable;
import java.time.LocalDate;

/**
 * 奖状类 —— 深克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class Citation implements Cloneable, Serializable {

    private Student student;

    public Student getStudent() {
        return student;
    }

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

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

    public void show() {
        System.out.println(student.getName()  + "同学,地址[" + student.getAddress() + "]:在[" + LocalDate.now().getYear() + "]学年中表现优秀,被Ray评为三好学生。特发此状!");
    }
}

瑞:相较于浅克隆,奖状类实现了Serializable 接口,测试类通过二进制流完成深克隆

测试类
import java.io.*;

/**
 * 测试类 —— 深克隆
 *
 * @author LiaoYuXing-Ray
 **/
public class CitationTest {
    public static void main(String[] args) throws Exception {
        // 1.创建原型对象
        Citation citation = new Citation();
        // 创建张三学生对象
        Student stu = new Student();
        stu.setName("张三");
        stu.setAddress("深圳");
        citation.setStudent(stu);
        // 获取当前文件的绝对路径
        String absolutePath = System.getProperty("user.dir");
        String filePath = absolutePath + File.separator + "CitationTest.txt";
        // 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        // 写对象
        oos.writeObject(citation);
        // 释放资源
        oos.close();

        // 创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
        // 读取对象
        Citation citation1 = (Citation) ois.readObject();
        // 释放资源
        ois.close();
        Student stu1 = citation1.getStudent();
        stu1.setName("李四");
        stu1.setAddress("成都");

        citation.show();
        citation1.show();

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

        // 深克隆效果进一步演示
        stu.setName("Ray001");
        stu.setAddress("广东深圳");
        stu1.setName("Ray002");
        stu1.setAddress("四川成都");
        citation.show();
        citation1.show();
    }
}

注:
  1️⃣ 以上代码主要是为了演示深克隆的效果,对流的处理并不规范
  2️⃣ Citation 类和 Student 类必须实现 Serializable 接口,否则会抛 NotSerializableException 异常。

  运行结果如下:

	张三同学,地址[深圳]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	李四同学,地址[成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	stu和stu1是同一个对象?false
	Ray001同学,地址[广东深圳]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!
	Ray002同学,地址[四川成都]:在[2024]学年中表现优秀,被Ray评为三好学生。特发此状!

4 总结

4.1 原型模式的使用场景

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

4.2 浅克隆和深克隆的区别

  通过以上浅克隆和深克隆的代码实现结果可以得出,如果有一个Citation对象,其中包含了一个Student对象的引用:
  浅克隆生成的新Student对象会和原对象共享同一个Student对象,因为浅克隆不会复制被引用的对象,而是复制引用本身,也就是说,新对象和原对象会共享相同的被引用对象
  而深克隆则会为新Citation对象创建一个独立的Student对象副本。这样,对原对象或其任何被引用对象的修改都不会影响到克隆出来的对象

  总的来说:浅克隆适用于不需要复制引用类型属性的场景,实现起来相对简单;而深克隆则用在需要完全独立的对象副本的场景,实现起来较为复杂,但能确保原对象和克隆出的对象互不影响。




本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞486

你的点赞评论收藏才更是动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值