原型模式
源碼:https://github.com/GiraffePeng/design-patterns
1、定義
原型模式(Prototype Pattern)是指原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。
2、應用場景
原型模式主要适用于以下場景:
- 1、類初始化消耗資源較多。
- 2、new 産生的一個對象需要非常繁瑣的過程(資料準備、通路權限等)
- 3、構造函數比較複雜。
- 4、循環體中生産大量對象時。
在 Spring 中,原型模式應用得非常廣泛,例如 scope=“prototype”。在我們經常用的 JSON.parseObject()也是一種原型模式。
3、淺克隆
針對接口程式設計,讓我們先建立一個克隆接口PrototypeInteface.java
public interface PrototypeInteface {
public PrototypeInteface clone();
}
建立一個實體類實作PrototypeInteface接口,重寫clone方法。
public class ShallowPrototype implements PrototypeInteface{
private int id;
private String name;
private String type;
private List<String> arr;
public List<String> getArr() {
return arr;
}
public void setArr(List<String> arr) {
this.arr = arr;
}
public ShallowPrototype() {
super();
}
public ShallowPrototype(int id, String name, String type,List<String> arr) {
super();
this.id = id;
this.name = name;
this.type = type;
this.arr = arr;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public ShallowPrototype clone() {
ShallowPrototype shallowPrototype = new ShallowPrototype();
shallowPrototype.setId(this.id);
shallowPrototype.setName(this.name);
shallowPrototype.setType(this.type);
shallowPrototype.setArr(this.arr);
return shallowPrototype;
}
}
建立測試類:
public class PrototypeTest {
public static void main(String[] args) {
ShallowPrototype shallowPrototype = new ShallowPrototype(1, "name", "type",new ArrayList<String>() {{
add("測試");
}});
ShallowPrototype clone = shallowPrototype.clone();
System.out.println(shallowPrototype.getName() == clone.getName());
System.out.println(shallowPrototype.getArr() == clone.getArr());
clone.getArr().add("修改");
List<String> arr = shallowPrototype.getArr();
for (String string : arr) {
System.out.println(string);
}
}
}
執行後列印的控制台結果:
true
true
測試
修改
從測試結果看出 arr 集合的引用位址是相同的,意味着複制的不是值,而是引用的位址。這樣的話,如果我們修改任意一個對象中的引用資料類型,shallowPrototype 和clone 的 arr 值(List)都會改變。這就是我們常說的淺克隆。隻是完整複制了值的資料,沒有複制引用對象本身去建立新的引用對象。換言之,所有的引用對象仍然指向原來的對象。
JDK中也預設提供了Object的clone方法,隻需在要求可以克隆的類上實作Cloneable接口,寫出clone方法,調用super.clone即可實作淺克隆,例如:
//實作Cloneable接口
public class PrototypeClone implements Cloneable{
private int id;
private String name;
private String type;
private List<String> arr;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<String> getArr() {
return arr;
}
public void setArr(List<String> arr) {
this.arr = arr;
}
public PrototypeClone(int id, String name, String type, List<String> arr) {
super();
this.id = id;
this.name = name;
this.type = type;
this.arr = arr;
}
public PrototypeClone clone() {
try {
//調用super.clone方法
return (PrototypeClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
建立測試類:
public class TestJdkClone {
public static void main(String[] args) {
PrototypeClone prototypeClone = new PrototypeClone(1, "123", "1", new ArrayList<String>() {{add("ceshi");}});
PrototypeClone clone = prototypeClone.clone();
System.out.println(prototypeClone.getArr() == clone.getArr());
}
}
列印結果:
true
說明JDK提供的clone也為淺克隆。
4、深克隆
深克隆就是僅僅克隆一個對象的值,而不克隆其引用位址,相當于重新在堆記憶體中開辟一塊空間配置設定個該對象中的屬性。
實作方式我們可以采用序列化與反序列化來實作。
同樣建立接口:
public interface DeepCloneInterface {
public Object cloneObject();
}
建立實作類,實作DeepCloneInterface 以及 Serializable接口(為了反序列化)
public class DeepPrototype implements DeepCloneInterface,Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private int id;
private String name;
private List<String> arr;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getArr() {
return arr;
}
public void setArr(List<String> arr) {
this.arr = arr;
}
//序列化與反序列化實作深克隆
@Override
public Object cloneObject() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object readObject = ois.readObject();
ois.close();
bis.close();
oos.flush();
oos.close();
bos.close();
return readObject;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
測試類:
public class DeepTest {
public static void main(String[] args) {
DeepPrototype deepPrototype = new DeepPrototype();
deepPrototype.setArr(new ArrayList<String>() {{add("測試");}});
deepPrototype.setId(1);
deepPrototype.setName("測試");
DeepPrototype cloneObject = (DeepPrototype)deepPrototype.cloneObject();
System.out.println(cloneObject.getArr() == deepPrototype.getArr());
}
}
列印結果:
false
可以看到深克隆下,克隆的僅僅為值,并不會去克隆引用類型的配置設定位址。
5、克隆破壞單例模式
如果我們克隆的目标的對象是單例對象,那意味着,深克隆就會破壞單例。實際上防止克隆破壞單例解決思路非常簡單,禁止深克隆便可。要麼單例類不實作Cloneable 接口;要麼我們重寫 clone()方法,在 clone 方法中傳回單例對象即可,具體如下:
....
@Override
protected Object clone() {
return 具體的單例執行個體對象;
}
....