本文共 6011 字,大约阅读时间需要 20 分钟。
声明:转载请附上原文链接
提示:标题序号从3开始,是照应不同设计模式笔记发布的顺序而定的,比如,第上一篇文章 序号从2开始。
标题后面之所以加上了解,是因为相对于单例模式,工厂模式来说原型模式用的比较少,但原型模式的深拷贝和浅拷贝是需要了解一下的!
3. 原型模式(了解)
3.1 原型模式介绍
- 原型设计模式Prototype
- 是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能
- 工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程
- 应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式
- 核心组成
- Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
- ConcretePrototype : 具体原型类
- Client: 让一个原型对象克隆自身从而创建一个新的对象
- 应用场景
- 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
- 如果系统要保存对象的状态,做备份使用
3.2 原型模式案例
首先我们来创建一个具体原型类 Person.java 并让其实现 Cloneable 接口,重写clone() 方法:
/** * @Auther: csp1999 * @Date: 2020/11/08/7:31 * @Description: Person 具体原型类实现 Cloneable 接口,能被克隆 */public class Person implements Cloneable{ private String name; private int age; public Person(){ System.out.println("空参构造函数调用..."); } 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 "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } /** * 重写克隆方法,返回Person对象类型 * * 注意:权限改成public,方便调用 * @return * @throws CloneNotSupportedException */ @Override public Person clone() throws CloneNotSupportedException { return (Person) super.clone(); }}
在测试类中调用并打印结果:
@Testpublic void testPropotype() throws CloneNotSupportedException { Person person1 = new Person(); person1.setAge(22); person1.setName("csp"); System.out.println(person1); Person person2 = person1.clone(); person2.setName("hzw"); System.out.println(person2);}
结果如下:
空参构造函数调用...Person{ name='csp', age=22}Person{ name='hzw', age=22}
从结果可以看出:person2 是 person1通过复制后得来的,二者数据内容相同。但需要注意的是,person1调用clone();
方法得到person2,并没有经过Person 类中的空参构造函数,因此打印结果只输出一次空参构造函数调用...
。
接下来,我们在Person 在加上新的复杂数据类型的成员变量:List
private Listlist;
再来测试:
@Testpublic void testPropotype() throws CloneNotSupportedException { Person person1 = new Person(); person1.setAge(22); person1.setName("csp"); // 初始化list 并为其加入数据 person1.setList(new ArrayList<>()); person1.getList().add("aaa"); person1.getList().add("bbb"); System.out.println("person1:"+person1); Person person2 = person1.clone(); person2.setName("hzw"); // 给peron2 中的list添加一条数据 person2.getList().add("ccc"); System.out.println("person2"+person2); System.out.println("person1:"+person1); boolean flag1 = person1 == person2; System.out.println("person1 和 person2 的 引用地址是否相同: " + flag1); boolean flag2 = person1.getList() == person2.getList(); System.out.println("person1 和 person2 的 list 引用地址是否相同: " + flag2);}
输出结果:
空参构造函数调用...person1:Person{ name='csp', age=22, list=[aaa, bbb]}person2Person{ name='hzw', age=22, list=[aaa, bbb, ccc]}person1:Person{ name='csp', age=22, list=[aaa, bbb, ccc]}person1 和 person2 的 引用地址是否相同: falseperson1 和 person2 的 list 引用地址是否相同: true
由结果可以看出:
- 当克隆执行完成后,实际上相当于新 new 一个Person 对象并为其分配了新的存储地址及引用,因此person1 和 person2 的地址引用不同;
- 而对于Person 对象的复杂类型成员变量 list,当执行克隆的时候,实际上是从被拷贝对象
person1
中 拷贝了list 的引用地址给person2
中的 list,而并非新new(创建)一个 List 出来; - 因此二者其实是共享一个相同地址引用的list,所以
person1.getList() == person2.getList();
为true,这也就说明了,为什么在person2
的list 中添加数据ccc
时,person1
中的list也添加了ccc
,而这种情况就被称为 浅拷贝;
那么如何解决浅拷贝的问题呢?请接着往下阅读!
3.1 原型模式深拷贝/浅拷贝
-
遗留问题:
-
通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的
-
浅拷贝实现 Cloneable,深拷贝是通过实现 Serializable 读取二进制流
-
拓展
-
浅拷贝:
如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址通过覆盖Object类的clone()方法可以实现浅克隆
-
深拷贝:
无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等方式来实现
-
-
-
优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率
- 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态
-
缺点
- 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
- 在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引用时,需要对每一层对象对应的类都必须支持深克隆
深拷贝实现:
首先Person 对象实现 Serializable 接口,然后自定义深拷贝方法 deepClone():
/** * 深拷贝 * * 注意:要实现序列化接口 * @return */public Person deepClone() { try { // 输出 (序列化) ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 输入 (反序列化) ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Person person = (Person) ois.readObject(); return person; } catch (Exception e) { e.printStackTrace(); return null; }}
接下来验证一下深拷贝是否成功:
@Testpublic void testPropotype() throws CloneNotSupportedException { Person person1 = new Person(); person1.setAge(22); person1.setName("csp"); // 初始化list 并为其加入数据 person1.setList(new ArrayList<>()); person1.getList().add("aaa"); person1.getList().add("bbb"); System.out.println("person1:"+person1); //-----------------------------浅拷贝------------------------------- //Person person2 = person1.clone(); //-----------------------------深拷贝------------------------------- Person person2 = person1.deepClone(); person2.setName("hzw"); // 给peron2 中的list添加一条数据 person2.getList().add("ccc"); System.out.println("person2"+person2); System.out.println("person1:"+person1); boolean flag1 = person1 == person2; System.out.println("person1 和 person2 的 引用地址是否相同: " + flag1); boolean flag2 = person1.getList() == person2.getList(); System.out.println("person1 和 person2 的 list 引用地址是否相同: " + flag2);}
输出结果:
空参构造函数调用...person1:Person{ name='csp', age=22, list=[aaa, bbb]}person2Person{ name='hzw', age=22, list=[aaa, bbb, ccc]}person1:Person{ name='csp', age=22, list=[aaa, bbb]}person1 和 person2 的 引用地址是否相同: falseperson1 和 person2 的 list 引用地址是否相同: false
由结果可得出:深拷贝 person2
所得到的 list 内存地址和原来person1
中的内存地址是不同的,深拷贝成功!
之后我会陆续更新其他设计模式博文,如果文章对您有帮助,希望点个赞/收藏/关注! O(∩_∩)O~
转载地址:https://csp1999.blog.csdn.net/article/details/109555549 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!