写在前面
- 记录学习设计模式的笔记
- 提高对设计模式的灵活运用
学习地址
https://www.bilibili.com/video/BV1G4411c7N4
https://www.bilibili.com/video/BV1Np4y1z7BU
参考文章
http://c.biancheng.net/view/1317.html
项目源码
https://gitee.com/zhuang-kang/DesignPattern
6,原型模式
6.1原形模式的定义和特点
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。
原型模式的优点:
- Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
6.2 原型模式的结构与实现
6.2.1 原形模式的结构
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
6.2.2 代码实现
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
6.2.2.1 浅拷贝
IdCard
package com.zhuang.prototype.shallowclone;
/**
* @Classname IdCard
* @Description 浅拷贝的示例
* @Date 2021/3/19 12:16
* @Created by dell
*/
public class IdCard {
private String id;
public IdCard(String id) {
this.id = id;
}
@Override
public String toString() {
return "IdCard{" +
"id=" + id +
'}';
}
}
Person
package com.zhuang.prototype.shallowclone;
/**
* @Classname Person
* @Description 浅拷贝的示例
* @Date 2021/3/19 12:17
* @Created by dell
*/
public class Person implements Cloneable {
private String name;
private int age;
private IdCard idCard;
public Person() {
}
public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
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 IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", idCard=" + idCard + ", idCard.hashCode=" + idCard.hashCode() +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
PersonTest
package com.zhuang.prototype.shallowclone;
/**
* @Classname PersonTest
* @Description 浅拷贝测试类
* @Date 2021/3/19 12:17
* @Created by dell
*/
public class PersonTest {
public static void main(String[] args) throws Exception {
Person person = new Person("张三", 20, new IdCard("10086"));
Person person1 = (Person) person.clone();
Person person2 = (Person) person.clone();
System.out.println(person);
System.out.println(person1);
System.out.println(person2);
}
}
我们发现可以通过实现implements Cloneable
来完成浅拷贝,基本变量是值传递克隆,而引用对象IdCard
则是引用传递,这不符合我们面向对象思想,每一个Person
应该都有一个独立的IdCard
,而不是共用一个,而要解决这种问题,我们需要使用深克隆
6.2.2.2 深拷贝(第一种)
IdCard
package com.zhuang.prototype.deepclone.one;
/**
* @Classname IdCard
* @Description 深克隆的示例
* @Date 2021/3/19 12:33
* @Created by dell
*/
//实现Cloneable接口
public class IdCard implements Cloneable {
private String id;
public IdCard(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "IdCard{" +
"id='" + id + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person
package com.zhuang.prototype.deepclone.one;
/**
* @Classname Person
* @Description 深克隆的示例
* @Date 2021/3/19 12:33
* @Created by dell
*/
public class Person implements Cloneable {
private String name;
private int age;
private IdCard idCard;
public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Person{" +
"personHashCode=" + this.hashCode() +
", name='" + name + '\'' +
", age=" + age +
", idCard=" + idCard +
", idCardHashCode=" + idCard.hashCode() +
'}';
}
//深克隆需要自己手动实现,在对象引用中也要实现clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
//完成基本数据类型的拷贝
//通过new关键字创建的对象是引用类型
Object person = super.clone();
//对引用类型单独处理
Person p = (Person) person;
IdCard idCard = (IdCard) p.getIdCard().clone(); //实现自己的克隆
p.setIdCard(idCard);
return p;
}
}
PersonTest
package com.zhuang.prototype.deepclone.one;
import java.io.Serializable;
/**
* @Classname PersonTest
* @Description 深克隆测试类
* @Date 2021/3/19 12:33
* @Created by dell
*/
public class PersonTest implements Serializable {
public static void main(String[] args) throws Exception {
Person person = new Person("张三", 20, new IdCard("10086"));
Person person1 = (Person) person.clone();
Person person2 = (Person) person.clone();
System.out.println(person);
System.out.println(person1);
System.out.println(person2);
}
}
使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了
6.2.2.2 深拷贝(第二种)
IdCard
package com.zhuang.prototype.deepclone.two;
/**
* @Classname IdCard
* @Description 深克隆的示例2
* @Date 2021/3/19 12:33
* @Created by dell
*/
//实现Serializable接口
public class IdCard implements Serializable {
private String id;
public IdCard(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "IdCard{" +
"id='" + id + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person
package com.zhuang.prototype.deepclone.two;
import java.io.*;
/**
* @Classname Person
* @Description 深克隆的示例2
* @Date 2021/3/19 12:33
* @Created by dell
*/
public class Person implements Serializable {
private String name;
private int age;
private IdCard idCard;
public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Person{" +
"personHashCode=" + this.hashCode() +
", name='" + name + '\'' +
", age=" + age +
", idCard=" + idCard +
", idCardHashCode=" + idCard.hashCode() +
'}';
}
//序列化的方式
public Person deelClone() {
//创建流对象
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);
return (Person) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
ois.close();
bis.close();
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
PersonTest
package com.zhuang.prototype.deepclone.two;
/**
* @Classname PersonTest
* @Description 深克隆测试类
* @Date 2021/3/19 12:33
* @Created by dell
*/
public class PersonTest {
public static void main(String[] args) throws Exception {
//创建一个对象
Person person = new Person("张三", 20, new IdCard("10086"));
//克隆两个对象
Person person1 = (Person) person.deelClone();
Person person2 = (Person) person.deelClone();
System.out.println("深拷贝(第二种 实现序列化接口)");
//打印三人信息
System.out.println(person);
System.out.println(person1);
System.out.println(person2);
}
}
这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高
6.3 原型模式的应用场景
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
- 在 Spring中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的具体应用。
6.4 原型模式的注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率
- 不用重新初始化对象,动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或减少属性),其他克隆对象也会发生相应的变化,无需修改代码
写在最后
- 如果我的文章对你有用,请给我点个,感谢你!
- 有问题,欢迎在评论区指出!