一、應用場景
在開發中常能見到如下代碼,雖然命名規範,註釋也全面,但是這種代碼並不算優雅,只是重複取值賦值。
能否通過某種方式將其簡化呢?原型模式就可以實現。
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;
}
對比單例模式和克隆模式,兩者是存在一定衝突的。
如果對象是單例的,要不破壞單例的就只能淺克隆,克隆來克隆去都是同一個對象。深度克隆實現克隆出新對象纔是我們需要的,淺克隆不符合克隆模式需要達成的效果。
而如果使用深度克隆,又會導致單例被破壞。