一句话概括“原型模式最主要的其实是深浅拷贝问题”
原型模式(Prototype Pattern),还是创建性的设计模式,主要还是遇到复杂的对象创建时候可以通过原型模式来减少操作,增加效率。
举例常规的创建对象:
@Data
public class AcAccountExt implements Serializable {
private Integer aeId;
private Integer aeAcctType;
private Integer aeAcctId;
private Integer aeUserId;
private String aeAcctName;
private String aeAcctCard;
private String aeAcctBank;
private String aeAcctCode;
private String aeAcctRelCode;
private String aeBankProv;
private String aeBankCity;
private String aeIdCard;
private String aeCrtTime;
private Integer aeState;
private String aeStateTime;
private String aeAppId;
private String aePrivateKey;
private String aePublicKey;
private String aeOtherPublicKey;
private String aeAlipayUserId;
private Integer aePubIdtyId;
private Integer aeQuerytransFlag;
private Integer aeQuerytransChannel;
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
AcAccountExt ae = new AcAccountExt();
ae.setAeAcctBank("");
ae.setAeAcctId(123);
... 省略其他的
}
}
最初时候,需要new完对象后一个个赋值,上面的例子是创建一个,如果创建多个,会非常费力,尤其是内容相差不多的情况下,可能只是其中的一两个字段修改,其他都一样。
此时就应运而生了原型模式(不太准确,应该是顺势被总结成了方法论),这样就可以这么写了:
@Data
public class AcAccountExt implements Serializable,Cloneable {
private Integer aeId;
private Integer aeAcctType;
private String aeAcctBank;
private Integer aeAcctId;
//省略一堆无用的字段
private static final long serialVersionUID = 1L;
// 这个重写写不写都行,或者只要简单的 return (AcAccountExt)super.clone();就好,这样可以有更多自己的定制化信息或者异常处理
@Override
protected AcAccountExt clone() throws CloneNotSupportedException {
AcAccountExt ae = null;
try {
ae = (AcAccountExt) super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return ae;
}
public static void main(String[] args) {
AcAccountExt ae = new AcAccountExt();
ae.setAeAcctBank("11");
ae.setAeAcctId(123);
AcAccountExt ae2 = null;
try {
ae2 = ae.clone();
ae2.setAeAcctId(222);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(ae);
System.out.println(ae2);
}
}
运行结果如下:
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=123, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null)
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=222, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null)
注意下几点:
- 实现的接口要加上Cloneable
- 如果有特殊需求可以重写一下clone方法(深浅拷贝在此区分)
上面就可以把正常的原型模式说差不多,但是会有一些问题,如果这个类稍微复杂点,不只是普通类型的,而替换成别的类呢?
还是这个类,举个例子:
@Data
public class AcAccountExt implements Cloneable {
private Integer aeId;
private Integer aeAcctId;
private String aeAcctBank;
// 去掉没用的
private AcAccount aa;
public static void main(String[] args) {
AcAccountExt ae = new AcAccountExt();
ae.setAeAcctBank("11");
ae.setAeAcctId(123);
ae.aa = new AcAccount("123",111);
AcAccountExt ae2 = null;
try {
ae2 = (AcAccountExt) ae.clone();
ae2.setAeAcctId(222);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(ae);
System.out.println(ae2);
System.out.println("修改一下");
ae.aa.setAcCrtTime("222");
System.out.println(ae);
System.out.println(ae2);
}
}
执行结果是:
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=123, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = 305768267, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=123, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=222, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = 305768267, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=123, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
修改一下
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=123, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = -849224565, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=222, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=222, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = -849224565, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=222, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
内容有点多,可以看到修改了ae的aa,但是ae2的也改了,还有可以看到修改前的一组的哈希值是相同的,修改后的也是相同的,说明其实只是指向了同一个地址,一个修改都修改了。
具体修改的方式可以比较容易想到一种,就是前面提到一嘴的重写clone方法,每个值都拿出来赋值一下,如果是新的类就新建一个赋值。
除了重写clone方法,还有一种是通过序列化来实现。
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
AcAccountExt copyObj = (AcAccountExt)ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
public static void main(String[] args) {
AcAccountExt ae = new AcAccountExt();
ae.setAeAcctBank("11");
ae.setAeAcctId(123);
ae.aa = new AcAccount("123",111);
AcAccountExt ae2 = null;
ae2 = (AcAccountExt) ae.deepClone();
ae2.setAeAcctId(222);
System.out.println(ae);
System.out.println(ae2);
System.out.println("修改一下");
ae.aa.setAcCrtTime("222");
System.out.println(ae);
System.out.println(ae2);
}
输出结果:
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=123, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = 305768267, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=123, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=222, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = 305768267, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=123, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
修改一下
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=123, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = -849224565, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=222, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
AcAccountExt(aeId=null, aeAcctType=null, aeAcctId=222, aeUserId=null, aeAcctName=null, aeAcctCard=null, aeAcctBank=11, aeAcctCode=null, aeAcctRelCode=null, aeBankProv=null, aeBankCity=null, aeIdCard=null, aeCrtTime=null, aeState=null, aeStateTime=null, aeAppId=null, aePrivateKey=null, aePublicKey=null, aeOtherPublicKey=null, aeAlipayUserId=null, aePubIdtyId=null, aeQuerytransFlag=null, aeQuerytransChannel=null, aa=AcAccount [Hash = 305768267, acId=111, acUserId=null, acCustId=null, acAcctType=null, acChannel=null, acChnlGrp=null, acInitVal=null, acAvbBlance=null, acFrezFee=null, acCrtTime=123, acCanCharge=null, acCanWithdraw=null, acCanPay=null, acCanAdjst=null, acCanInvc=null, acState=null, acStateTime=null, acRelationUserId=null, acPubIdtyId=null])
的确不一样了,哈希值也不一样了
总结一下:
原型模式便捷了对象的创建,但是细微之处还是有些需要注意的,如果类稍微复杂点,就容易不能达到预期的目标,所以需要深拷贝一下,方式有两种:
- 重写clone方法,每个值单独赋值
- 通过序列化来处理(有些不想序列化可以在属性前面加transient或者static关键字)
序列化实现深拷贝的原理就是:
序列化是把对象的字面量保存,然后反序列化时候一个个赋值回去,如果里面的属性是其他的类,则需要这个类也实现序列化接口Serializable。同样序列化实现深拷贝也能通过泛型限定是否实现序列化接口。
深拷贝有点不好的就是需要额外的一堆编码。