- 模式定义
指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 知识点
1.Cloneable接口/Object#clone方法 详解
2.浅拷贝/深拷贝
3.序列化机制实现深拷贝
- 应用场景
当代码不应该依赖于需要复制的对象的具体类时,请使用Prototype模式。
- 有点
1.可以不耦合具体类的情况下克隆对象
2.避免重复的初始化代码
3.更方便的构建复杂对象
- 示例:
我们有一个类Product
class Product{ private String part1; private String part2; private Integer part3; private Integer part4; public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public Integer getPart3() { return part3; } public void setPart3(Integer part3) { this.part3 = part3; } public Integer getPart4() { return part4; } public void setPart4(Integer part4) { this.part4 = part4; } public Product(String part1, String part2, Integer part3, Integer part4) { this.part1 = part1; this.part2 = part2; this.part3 = part3; this.part4 = part4; } @Override public String toString() { return "Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3=" + part3 + ", part4=" + part4 + '}'; } }
我们在创建一群比较类似的对象时,以往就是不停的new
public class ProtoTypeTest { public static void main(String[] args) { Product product = new Product("part1","part2",3,4); //...new } }
现在我们需要通过拷贝来实现
- 实现Cloneable接口。
- 实现Cloneable接口的clone方法。
class Product implements Cloneable{ private String part1; private String part2; private Integer part3; private Integer part4; public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public Integer getPart3() { return part3; } public void setPart3(Integer part3) { this.part3 = part3; } public Integer getPart4() { return part4; } public void setPart4(Integer part4) { this.part4 = part4; } public Product(String part1, String part2, Integer part3, Integer part4) { this.part1 = part1; this.part2 = part2; this.part3 = part3; this.part4 = part4; } @Override protected Product clone() throws CloneNotSupportedException { return ((Product) super.clone()); } @Override public String toString() { return "hashcode:"+super.hashCode()+"|Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3=" + part3 + ", part4=" + part4 + '}'; } }
我们来看一下clone的对象
public class ProtoTypeTest { public static void main(String[] args) throws CloneNotSupportedException { Product product = new Product("part1","part2",3,4); //...new Product clone = product.clone(); System.out.println("original: "+product); System.out.println("clone: "+clone); } }
通过hashcode我们发现这是clone出来的对象是与原对象不相关的新对象(没有依赖关系),clone的底层实现是native修饰的,说明是虚拟机帮我们来做的。
现在我们对象中全部是八中原生类型及其包装类型,string,bigInteger等都是属于不可变类型(immutable),所以我们可以用上面的方式进行实现,没有问题。但是如果对象中有引用类型就会有问题,如下:
class BaseInfo{ private String companyName; public BaseInfo(String companyName) { this.companyName = companyName; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } @Override public String toString() { return super.hashCode()+"|BaseInfo{" + "companyName='" + companyName + '\'' + '}'; } } class Product implements Cloneable{ private String part1; private String part2; private Integer part3; private Integer part4; private BaseInfo baseInfo; public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public Integer getPart3() { return part3; } public void setPart3(Integer part3) { this.part3 = part3; } public Integer getPart4() { return part4; } public void setPart4(Integer part4) { this.part4 = part4; } public BaseInfo getBaseInfo() { return baseInfo; } public void setBaseInfo(BaseInfo baseInfo) { this.baseInfo = baseInfo; } public Product(String part1, String part2, Integer part3, Integer part4, BaseInfo baseInfo) { this.part1 = part1; this.part2 = part2; this.part3 = part3; this.part4 = part4; this.baseInfo = baseInfo; } @Override protected Product clone() throws CloneNotSupportedException { return ((Product) super.clone()); } @Override public String toString() { return super.hashCode()+"Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3=" + part3 + ", part4=" + part4 + ", baseInfo=" + baseInfo + '}'; } }
我们来测试一下:
public class ProtoTypeTest { public static void main(String[] args) throws CloneNotSupportedException { BaseInfo baseInfo = new BaseInfo("xxx"); Product product = new Product("part1","part2",3,4,baseInfo); //...new Product clone = product.clone(); System.out.println("original: "+product); System.out.println("clone: "+clone); product.getBaseInfo().setCompanyName("yyyy"); System.out.println("original: "+product); System.out.println("clone: "+clone); } }
可以看到clone对象和原始对象中的baseInfo用的是同一个对象,当我们对原始对象的baseInfo进行修改后,发现clone对象和原始对象的baseInfo都修改了,这个时候说明我们的拷贝出问题了,原始对象和拷贝对象有了关系,产生了依赖,怎么解决呢?
我们有两种方式进行解决:
一是我们对被引用对象也实现Cloneable接口,然后修改引用对象的clone方法,直接在引用对象中的clone方法中实现对引用对象的拷贝,如下
class BaseInfo implements Cloneable{ private String companyName; public BaseInfo(String companyName) { this.companyName = companyName; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } @Override protected BaseInfo clone() throws CloneNotSupportedException { return ((BaseInfo) super.clone()); } @Override public String toString() { return super.hashCode()+"|BaseInfo{" + "companyName='" + companyName + '\'' + '}'; } } class Product implements Cloneable{ private String part1; private String part2; private Integer part3; private Integer part4; private BaseInfo baseInfo; public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public Integer getPart3() { return part3; } public void setPart3(Integer part3) { this.part3 = part3; } public Integer getPart4() { return part4; } public void setPart4(Integer part4) { this.part4 = part4; } public BaseInfo getBaseInfo() { return baseInfo; } public void setBaseInfo(BaseInfo baseInfo) { this.baseInfo = baseInfo; } public Product(String part1, String part2, Integer part3, Integer part4, BaseInfo baseInfo) { this.part1 = part1; this.part2 = part2; this.part3 = part3; this.part4 = part4; this.baseInfo = baseInfo; } @Override protected Product clone() throws CloneNotSupportedException { Product clone = (Product) super.clone(); BaseInfo clone1 = this.baseInfo.clone(); clone.setBaseInfo(clone1); return clone; } @Override public String toString() { return super.hashCode()+"Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3=" + part3 + ", part4=" + part4 + ", baseInfo=" + baseInfo + '}'; } }
这时候我们再看一下运行结果,就发现clone出来的对象和原始对象已经没有依赖关系:
如果我们的baseInfo中还有引用对象,那我们必须对改对象也实现cloneable接口。这就是深拷贝。
注意数组类型已实现cloneable接口,但是数组类型clone出来的是浅拷贝而不是深拷贝。
我们在拷贝的时候要注意浅拷贝和深拷贝,如果类中有引用类型时我们就要进行递归式的实现cloneable接口。
对于引用对象比较复杂的情况我们还有另一种方式来实现:序列化方式实现深拷贝,首先原始对象和其包含的引用对象都要实现序列化接口,然后通过流进行序列化操作,这种操作不推荐使用,会非常耗性能,具体如下:
class BaseInfo implements Cloneable,Serializable{ private static final long serialVersionUID = 42L; private String companyName; public BaseInfo(String companyName) { this.companyName = companyName; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } @Override protected BaseInfo clone() throws CloneNotSupportedException { return ((BaseInfo) super.clone()); } @Override public String toString() { return super.hashCode()+"|BaseInfo{" + "companyName='" + companyName + '\'' + '}'; } } class Product implements Cloneable,Serializable{ private static final long serialVersionUID = 42L; private String part1; private String part2; private Integer part3; private Integer part4; private BaseInfo baseInfo; public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public Integer getPart3() { return part3; } public void setPart3(Integer part3) { this.part3 = part3; } public Integer getPart4() { return part4; } public void setPart4(Integer part4) { this.part4 = part4; } public BaseInfo getBaseInfo() { return baseInfo; } public void setBaseInfo(BaseInfo baseInfo) { this.baseInfo = baseInfo; } public Product(String part1, String part2, Integer part3, Integer part4, BaseInfo baseInfo) { this.part1 = part1; this.part2 = part2; this.part3 = part3; this.part4 = part4; this.baseInfo = baseInfo; } @Override protected Product clone() throws CloneNotSupportedException { /*Product clone = (Product) super.clone(); BaseInfo clone1 = this.baseInfo.clone(); clone.setBaseInfo(clone1);*/ ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (ObjectOutputStream oos = new ObjectOutputStream(bos)){ oos.writeObject(this); } catch (IOException e) { e.printStackTrace(); } ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); try (ObjectInputStream ois = new ObjectInputStream(bis);){ Product object =(Product) ois.readObject(); return object; } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e){ e.printStackTrace();; } return null; } @Override public String toString() { return super.hashCode()+"Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3=" + part3 + ", part4=" + part4 + ", baseInfo=" + baseInfo + '}'; } }
我们看一下运行结果:也是可以的
- 源码中的应用
org.springframework.beans.factory.support.AbstractBeanDefinition
java.util.Arrays