原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它提供了一种创建对象的最佳方式。原型模式单独使用的比较少,较多的是结合工厂模式创建一些复杂的、创建开销比较大的对象,或者是复制一些经过处理、改变初始属性值的对象。
原型模式可以有两种实现方式,一是通过实现Cloneable接口,重写clone方法实现;二是通过序列化和反序列化方式实现。
一、Clone方式实现:
先创建一个原型类:
package com.minant.prototype;
import java.util.Date;
import java.util.Objects;
/**
* @ClassName MyProto
* @Description TODO 原形对象
* @Author MinAnt
* @Date 2020/5/20
* @Version V1.0
*/
public class MyProto implements Cloneable {
private String name;
private ProtoTarget protoTarget;
public MyProto() {
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public ProtoTarget getProtoTarget() {
return protoTarget;
}
public void setProtoTarget(ProtoTarget protoTarget) {
this.protoTarget = protoTarget;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyProto)) return false;
MyProto proto = (MyProto) o;
return Objects.equals(getName(), proto.getName()) &&
Objects.equals(getProtoTarget(), proto.getProtoTarget());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getProtoTarget());
}
@Override
public String toString() {
return "MyProto{" +
"name='" + name + '\'' +
", protoTarget=" + protoTarget +
'}';
}
}
该类实现了Cloneable接口,并重写了Object类的clone方法。Cloneable为一个空接口,若不实现该接口则在调clone方法的时候会报出异常(CloneNotSupportedException),无法实现克隆操作。该类中还包含一个ProtoTarget对象,ProtoTarget类也实现了Cloneable接口,重写了clone方法。
package com.minant.prototype;
import java.util.Objects;
/**
* @ClassName ProtoTarget
* @Description TODO
* @Author MinAnt
* @Date 2020/5/20
* @Version V1.0
*/
public class ProtoTarget implements Cloneable {
private String msg;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public ProtoTarget(String msg) {
this.msg = msg;
}
public ProtoTarget() {
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ProtoTarget)) return false;
ProtoTarget that = (ProtoTarget) o;
return Objects.equals(getMsg(), that.getMsg());
}
@Override
public int hashCode() {
return Objects.hash(getMsg());
}
@Override
public String toString() {
return "ProtoTarget{" +
"msg='" + msg + '\'' +
'}';
}
}
测试类:
package com.minant.prototype;
/**
* @ClassName TestProtoType
* @Description TODO 测试原型模式
* @Author MinAnt
* @Date 2020/5/20
* @Version V1.0
*/
public class TestProtoType {
public static void main(String[] args) throws CloneNotSupportedException {
MyProto proto = new MyProto();
proto.setName("我是正版");
proto.setProtoTarget(new ProtoTarget("aaa"));
System.out.println(proto.toString());
MyProto cpproto = (MyProto) proto.clone();
System.out.println(cpproto.toString());
}
}
结果:
看这个结果貌似没有什么问题,把原型对象复制了一份一模一样的对象出来。但实际上是有问题,下面就来修改下对象属性看下结果。
修改克隆对象中的ProtoTarget对象的属性后,原对象中的属性也跟着改变了,说明克隆对象的ProtoTarget属性和原对象中的这一属性还是指定在同一个内存地址。这种克隆称之为浅克隆。那么要想实现深克隆,就得把原型对象的所有类属性都克隆一遍,如下:
这也是为什么ProtoTarget也要实现Cloneable、重写clone方法的原因。如此以后,现查看结果:
这样就实现深克隆了。
二、序列化反序列化方式实现:
序列化的实现就需要类实现Serializable接口。
然后写测试类:
package com.minant.prototype;
import java.io.*;
/**
* @ClassName TestProtoSeril
* @Description TODO 序列化反序列化生成测试
* @Author MinAnt
* @Date 2020/5/21
* @Version V1.0
*/
public class TestProtoSeril {
public static void main(String[] args) throws IOException, ClassNotFoundException {
MyProto proto = new MyProto();
proto.setName("我是正版");
proto.setProtoTarget(new ProtoTarget("aaa"));
System.out.println(proto.toString());
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(proto);
bout.close();
bout.close();
byte[] rs = bout.toByteArray();
ByteArrayInputStream bin = new ByteArrayInputStream(rs);
ObjectInputStream oin = new ObjectInputStream(bin);
MyProto cpproto = (MyProto) oin.readObject();
System.out.println(cpproto.toString());
System.out.println("==============================");
cpproto.getProtoTarget().setMsg("bbb");
System.out.println(proto.toString());
System.out.println(cpproto.toString());
}
}
运行结果:
、
修改复制类不影响原型类。
最后我们可以来简单测试一下New创建和Clone创建之间的效率问题:
package com.minant.prototype;
/**
* @ClassName ProtoPerformTest
* @Description TODO 性能测试
* @Author MinAnt
* @Date 2020/5/21
* @Version V1.0
*/
public class ProtoPerformTest {
public static void testNew() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
ProtoPerForm form = new ProtoPerForm();
}
long end = System.currentTimeMillis();
System.out.println("New 耗时:" + (end - start));
}
public static void testClone() throws CloneNotSupportedException {
ProtoPerForm form = new ProtoPerForm();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
ProtoPerForm temp = (ProtoPerForm) form.clone();
}
long end = System.currentTimeMillis();
System.out.println("Clone 耗时:" + (end - start));
}
public static void main(String[] args) throws CloneNotSupportedException {
testNew();
testClone();
}
}
class ProtoPerForm implements Cloneable {
public ProtoPerForm() {
// 模拟创建对象过程
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
结果:
由结果可看出,new的过程越复杂、耗时越多则两者效率差距会越大。
原型模式到此结束,本人能力有限,有不足之处还请各位不吝赐教。