一、应用场景
在开发中常能见到如下代码,虽然命名规范,注释也全面,但是这种代码并不算优雅,只是重复取值赋值。
能否通过某种方式将其简化呢?原型模式就可以实现。
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.循环中创建大量对象
二、简单克隆
一个标准的原型模式代码,应该这样设计。
原型接口:
public interface Prototype {
Prototype clone();
}
继承原型接口的需要被克隆的对象:
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description:
*/
public class ConcretePrototype implements Prototype {
private int age;
private String name;
private List hobbies;
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 getHobbies() {
return hobbies;
}
public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}
@Override
public Prototype clone() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
concretePrototype.setHobbies(this.hobbies);
return concretePrototype;
}
}
操作克隆的client对象:
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description:
*/
public class Client {
public Client(){}
public Prototype startClone(Prototype prototype){
return prototype.clone();
}
}
测试类:
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description:
*/
public class PrototypeTest {
public static void main(String[] args) {
// 创建一个具体的需要克隆的对象,填充属性,方便测试
ConcretePrototype concretePrototypeA = new ConcretePrototype();
concretePrototypeA.setAge(18);
concretePrototypeA.setName("prototype");
List hobbies = new ArrayList<String>();
concretePrototypeA.setHobbies(hobbies);
Client client = new Client();
ConcretePrototype concretePrototypeB = (ConcretePrototype) client.startClone(concretePrototypeA);
System.out.println("concretePrototypeA = " + concretePrototypeA);
System.out.println("concretePrototypeB = " + concretePrototypeB);
System.out.println(concretePrototypeA.getHobbies());
System.out.println(concretePrototypeB.getHobbies());
System.out.println(concretePrototypeA.getHobbies()==concretePrototypeB.getHobbies());
}
}
可以看出hobbies 的引用地址是相同的,意味着复制的不是hobbies的值,而是引用的地址。
因此当我们修改concretePrototypeA中的hobbies或concretePrototypeB中的hobbies,两个对象中的hobbies都会改变。
这就是所谓的“浅克隆”,只是复制了对象中属性的地址值,不是真正的重新赋予一个值相同的属性。
一处对象属性变其他地方也改变,互相干扰。这显然不是我们想要的结果。
三、深度克隆
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description: 原型猴子 Monkey 类
*/
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
然后是猴子的引用对象类-金箍棒:
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description: 引用对象金箍棒 Jingubang 类
*/
public class Jingubang implements Serializable {
public float h = 100;
public float d = 10;
public void big() {
this.d = d * 2;
this.h = h * 2;
}
public void small() {
this.d = d / 2;
this.h = h / 2;
}
}
创建具体的对象-齐天大圣类:
/**
* @Auther: jesses
* @Date: 2020/7/3
* @Description: 具体的对象齐天大圣 QiTianDaSheng 类
*/
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
public Jingubang jingubang;
//初始化
public QiTianDaSheng(){
this.birthday=new Date();
this.jingubang=new Jingubang();
}
/**
* 深度克隆
*/
public Object 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);
QiTianDaSheng copy = (QiTianDaSheng) ois.readObject();
copy.birthday=new Date();
return copy;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 浅克隆
*/
public QiTianDaSheng shallowClone(QiTianDaSheng target){
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height=target.height;
qiTianDaSheng.weight=target.weight;
qiTianDaSheng.jingubang=target.jingubang;
qiTianDaSheng.birthday=new Date();
return qiTianDaSheng;
}
}
测试类:
public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
QiTianDaSheng deep = (QiTianDaSheng) qiTianDaSheng.deepClone();
System.out.println("深克隆"+(qiTianDaSheng.jingubang == deep.jingubang));
QiTianDaSheng shallow = qiTianDaSheng.shallowClone(qiTianDaSheng);
System.out.println("浅克隆"+(qiTianDaSheng.jingubang == shallow.jingubang));
}
}
运行结果:
四、小结
原型模式和克隆的关系
为什么原型模式一直在说克隆?
原型模式其实就是如何快速构建对象的方法总结,克隆是原型模式实现的最常见的方式,一般通过实现JDK提供的Cloneable接口,实现快速复制。
克隆破坏单例模式
如果克隆的目标对象是单例对象,那就意味着,深度克隆就会破坏单例。
想解决这个问题,就要禁止深度克隆。
要么单例类不再实现Cloneable接口,要么就重写clone()方法,在clone方法中返回单例对象。
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
对比单例模式和克隆模式,两者是存在一定冲突的。
如果对象是单例的,要不破坏单例的就只能浅克隆,克隆来克隆去都是同一个对象。深度克隆实现克隆出新对象才是我们需要的,浅克隆不符合克隆模式需要达成的效果。
而如果使用深度克隆,又会导致单例被破坏。