原型模式的定义
原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
调用这不需要知道任何创建细节,不调用构造函数。
属于创建型模式。
原型模式的使用场景
在开发中常能见到如下代码,虽然命名规范,注释也全面,但是这种代码并不算优雅,只是重复取值赋值。
能否通过某种方式将其简化呢?原型模式就可以实现。
public UserDTO converEntityToDTO(UserEntity entity){
UserDTO dto = new UserDTO();
//用户名
dto.setUserName(entity.getUserName());
//用户手机号
dto.setUserPhone(entity.getUserPhone());
//用户地址
dto.setUserAddress(entity.getUserAddress());
//用户性别
dto.setGender(entity.getGender);
//用户头像
dto.setPicture(entity.getPicture);
//用户xxx...
//....more lines
return dto;
}
原型模式适用的场景:
1.类初始化消耗的资源较多
2.new一个对象的过程较为繁琐(准备数据、访问权限等)
3.构造函数较为复杂
4.循环中创建大量对象
在Spring中,原型模式使用广泛,如scope="prototype",还有经常使用到的JSON.parseObject()也是原型模式的应用。
原型模式——浅克隆
/**
* 原型的抽象
*/
public interface IPrototype {
IPrototype clone();
}
/**
* @Description: 原型的具体实现类
*/
public class PrototypeObj implements IPrototype {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public IPrototype clone() {
PrototypeObj prototypeObj = new PrototypeObj();
prototypeObj.setAge(this.age);
prototypeObj.setName(this.name);
return prototypeObj;
}
@Override
public String toString() {
return "PrototypeObj{" + "age=" + age +
", name='" + name + '}';
}
}
/**
* @Description:测试类
*/
public class PrototypeTest {
public static void main(String[] args) {
PrototypeObj obj = new PrototypeObj();
obj.setAge(18);
obj.setName("prototype");
System.out.println("obj = " + obj);
PrototypeObj cloneObj = (PrototypeObj) obj.clone();
System.out.println("cloneObj = " + cloneObj);
}
}
原型模式就是这样,只是上面的属性赋值过程是手动完成的,该如何优化呢?
其实JDK中已经提供了用于clone的API,只需要实现Cloneable接口即可:
/**
* @Description: 优化后的原型的具体实现类
*/
public class PrototypeObj implements Cloneable {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "PrototypeObj{" + "age=" + age +
", name='" + name + '}';
}
}
重新运行,可以发现仍可以得到一样的结果。这样繁多的属性复制就可以通过JDK的api解决了。
再进行一个测试,给原型对象加上一个family属性:
/**
* @Description: 原型的具体实现类
*/
public class PrototypeObj implements Cloneable {
private int age;
private String name;
private List<String> family;//家庭成员
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getFamily() {
return family;
}
public void setFamily(List<String> family) {
this.family = family;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "PrototypeObj{" +
"age=" + age + ", name='" + name + ", family="
+ family + '}';
}
}
/**
* @Description: 测试类
*/
public class PrototypeTest {
public static void main(String[] args) {
PrototypeObj obj = new PrototypeObj();
obj.setAge(18);
obj.setName("prototype");
List<String> family = new ArrayList<>();
family.add("父亲");
family.add("母亲");
obj.setFamily(family);
PrototypeObj cloneObj = (PrototypeObj) obj.clone();
cloneObj.getFamily().add("表妹");
System.out.println("obj = " + obj);
System.out.println("cloneObj = " + cloneObj);
}
}
可以发现,给克隆后的对象的家庭成员添加一个表妹,原型对象的家庭成员也发生了改变。
克隆出来的对象和原对象应该是两个独立了的对象才对,这种情况显然不符合预期。
System.out.println(obj.getFamily()==cloneObj.getFamily());
比较两个对象中该属性地址值,发现是相同的。这表示复制的并不是值,而是引用地址。
那么当修改两个对象任意其一的family属性时,两者都会发生改变。
这就是所谓的“浅克隆”,只是复制了对象中属性的地址值,不是真正的重新赋予一个值相同的属性。
一处对象属性变其他地方也改变,互相干扰。这显然不是我们想要的结果。
原型模式——深度克隆
在上面的基础继续改造,增加一个deepClone()方法:
/**
* @Description: 原型的具体实现类
*/
@Data
public class PrototypeObj implements Cloneable,Serializable {
private int age;
private String name;
private List<String> family;//家庭成员
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public PrototypeObj 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);
PrototypeObj cloneObj = (PrototypeObj) ois.readObject();
return cloneObj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "PrototypeObj{" +
"age=" + age + ", name='" + name + ", family="
+ family + '}';
}
}
/**
* @Auther: jesses
* @Date: 2021/4/13
* @Description: 深度克隆的测试
*/
public class DeepCloneTest {
public static void main(String[] args) {
PrototypeObj obj = new PrototypeObj();
obj.setAge(24);
obj.setName("jesses");
List<String> family = new ArrayList<>();
family.add("父亲");
family.add("母亲");
obj.setFamily(family);
PrototypeObj cloneObj = obj.deepClone();
cloneObj.getFamily().add("表哥");
System.out.println("obj = " + obj);
System.out.println("cloneObj = " + cloneObj);
System.out.println(obj.getFamily()==cloneObj.getFamily()); //比较对象中引用类型的地址
}
}
运行得到了预期的结果,这样就只是对值进行拷贝了。
原型模式和克隆的关系
为什么原型模式一直在说克隆?
原型模式其实就是如何快速构建对象的方法总结,克隆是原型模式实现的最常见的方式,一般通过实现JDK提供的Cloneable接口,实现快速复制。
克隆破坏单例模式
如果克隆的目标是单例对象,那么深克隆就会破坏单例。
防止克隆破坏单例就要禁止深克隆。要么单例类不实现Cloneable接口,要么重写clone()方法,在clone方法中返回单例对象。
比如:
/**
* @Description: 原型的具体实现类
*/
@Getter
@Setter
public class PrototypeObj implements Cloneable,Serializable {
private int age;
private String name;
private List<String> family;//家庭成员
//构造方法私有化
private PrototypeObj() {
}
public static PrototypeObj INSTANCE=new PrototypeObj();
//调用clone时返回静态初始化的单例对象
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
@Override
public String toString() {
return "PrototypeObj{" +
"age=" + age + ", name='" + name + ", family="
+ family + '}';
}
}
原型模式和单例模式的冲突
对比单例模式和克隆模式,两者是存在一定冲突的。
如果对象是单例的,要不破坏单例的就只能浅克隆,克隆来克隆去都是同一个对象。深度克隆实现克隆出新对象才是我们需要的,浅克隆不符合克隆模式需要达成的效果。
而如果使用深度克隆,又会导致单例被破坏。
原型模式在JDK中的应用:
ArrayList是实现Cloneable接口,重写clone方法,将list中的元素遍历复制了一遍。
但是这种方式并不适用于业务中的代码使用,如果对象中定义了多种集合类型,每种情况都要单独处理,这种硬编码不如使用序列化来操作。
原型模式的优点
1.基于二进制流的拷贝,性能比new创建对象提升许多。
2.简化了创建对象时内部繁琐的过程
原型模式的缺点
1.需要为每一个类配置一个克隆方法
2.克隆方法位于类的内部,对类进行改造的时候,需要修改各个类的代码,违反开闭原则
3.实现深克隆代码较复杂,当多个类嵌套时,每一层对象都要实现深克隆,实现比较麻烦。需根据需求场景适用浅克隆和深克隆。