一、简介
原型模式(Prototype Pattern)是一种创建型设计模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
二、UML 类图
1、客户(Client)角色:客户类提出创建对象的请求;也就是我们用户使用克隆复制的功能。
2、抽象原型(Prototype)角色:此角色定义了的具体原型类所需的实现的方法。也就是定义一个文件,说明一下它有被克隆复制的功能。
3、具体原型(Concrete Prototype)角色:实现抽象原型角色的克隆接口。就是我们的文件实现了可以被复制的功能。我们会发现其实原型模式的核心就是Prototype(抽象原型),他需要继承Cloneable接口,并且重写Object类中的clone方法才能有克隆复制的功能。
三、浅拷贝
简介
通过 java.lang.Object的clone() 方法实现浅拷贝对象。每次只复制一个对象,对象内部存在的指向其他对象数组或者引用则不复制。
案例
1、创建两个对象,并分别实现Cloneable 接口
public class Pwd implements Serializable, Cloneable{ private String pwdType; private String pwd; public String getPwdType() { return pwdType; } public void setPwdType(String pwdType) { this.pwdType = pwdType; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Pwd{" + "pwdType='" + pwdType + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
public class User implements Serializable, Cloneable { private int id; private String name; private Pwd pwd; private List<String> girls; public User(int id, String name, Pwd pwd, List<String> girls){ this.id = id; this.name =name; this.pwd =pwd; this.girls = girls; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Pwd getPwd() { return pwd; } public void setPwd(Pwd pwd) { this.pwd = pwd; } public List<String> getGirls() { return girls; } public void setGirls(List<String> girls) { this.girls = girls; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd=" + pwd + ", girls=" + girls + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
2、 模拟一个客户端
public static void main(String[] args) { try { Pwd pwd = new Pwd(); pwd.setPwdType("mm"); pwd.setPwd("123456"); List<String> girls = new ArrayList<>(); girls.add("AA"); girls.add("BB"); User user = new User(1,"jy", pwd, girls); User user1 = (User) user.clone(); user1.setName("zs"); user1.getPwd().setPwd("1111111"); user1.getGirls().add("CC"); System.out.println(user.toString() + "|" + user.getPwd().hashCode() + "|" + user.getGirls().hashCode() + "|" + user.hashCode()); System.out.println(user1.toString() + "|" + user1.getPwd().hashCode() + "|" + user1.getGirls().hashCode() + "|" + user1.hashCode()); } catch (Exception e) { e.printStackTrace(); } }
3、输出日志
User{id=1, name='jy', pwd=Pwd{pwdType='mm', pwd='1111111'}, girls=[AA, BB, CC]}|225534817|2096287|1878246837
User{id=1, name='zs', pwd=Pwd{pwdType='mm', pwd='1111111'}, girls=[AA, BB, CC]}|225534817|2096287|929338653
总结
1、 基本类型
对于基本数据类型的成员变量,比如int、float等浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2、 对象
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
3、 String字符串
对于变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有对象保持不变。
简介
我们需要对每个对象的clone() 方法进行实现。跟浅拷贝不同,深拷贝会对 对象以及对象内部的引用均复制。
案例
1、 修改User 对象的clone() 方法
@Override protected Object clone() throws CloneNotSupportedException { User user = null; try{ user = (User) super.clone(); } catch (Exception e){ e.printStackTrace(); } user.pwd = (Pwd) user.getPwd().clone(); user.girls = (List<String>) ((ArrayList) user.getGirls()).clone(); return user; }
2、模拟一个客户端,输出日志:
User{id=1, name='jy', pwd=Pwd{pwdType='mm', pwd='123456'}, girls=[AA, BB]}|225534817|67553|1878246837
User{id=1, name='zs', pwd=Pwd{pwdType='mm', pwd='1111111'}, girls=[AA, BB, CC]}|929338653|2096287|1259475182
3、当对象层次复杂的时候,这样做不但困难而且浪费时间和容易出现错误,特别有时候你不但需要深拷贝同时你也对这个对象进行浅拷贝的时候,你会发现重写clone() 方法并不是一个好的解决方案。这时候我们可以采取序列化,将要拷贝的对象输出成byte array,然后再利用ObjectInputStream 转换出新的对象,这样就可以完成深拷贝。
public Object deepClone() { byte[] bytes = null; // 序列化 try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos);){ //当前这个对象以对象流的方式输出 oos.writeObject(this); bytes = bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try(ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis);) { User copyObj = (User) ois.readObject(); return copyObj; } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
五、总结
1、创建新的对象比较复杂时,可以利用原型模式简化 对象的创建过程,同时也能够提高效率
2、不用重新初始化对象,而是 动态地获得对象运行时的状态
3、 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
4、在实现深克隆的时候可能需要比较复杂的代码
5、需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则