zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【设计模式】-创建型模式-第2章第4讲-【原型模式】

模式设计模式 创建 原型
2023-09-11 14:22:32 时间

目录

1、原型模式(Prototype Pattern)概念

2、浅拷贝与深拷贝

2.1、概念

2.2、Java 中的深浅拷贝

浅拷贝:

深拷贝:

实例

浅拷贝

深拷贝的两种实现方式

方式一

方式二

3、原型模式的优缺点

4、 结尾


1、原型模式(Prototype Pattern)概念

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

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

原型模式看似复杂,实际上它只是一种克隆对象的方法。现在实例化对象操作并不特别耗费性能,那为啥还需要对象克隆呢?在以下几种情况下,确实需要克隆那些已经实例化的对象:

  • 依赖于外部资源或硬件密集型操作进行新对象的创建的情况。
  • 获取相同对象在相同状态的拷贝而无需进行重复获取状态操作操作的情况。
  • 在不确定所需具体类时需要对象的实例的情况。

先来看一个原型模式的类图,如图1-1 

 图1-1

从上面类图中,我们可以看到在原型模式中,主要涉及以下两个类:

  • Prototype (抽象原型类):声明了 clone()方法的接口或基类,其中 clone() 方法必须由派生对象实现。在简单场景中,不需要这种基类,只需要直接具体类就足够了。
  • ConcretePrototype(具体原型类):用于实现或扩展 clone() 方法的类。clone() 方法必须要实现,因为它返回了类型的新实例。如果只在基类中实现了 clone() 方法,却没有在具体原型类中实现,那么当我们在具体原型类的对象上调用该方法时,会返回一个基类的抽象原型对象。

我们知道如果在接口中声明 clone() 方法,那么在编译阶段就会强制必须在类的实现里面实现 clone() 方法。如果是多继承结构中,父类实现了 clone() 方法,继承自它的子类将不会强制执行 clone() 方法。


2、浅拷贝与深拷贝

原型模式中既然提到了对象的拷贝,那么就得注意拷贝的深度。

2.1、概念

  • 浅拷贝:当拷贝的对象只包含简单数据类型(如:int 和 float)或不可变的对象(字符串)时,就直接将这些字段复制到新对象中。浅拷贝是一种仅将本对象作为拷贝内容的方法。
  • 深拷贝:当拷贝的对象包含对其他对象的引用时,如果引用对象不能共用时,就需要用到深拷贝,不仅要拷贝当前属性,还要拷贝当前属性的引用对象。 

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

如何实现?

 在 Java 中需要拷贝的原型类,需要实现 cloneable 接口,重写 clone() 方法,才可以实现类的拷贝。

2.2、Java 中的深浅拷贝

浅拷贝:

  1. 当类的成员变量是基本数据类型时,浅拷贝会将原对象的属性值赋值给新对象。
  2. 当类中成员变量时引用数据类型时,浅拷贝 会将 原对象的引用数据类型的地址 赋值给新对象的成员变量。也就是说 两个对象共享了同一个数据。当其中一个对象修改成员变量的值时,另外一个的值也会随之改变。

深拷贝:

  1. 无论是基本数据类型还是引用数据类型,都会去开辟额外的空间给新对象。不会出现浅拷贝中存在的问题。
  2. 当一个类中只有 基本数据类型时,浅拷贝与深拷贝是同样的。
  3. 当一个类中含有 引用数据类型是,浅拷贝只是拷贝一份引用,修改浅拷贝的值,原来的也会跟着变化。

实例

有一个叫张三的人,他想克隆自己,减轻自己的工作任务。

要实现克隆,需要实现Cloneable标志接口,并实现里面的clone方法。

浅拷贝

Person类
  • 类中为基本数据类型
  • 注意,String 不是基本数据类型,但是因为String 是final 修饰的。改变这个值 其实是new 一个新String对象,所以不会影响之前的原对象。
package com.zhaoyanfei.designpattern.prototypepattern;

public class Person implements Cloneable {

	private String name;
	
	private int age;

	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return super.toString()+">>{name="+this.name+",age="+this.age+"}";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {//重写clone方法
		Person person = null;
		person = (Person) super.clone();
		return person;
	}
}

Client类 

package com.zhaoyanfei.designpattern.prototypepattern;

public class Client {

	public static void main(String[] args) throws CloneNotSupportedException {
		Person person = new Person("飞哥",28);
		Person clonePerson = (Person) person.clone();
		System.out.println("源对象");
		System.out.println(person);
		System.out.println("浅拷贝对象");
		System.out.println(clonePerson);
		System.out.println("修改拷贝对象的值");
		clonePerson.setName("跟着飞哥学编程");
		clonePerson.setAge(18);
		System.out.println("修改拷贝对象后,源对象:"+person);
		System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);
	}
	
}

 从上面的示例代码可以看到修改拷贝对象的值,不影响源对象的值。

 当类中含有引用数据类型的对象(女朋友)时

GirlFriend类

package com.zhaoyanfei.designpattern.prototypepattern;

public class GirlFriend implements Cloneable {

	private String name;
	
	private float height;

	
	public GirlFriend(String name, float height) {
		this.name = name;
		this.height = height;
	}

	public String getName() {
		return name;
	}

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

	public float getHeight() {
		return height;
	}

	public void setHeight(float height) {
		this.height = height;
	}
	
	@Override
	public String toString() {
		return super.toString()+">>{name="+this.name+",height="+this.height+"}";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {//重写clone方法
		GirlFriend girlFriend = null;
		girlFriend = (GirlFriend) super.clone();
		return girlFriend;
	}
}

 修改 Person 类,增加 GirlFriend 引用对象,并修改 toString() 方法。

package com.zhaoyanfei.designpattern.prototypepattern;

public class Person implements Cloneable {

	private String name;
	
	private int age;
	
	private GirlFriend girlFriend;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public Person(String name, int age,GirlFriend girlFriend) {
		this.name = name;
		this.age = age;
		this.girlFriend = girlFriend;
	}

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public GirlFriend getGirlFriend() {
		return girlFriend;
	}

	public void setGirlFriend(GirlFriend girlFriend) {
		this.girlFriend = girlFriend;
	}

	@Override
	public String toString() {
		return super.toString()+">>{name="+this.name+",age="+this.age+",girlFriend={name="+girlFriend.getName()+",height="+girlFriend.getHeight()+"}}";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {//重写clone方法
		Person person = null;
		person = (Person) super.clone();
		return person;
	}
}

Client2 再次使用Clone方法。

package com.zhaoyanfei.designpattern.prototypepattern;

public class Client2 {

	public static void main(String[] args) throws CloneNotSupportedException {
		GirlFriend girlFriend = new GirlFriend("周冬雨",160.50f);
		Person person = new Person("飞哥",28,girlFriend);
		Person clonePerson = (Person) person.clone();
		System.out.println("源对象");
		System.out.println(person);
		System.out.println("浅拷贝对象");
		System.out.println(clonePerson);
		System.out.println("修改拷贝对象飞哥女朋友的值");
		clonePerson.getGirlFriend().setName("关晓彤");
		clonePerson.getGirlFriend().setHeight(174);
		System.out.println("修改拷贝对象后,源对象:"+person);
		System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);
	}
	
}

 查看这次代码运行结果,发现引用对象修改后,源对象的引用对象值也发生了变化。

GirlFriend 引用类型 被克隆时,只是将原有的地址 指向了被克隆的对象。

此时 GirlFriend 还只是一个对象。只是有两个引用指向同一份地址,一个地址中存放着一个值。

其中一个引用改变 了地址中的内容,另外一个也会跟着改变。

深拷贝的两种实现方式

方式一

使用 两层浅拷贝实现。

此时 我们使用深拷贝。 将 GirlFriend 对象也复制一份。

我们修改 Person 中的 clone() 方法

@Override
	protected Object clone() throws CloneNotSupportedException {//重写clone方法
		Person person = null;
		person = (Person) super.clone();
		person.girlFriend = (GirlFriend) person.getGirlFriend().clone();
		return person;
	}

 此时,再次运行 Client2 ,可以看到代码运行结果如下:

 发现修改拷贝对象的值,源对象的值还是原来的值,不受拷贝对象修改的影响。

此时,进行的复制 是将整个引用类型的对象,开辟了另外的空间。

复制的对象 与原有的对象 不再共享同一个 GirlFriend.

其实这种方式 也是属于浅层次的拷贝,因为 GirlFriend 类中数据类型是基本数据类型。

如果 GirlFriend 类中 还有 一层引用数据类型的话,那还需要在 clone() 方法中再添加一层,才可以实现真正的深拷贝。

所以不太推荐使用这种方式。

方式二

使用 序列化对象 实现 深拷贝

Person,GirlFriend 需要实现 Serializable 接口

具体代码如下:

Person 类改造如下:

package com.zhaoyanfei.designpattern.prototypepattern;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Cloneable ,Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 8606208479723984421L;

	private String name;
	
	private int age;
	
	private GirlFriend girlFriend;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public Person(String name, int age,GirlFriend girlFriend) {
		this.name = name;
		this.age = age;
		this.girlFriend = girlFriend;
	}

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public GirlFriend getGirlFriend() {
		return girlFriend;
	}

	public void setGirlFriend(GirlFriend girlFriend) {
		this.girlFriend = girlFriend;
	}

	@Override
	public String toString() {
		return super.toString()+">>{name="+this.name+",age="+this.age+",girlFriend={name="+girlFriend.getName()+",height="+girlFriend.getHeight()+"}}";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {//重写clone方法
		Person person = null;
		person = (Person) super.clone();
//		person.girlFriend = (GirlFriend) person.getGirlFriend().clone();
		return person;
	}
	//序列化实现深拷贝
	public Person deepClone(){
        //声明流对象
        ByteArrayOutputStream bos = null;
        ByteArrayInputStream bis = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            //创建序列化流
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //将当前对象以对象流的方式输出
            oos.writeObject(this);
            //创建反序化流
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            //将流对象反序列化,实现类的深拷贝。
            return (Person) ois.readObject();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                //关闭资源
                bos.close();
                bis.close();
                oos.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

GirlFriend 类只需要再实现序列化 Serializable 即可

 测试类 Client3 代码如下:

package com.zhaoyanfei.designpattern.prototypepattern;

public class Client3 {

	public static void main(String[] args) throws CloneNotSupportedException {
		GirlFriend girlFriend = new GirlFriend("周冬雨",160.50f);
		Person person = new Person("飞哥",28,girlFriend);
		Person clonePerson = (Person) person.deepClone();
		System.out.println("源对象");
		System.out.println(person);
		System.out.println("深拷贝对象");
		System.out.println(clonePerson);
		System.out.println("修改拷贝对象飞哥女朋友的值");
		clonePerson.getGirlFriend().setName("关晓彤");
		clonePerson.getGirlFriend().setHeight(174);
		System.out.println("修改拷贝对象后,源对象:"+person);
		System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);
	}
	
}

 运行测试结果如下:

 这种方式同样也实现了深拷贝。

注:推荐使用第二种方式实现深拷贝。

在实践中,我们应根据具体情况来决定使用深拷贝、浅拷贝或混合拷贝。通常,浅拷贝对应于聚合关系,而深拷贝对应于组合关系。

3、原型模式的优缺点

优点:

  1. 性能提高。
  2. 逃避构造函数的约束。

缺点:

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

4、 结语

下节分享创建型模式中的对象池模式,想要系统学习设计模式的小伙伴可以给我点个免费的关注,小编会持续完善设计模式的讲解。