Java设计模式之创建型模式(三):原型模式

导读:本篇文章讲解 Java设计模式之创建型模式(三):原型模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

原型模式介绍

原型模式是一种创建型的设计模式,原型模式是指复制现有的实例来创建新的实例,不需要知道实例的创建细节。(在Java中意味使用clone方法或者序列化和反序列化)。

当创建给定类的实例的过程很昂贵或很复杂时,就需要使用原型模式。

原型模式的原理UML类图,如下:
在这里插入图片描述
原理结构图说明:

  • ProtoType:原型类,实现Cloneable接口,声明克隆自己的
    抽象方法。
  • ConcreteProtoType:具体的原型类,实现克隆自己的方法。
  • Client:其中使用ProtoType,让一个原型对象克隆自己,从而创建一个新的对象。

clone()方法说明:

Object中clone()方法是protected的,是浅拷贝,要使用clone()方法,要重写它,只有实现了Cloneable接口才可以调用该方法,否则会抛出CloneNotSupportedException异常。

clone()方法在实现上是在内存上直接复制二进制流,然后重新分配内存空间,比new出来的效率要高,clone的过程不会调用构造函数,也就是说通过这一方法创造出来的对象不会由构造函数的处理。

clone()方法创建并返回此对象的一个副本。对于任何对象x,满足以下表达式:

  1. x.clone() != x为true
  2. x.clone().getClass() == x.getClass()为true
  3. x.clone().equals(x)一般情况下为true,但这并不是必须要满足的要求

浅拷贝介绍:

  1. 对于数据类型为基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。无论是浅拷贝还是深拷贝,拷贝出的新的对象指向新的地址空间。
  2. 对于数据类型为引用数据类型的成员变量,如数组,对象。那么浅拷贝将进行引用传递,也就是只是将成员变量的引用值(内存地址)复制一份给新的对象。在这种情况下,若成员变量是数组或对象,因为两个对象的成员变量指向同一实例,在一个对象中修改该成员变量会影响到另一个对象的成员变量。

实现原型模式示例

场景:现在有一只羊名为li,大小为45斤,颜色为白色。朋友为一个叫kai的羊,编程实现复制十只一模一样的羊。

下面创建一个羊的实体类,实现cloneable接口并重写clone()方法:

public class Sheep implements Cloneable {
	private String name;
	private String color;
	private int size;
	public Sheep friend;

	public Sheep(String name, String color, int size) {
		this.name = name;
		this.color = color;
		this.size = size;
	}

	@Override
	public String toString() {
		return "Sheep [name=" + name + ", color=" + color + ", size=" + size + "]";
	}

	@Override
	protected Sheep clone() {
		Sheep sheep = null;
		try {
			sheep = (Sheep) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sheep;
	}
}

在Client类中完成复制Sheep对象
这里很容易想到使用原型对象的属性来new一个对象,但是这种方法总是需要重新获取原始对象的属性,如果创建的对象复杂就会效率很低,另外如果原型对象有修改,可能会需要修改代码。

public class Client {
	public static void main(String[] args) {
		Sheep sheep = new Sheep("li", "white", 40);
		sheep.friend = new Sheep("kai", "black", 45);
		System.out.println(sheep.toString() + sheep.friend.hashCode());
		Sheep copySheep = (Sheep) sheep.clone();
		System.out.println("复制的羊:" + copySheep.toString() + copySheep.friend.hashCode());
	}
}

运行程序查看结果:
在这里插入图片描述
可以看到两个对象属性friend的hashcode的值一致,即引用的对象是同一个,这说明上面使用的拷贝方式是浅拷贝。

深拷贝

1.基本介绍

深拷贝也就是复制对象的所有基本数据类型的成员变量,对于所有的引用类型的成员变量,深拷贝会为其申请存储空间,并会复制每个引用类型的成员变量所引用的对象。

也就是说对象进行深拷贝是要对整个对象进行拷贝

深拷贝的两种实现方式:重写clone()方法和对象序列化。

2.深拷贝应用

1.方式1:重写clone()方法。
新建一个类DeepCloneableTarget 作为成员变量,该类的属性只有String类型,虽然String类型也属于引用类型,但因为String的不可变性(可以使用反射来修改String的值),这里使用Object类的clone即可。

public class DeepCloneableTarget implements Cloneable {
	private String cloneName;

	public DeepCloneableTarget(String cloneName) {
		super();
		this.cloneName = cloneName;
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
}

类 DeepProtoType 将 DeepCloneableTarget类对象作为成员变量。

public class DeepProtoType implements Cloneable {
	private String name;
	public DeepCloneableTarget deepCloneableTarget;

	public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
		super();
		this.name = name;
		this.deepCloneableTarget = deepCloneableTarget;
	}
	@Override
	protected DeepProtoType clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		DeepProtoType deep = (DeepProtoType) super.clone();
		// 对引用类型做单独处理,单独进行克隆
		deep.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
		return deep;
	}
}

Client类测试是否完成成员变量DeepCloneableTarget类对象的拷贝。

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {
		DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
		DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
		DeepProtoType clone = deepProtoType.clone();
		System.out.println(deepProtoType.deepCloneableTarget.hashCode());
		System.out.println(clone.deepCloneableTarget.hashCode());
	}
}

通过运行结果中的hashcode的值的不同判断两个作为成员变量的对象是不同的即完成了深拷贝。

2.方式2:通过对象的序列化实现

类DeepCloneableTarget改为实现Serializable接口

public class DeepCloneableTarget implements Serializable {
	private static final long serialVersionUID = 1L;
	private String cloneName;

	public DeepCloneableTarget(String cloneName) {
		super();
		this.cloneName = cloneName;
	}
}

类DeepProtoType也实现Serializable 接口,定义deepClone()方法使用序列化和反序列化实现深拷贝。

public class DeepProtoType implements Serializable {
	private static final long serialVersionUID = 1L;
	public String name;
	public DeepCloneableTarget deepCloneableTarget;

	public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
		super();
		this.name = name;
		this.deepCloneableTarget = deepCloneableTarget;
	}
	public DeepProtoType deepClone() {
		// 创建流对象
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream ois = null;

		try {
			// 序列化
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(this);
			// 反序列化
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			DeepProtoType deepProtoType = (DeepProtoType) ois.readObject();
			return deepProtoType;

		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			return null;
		} finally {
			// 关闭流
			try {
				bos.close();
				oos.close();
				bis.close();
				ois.close();
			} catch (Exception e2) {
				// TODO: handle exception
				e2.printStackTrace();

			}
		}

	}
}

测试是否能完成深拷贝。

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {
		DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
		DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
		DeepProtoType clone = deepProtoType.deepClone();
		System.out.println(deepProtoType.name + " " + deepProtoType.deepCloneableTarget.hashCode());
		System.out.println(deepProtoType.name + " " + clone.deepCloneableTarget.hashCode());
	}
}

运行结果如下:
在这里插入图片描述
两个对象的成员变量中的DeepCloneableTarget 对象的hashcode不同,所以两个变量是不同的两个对象,这同样实现了深拷贝。

原型模式在Spring中的应用

在Spring 中我們可以定義Bean的作用域为prototype,下面使用Java配置类的方式来注册一个Bean。

@Configuration
public class SpringConfig {

	@Bean(value = "person")
	@Scope(value = "prototype")
	// 或@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
	public Person getPerson() {
		return new Person();
	}
}

当使用getBean()方法获取容器中的这个Bean时,下面通过Debug来走一遍该Bean的实例化过程:
首先调用AbstractApplicationContext的getBean()方法
在这里插入图片描述
这里的getBeanFactory()是通过子类GenericApplicationContext 中的getBeanFactory()获取DefaultListableBeanFactory。
在这里插入图片描述
然后getBean()方法调用AbstractBeanFactory的getBean()
在这里插入图片描述
接着调用本类中的doGetBean()方法,最终在这里判断如果是prototype,就调用这个类的createBean()方法。
在这里插入图片描述

总结

下面总结一下原型模式:

  • 适用场景:
  1. 当创建新的对象复杂或需要一个类的许多对象时,就可以利用原型模式简化对象的创建过程来提高效率。
  2. 类初始化需要消耗资源较多,就可以使用原型模式。
  • 优点:
  1. 向客户隐藏创建新实例的复杂性
  2. 提供让客户能够产生未知类型对象的选项。即客户在不知道要实例化何种特定类的情况下,可以制造出新的实例。
  3. 不用重新初始化对象,而是动态地获得对象运行时的状态。
  • 缺点:实现深克隆时可能需要复杂的代码。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/44326.html

(0)
小半的头像小半

相关推荐

半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!