• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【设计模式与体系结构】创建型模式-原型模式

简介

原型模式(Prototype Pattern)指的是用一个已经创建的对象作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式的角色

  1. 抽象原型类:规定具体原型对象必须实现的 \(clone()\) 方法
  2. 具体原型类:实现抽象原型类的 \(clone()\) 方法,它是可被复制的对象
  3. 访问类:使用具体原型类的 \(clone()\) 方法来复制新的对象

原型模式的类型

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

原型模式的使用场景

  1. 对象的创建过程十分复杂,可以使用原型模式快速创建对象
  2. 性能和安全要求比较高

正文

浅克隆

世上第一只克隆羊 Dolly 人尽皆知,下面就以此写一份代码:
母羊的代码:

public class Sheep implements Cloneable {
    private String gene;//非基本类型,因此浅克隆得到的是引用类型
    
    @NonNull
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String getGene() {
        return gene;
    }

    public void setGene(String gene) {
        this.gene = gene;
    }
}

克隆出 Dolly 的代码:

private void shallowClone() {
    Sheep mother = new Sheep();
    mother.setGene("456");
    try {
        Sheep Dolly = (Sheep) mother.clone();
        Log.i("Clone", "mother == Dolly? " + (mother == Dolly));
        Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
    }
}

运行截图如下:

由上述代码不难看出,实现 \(Cloneable\) 接口的克隆方式是一种浅克隆。Dolly 和母羊明显是不同的个体,但是有相同的基因片段(视为引用类型),因此使用浅克隆是合适的。

对于引用类型的浅克隆,我们有一个共识:修改克隆的对象的引用类型属性后,被克隆对象的对应属性也一同修改(准确点说是修改的同一个属性)。不妨对 Dolly 的案例进行测试:

private void shallowClone() {
    Sheep mother = new Sheep();
    mother.setGene("456");
    try {
        Sheep Dolly = (Sheep) mother.clone();
        Log.i("Clone", "mother == Dolly? " + (mother == Dolly));
        Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
        Dolly.setGene("123");
        Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
    }
}

运行截图如下:

既然对于非基本类型的浅克隆都是引用类型,为什么会出现修改 Dolly 的 String 类型的 gene 属性时,母羊没有同时修改呢?
答:String 类型虽然属于非基本类型,但是 Java 存在常量池机制,不同的 String 类型字符串都会存储在常量池中,因此不同对象的 gene 指向了不同的地址。

因此我们需要对 String 这个非基本类型特别看待,那么我们不妨用其他非基本类型的数据对“引用类型”这个性质进行验证。

深克隆

假设同学 A 和同学 B 是同班同学。每个学生都有姓名、性别等属性,这些属性都是非基本类型 String,也有年龄(int)、身高(float)等属性,这些属性都是基本类型。
对每个同学而言,都具备姓名、性别、年龄和身高等属性,且是个人特有的。假设我们想由同学 A 对象克隆出同学 B 对象,那么非基本类型再使用浅克隆就不合适了。因为两位同学的名字、性别等明显是不共享的,因此不可以使用引用类型,而是要用拷贝类型,所以要使用深克隆。

对于深克隆的参考文章为:https://www.cnblogs.com/gollong/p/9668699.html

对于引用类型,我们将其也实现 Cloneable 接口,这样子就可以使得引用类型的属性也变为拷贝类型

实现方式一:clone()函数的嵌套调用

定义一个学生类 Student.java

学生类
public class Student implements Cloneable {
	private Information information;
	private int schoolId;
	
	public Student(int schoolId, int age, float height) {
		this.schoolId = schoolId;
		this.information = new Information(age, height);
	}
	
	public Information getInformation() {
		return information;
	}
	
	public void setInformation(Information information) {
		this.information = information;
	}
	
	public int getSchoolId() {
		return schoolId;
	}
	
	public void setSchoolId(int schoolId) {
		this.schoolId = schoolId;
	}

	@Override
	public Student clone() throws CloneNotSupportedException {
		Student student = (Student) super.clone();
		student.information = (Information) information.clone();
		return student;
	}
}

定义一个信息类 Information.java

信息类
public class Information implements Cloneable {
	private int age;
	private float height;
	
	public Information(int age, float height) {
		this.age = age;
		this.height = height;
	}
	
	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public float getHeight() {
		return height;
	}
	
	public void setHeight(float height) {
		this.height = height;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public String toString() {
		return "age: " + getAge() + "  height: " + getHeight();
	}
	
}

测试深克隆模式实现方式一

clone() 方法的嵌套调用测试
private void testDeepClone() {
    Student stu1 = new Student(1, 15, 165.5f);
    try {
        Student stu2 = stu1.clone();
  	    System.out.println("修改前:");
  	    System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
  	    System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
  	    System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
  	    System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
  	    System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
  			
  	    System.out.println("修改后:");
  	    stu1.setSchoolId(2);
  	    stu2.getInformation().setAge(18);
  	    stu2.getInformation().setHeight(175.3f);
  	    System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
  	    System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
  	    System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
  	    System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
  	    System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
    } catch (CloneNotSupportedException e) {
  	    e.printStackTrace();
    }
}

运行截图如下:

实现方式二:序列化

clone() 方法的嵌套调用,已经可以实现深克隆的需求。但是当所要克隆的类嵌套深度较大,或者数据结构较为复杂的情况下,方式一就略显复杂,并且不符合开闭原则。而序列化只需要为每一个类实现一个 Serializable 接口,最后通过序列化和反序列化即可实现深克隆。

定义一个学生类 Student.java

学生类
public class Student implements Serializable {
	private Information information;
	private int schoolId;
	
	public Student(int schoolId, int age, float height) {
		this.schoolId = schoolId;
		this.information = new Information(age, height);
	}
	
	public Information getInformation() {
		return information;
	}
	
	public void setInformation(Information information) {
		this.information = information;
	}
	
	public int getSchoolId() {
		return schoolId;
	}
	
	public void setSchoolId(int schoolId) {
		this.schoolId = schoolId;
	}
}

定义一个信息类 Information.java

信息类
public class Information implements Serializable {
	private int age;
	private float height;
	
	public Information(int age, float height) {
		this.age = age;
		this.height = height;
	}
	
	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public float getHeight() {
		return height;
	}
	
	public void setHeight(float height) {
		this.height = height;
	}

	@Override
	public String toString() {
		return "age: " + getAge() + "  height: " + getHeight();
	}
	
}

测试深克隆模式实现方式二

序列化测试
private void testDeepClone() {
	Student stu1 = new Student(1, 15, 165.5f);
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	ObjectOutputStream oos;
	try {
		oos = new ObjectOutputStream(baos);
		oos.writeObject(stu1);
		ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
		ObjectInputStream ois = new ObjectInputStream(bais);
		Student stu2 = (Student) ois.readObject();
		System.out.println("修改前:");
		System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
		System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
		System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
		System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
		System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
			
		System.out.println("修改后:");
		stu1.setSchoolId(2);
		stu2.getInformation().setAge(18);
		stu2.getInformation().setHeight(175.3f);
		System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
		System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
		System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
		System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
		System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
	} catch (IOException e) {
		e.printStackTrace();
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}
}

运行截图如下:

posted on 2025-01-10 21:56  RomanLin  阅读(26)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3