知识点六: 原型模式
一、概述
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它属于23种GOF设计模式的创建型设计模式 ,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。它有如下特点:
- 由原型对象自身创建目标对象。也就是说,对象创建这一动作发自原型对象本身。
- 目标对象是原型对象的一个克隆。也就是说,通过Prototype模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值
- 根据对象克隆的深度层次不同,可以分为浅度克隆(浅拷贝)和深度克隆(深拷贝)。
简单的说就是:
采用复制原型对象的方法来创建对象实例,创建出来的实例具有与原型一样的数据
二、深拷贝和浅拷贝
浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。
三、优缺点和使用场景
1、优点
- 性能提高。
- 逃避构造函数的约束。
2、缺点
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
3、使用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
四、模式中包含的角色和其职责以及模式实现方式
1、角色
抽象原型(Prototype)角色:声明克隆自身的接口,Cloneable接口。
具体原型(ConcretePrototype)角色:实现克隆的具体操作。
2、实现方式
了解Java的同学应该都知道,所有的Java类都继承至Object,而Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable。该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。
一般而言,clone()方法满足:
- 对任何的对象obj,都有obj.clone() !=obj,即克隆对象与原对象不是同一个对象。
- 对任何的对象obj,都有obj.clone().getClass()==obj.getClass(),即克隆对象与原对象的类型一样。
- 如果对象obj的equals()方法定义恰当,那么obj.clone().equals(obj)应该成立。
五、在Java中的实现
1.浅度克隆(浅拷贝)
-
原型类Person.java
/** * @Description: 原型类 * @Author: Ling.D.S * @Date: Created in 2018/11/8 20:25 */ public class Person implements Cloneable{ private String name; private int age; //朋友(复杂类型) private List<String> friends; 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 List<String> getFriends() { return friends; } public void setFriends(List<String> friends) { this.friends = friends; } public Person clone(){ try { return (Person) super.clone(); } catch (CloneNotSupportedException e) { //抛出一个不支持克隆异常 e.printStackTrace(); return null; } } }
-
测试类MainClass.java
/** * @Description: * @Author: Ling.D.S * @Date: Created in 2018/11/8 20:25 */ public class MainClass { public static void main(String[] args) { //先创建一个人,他有一些朋友 Person person = new Person(); person.setName("宋德凌"); person.setAge(18); ArrayList<String> friends = new ArrayList<String>(); friends.add("彭于晏"); friends.add("吴彦祖"); person.setFriends(friends); //再克隆这个人 Person person1 = person.clone(); //比较克隆体和原型的属性 System.out.println("=======第一次比较======="); System.out.println(person.getFriends().toString()); System.out.println(person1.getFriends().toString()); //这时候原型又交了一个朋友 friends.add("胡歌"); person.setFriends(friends); //比较克隆体和原型的属性 System.out.println("=======第二次比较======="); System.out.println(person.getFriends().toString()); System.out.println(person1.getFriends().toString()); } }
-
结果
导致这样的结果的原因在于,浅拷贝只是传递引用,不能复制实例
2.深度克隆
-
原型类
/** * @Description: 原型类 * @Author: Ling.D.S * @Date: Created in 2018/11/8 20:25 */ public class Person implements Cloneable{ private String name; private int age; //朋友(复杂类型) private List<String> friends; 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 List<String> getFriends() { return friends; } public void setFriends(List<String> friends) { this.friends = friends; } public Person clone(){ try { //这个地方还是传递引用,克隆数值型的属性 Person person = (Person) super.clone(); //核心:为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。 ArrayList<String> newFriends = new ArrayList<String>(); for (String friend : this.getFriends()) { newFriends.add(friend); } person.setFriends(newFriends); return person; } catch (CloneNotSupportedException e) { //抛出一个不支持克隆异常 e.printStackTrace(); return null; } } }
-
测试类
同上
-
结果
转载请注明原地址,宋德凌的博客:http://CoderOfSong.github.io 谢谢!