写点什么

PrototypePattern- 原型模式

作者:梁歪歪 ♚
  • 2022 年 5 月 28 日
  • 本文字数:4562 字

    阅读完需:约 15 分钟

PrototypePattern-原型模式

原型模式

原型模式(Prototype Pattern):一般指的是我们通过一个原型实例,然后创建出和原型实例一样的重复对象,主要就是用来实现对象的克隆。


原型模式的实质就是克隆对象,那么克隆又可以分为浅克隆和深克隆。


浅克隆:指拷贝对象时仅仅拷贝对象本身和对象中的基本变量,但是不拷贝对象包含的引用类型。假如对象中含有一个引用属性,那么拷贝的时候只会把引用属性的地址拷贝过来,这样的缺点就是一旦原型实例对象的引用属性发生了修改,那么克隆过来的对象也会一起变动。


深克隆:不仅拷贝对象本身,而且会将引用对象也一起实现拷贝,这样一旦原型实例中的引用属性发生变化,不会影响到克隆后的对象。


想必大家看概念会很模糊,下边通过代码来实现两种克隆方式,进行比对学习。


示例:


  • 新建一个原型接口IPrototype.java


package cn.liangyy.prototype;
/** * 原型接口 */public interface IPrototype { IPrototype clone(); //克隆方法}
复制代码


  • 定义一个原型实例类ShallowPrototype.java来实现 IPrototype 接口,该类中定义了三个属性,其中一个为引用类型


package cn.liangyy.prototype;
import java.util.List;
/** * 原型实例类 */public class ShallowPrototype implements IPrototype { private String name; private int age;
private List<String> phoneList;
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> getPhoneList() { return phoneList; }
public void setPhoneList(List<String> phoneList) { this.phoneList = phoneList; }
@Override public IPrototype clone() { ShallowPrototype shallowPrototype = new ShallowPrototype(); shallowPrototype.setAge(this.age); shallowPrototype.setName(this.name); shallowPrototype.setPhoneList(this.phoneList); return shallowPrototype; }}
复制代码


  • 测试TestShallowPrototype.java


package cn.liangyy.prototype;
import java.util.ArrayList;import java.util.List;
public class TestShallowPrototype { public static void main(String[] args) { //初始化一个原型实例对象ShallowPrototype ShallowPrototype shallowPrototype = new ShallowPrototype(); shallowPrototype.setAge(20); shallowPrototype.setName("亚索");
List<String> phoneList = new ArrayList<>(); phoneList.add("133xxxx452"); shallowPrototype.setPhoneList(phoneList);
//克隆原型对象 ShallowPrototype cloneShallowPrototype = (ShallowPrototype) shallowPrototype.clone(); //打印结果 System.out.println("克隆前:"+shallowPrototype.getPhoneList()); System.out.println("克隆后:"+cloneShallowPrototype.getPhoneList());
//比较两个对象是否指向同一内存地址 System.out.println(shallowPrototype.getPhoneList() == cloneShallowPrototype.getPhoneList()); }}
复制代码


由以上测试类可以看出,克隆前后两个对象的phoneList属性是相等的,说明这两个属性指向的是同一个内存地址,所以其中的一个对象修改了这个属性,另一个也就会随之改变。


  • 再新建一个测试TestShallowPrototype2.java,再这个基础上验证上面这个说法


package cn.liangyy.prototype;
import java.util.ArrayList;import java.util.List;
public class TestShallowPrototype2 { public static void main(String[] args) { //初始化一个原型实例对象ShallowPrototype ShallowPrototype shallowPrototype = new ShallowPrototype(); shallowPrototype.setAge(20); shallowPrototype.setName("亚索");
List<String> phoneList = new ArrayList<>(); phoneList.add("133xxxx452"); shallowPrototype.setPhoneList(phoneList);
//克隆原型对象 ShallowPrototype cloneShallowPrototype = (ShallowPrototype) shallowPrototype.clone(); //打印结果 System.out.println("克隆前:"+shallowPrototype.getPhoneList()); System.out.println("克隆后:"+cloneShallowPrototype.getPhoneList());
//比较两个对象是否指向同一内存地址 System.out.println(shallowPrototype.getPhoneList() == cloneShallowPrototype.getPhoneList());
//修改原对象中的属性phoneList List<String> list = shallowPrototype.getPhoneList(); list.add("155xxxx1564"); //再次打印结果 System.out.println("克隆前:(修改后)"+shallowPrototype.getPhoneList()); System.out.println("克隆后:(修改后)"+cloneShallowPrototype.getPhoneList()); }}
复制代码


通过这个测试可以看到,当我们对phoneList进行了一个添加操作之后,两个对象都一同被修改了。


上面就是一个浅克隆的示例,浅克隆存在的问题使它有些时候并不适合我们的业务需求,下面我们看一下深克隆的示例。


  • 新建一个深克隆原型实例对象DeepPrototype.java,实现 Cloneable 和 Serializable 两个接口


package cn.liangyy.prototype.deep;
import java.io.*;import java.util.List;
public class DeepPrototype implements Cloneable, Serializable { private String name; private int age;
private List<String> phoneList;
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> getPhoneList() { return phoneList; }
public void setPhoneList(List<String> phoneList) { this.phoneList = phoneList; }
public DeepPrototype deepClone(){ try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis);
DeepPrototype clone = (DeepPrototype) ois.readObject(); return clone; } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
public Object clone(){ return this.deepClone(); }}
复制代码


  • 测试TestDeepPrototype.java


package cn.liangyy.prototype.deep;
import java.util.ArrayList;import java.util.List;
public class TestDeepPrototype { public static void main(String[] args) { DeepPrototype deepPrototype = new DeepPrototype(); deepPrototype.setAge(20); deepPrototype.setName("亚索"); List<String> phoneList = new ArrayList<>(); phoneList.add("132xxxx4562"); deepPrototype.setPhoneList(phoneList);
DeepPrototype cloneDeepPrototype = (DeepPrototype) deepPrototype.clone(); System.out.println("克隆前:"+deepPrototype.getPhoneList()); System.out.println("克隆后:"+cloneDeepPrototype.getPhoneList()); System.out.println(deepPrototype.getPhoneList() == cloneDeepPrototype.getPhoneList()); }}
复制代码


由上面的测试,可以看到,虽然输出的值是相同的,但是最后的结果却是false,这就说明原对象和克隆对象之间是完全独立,phoneList属性没有共用同一个内存地址。


  • 一样的修改原对象的属性,再创建一个测试TestDeepPrototype2.java


package cn.liangyy.prototype.deep;
import java.util.ArrayList;import java.util.List;
public class TestDeepPrototype2 { public static void main(String[] args) { DeepPrototype deepPrototype = new DeepPrototype(); deepPrototype.setAge(20); deepPrototype.setName("亚索"); List<String> phoneList = new ArrayList<>(); phoneList.add("132xxxx4562"); deepPrototype.setPhoneList(phoneList);
DeepPrototype cloneDeepPrototype = (DeepPrototype) deepPrototype.clone(); System.out.println("克隆前:"+deepPrototype.getPhoneList()); System.out.println("克隆后:"+cloneDeepPrototype.getPhoneList()); System.out.println(deepPrototype.getPhoneList() == cloneDeepPrototype.getPhoneList());
//修改原对象中的属性phoneList List<String> list = deepPrototype.getPhoneList(); list.add("188xxxx7896"); System.out.println("克隆前:"+deepPrototype.getPhoneList()); System.out.println("克隆后:"+cloneDeepPrototype.getPhoneList()); }}
复制代码


由该测试类,我们可以清楚的知道,修改了原对象的引用属性之后,并没有影响到克隆对象。


关于深克隆上面的示例,深克隆实现了两个接口:CloneableSerializable。为什么要实现这两个接口呢?


Serializable接口大家应该比较好理解,因为我们的deepClone方法是通过序列化来实现对象克隆的,所以必须要实现序列化接口,否则会抛出异常NotSerializableException


对于Cloneable接口,因为clone方法是Object对象的,而 Java 中所有的对象默认都是Object对象的子类,所以我们的类中也同样的具有clone方法,但是默认的clone方法实现的是浅克隆,为了使得我们的深克隆对象中不会同时具备深克隆和浅克隆两个功能,我这里选择了重写clone方法,而 Java 中的规范约定,如果我们需要实现clone对象,那么必须要实现Cloneable接口,否则就会抛出异常CloneNotSupportedException


原型模式使用场景原型模式的作用就是克隆对象,而如果一个对象本身的创建就非常简单,那么没必要使用克隆模式,所以原型模式一般适用于类初始化消耗资源较多时或者就是创建一个对象非常复杂的场景。


原型模式的优点


  • 当我们创建一个对象比较复杂时,使用原型对象通常效率会更高也更方便快捷(因为原型模式是在内存中通过二进制流的方式拷贝的,在产生大量对象时,相对 new 一个对象的性能更好一些)。


原型模式的缺点


  • 每个对象都需要单独实现克隆的方法

  • 深克隆和浅克隆需要灵活应用,否则可能会导致业务出错

发布于: 刚刚阅读数: 4
用户头像

梁歪歪 ♚

关注

还未添加个人签名 2021.07.22 加入

还未添加个人简介

评论

发布
暂无评论
PrototypePattern-原型模式_设计模式_梁歪歪 ♚_InfoQ写作社区