java設計模式之原型模式
什麼是原型模式
原型模式(Prototype Pattern)是指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象,調用者不需要知道任何創建細節,不調用構造函數,原型模式屬於創建型模式。
原型模式示例1
1.標準的原型模式需要先創建一個原型接口
package com.zwx.design.pattern.prototype;
public interface IPrototype {
IPrototype clone();
}
2.創建一個對象實現原型接口
package com.zwx.design.pattern.prototype.impl;
import com.zwx.design.pattern.prototype.IPrototype;
import java.util.List;
public class PrototypeImplA implements IPrototype{
private String name;
private int age;
private List<String> phoneList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getPhoneList() {
return phoneList;
}
public void setPhoneList(List<String> phoneList) {
this.phoneList = phoneList;
}
@Override
public IPrototype clone() {
PrototypeImplA prototypeImplA = new PrototypeImplA();
prototypeImplA.setAge(this.age);
prototypeImplA.setName(this.name);
prototypeImplA.setPhoneList(this.phoneList);
return prototypeImplA;
}
}
PrototypeImplA實現了接口IPrototype,並且實現了clone方法,返回了一個新的對象
3.編寫測試類
package com.zwx.design.pattern.prototype;
import com.zwx.design.pattern.prototype.impl.PrototypeImplA;
import java.util.ArrayList;
import java.util.List;
public class ProtoTypeTest {
public static void main(String[] args) throws Exception {
PrototypeImplA prototypeImplA = new PrototypeImplA();
prototypeImplA.setAge(18);
prototypeImplA.setName("張三");
List<String> phoneList = new ArrayList<>();
phoneList.add("88888888");
phoneList.add("77777777");
prototypeImplA.setPhoneList(phoneList);
PrototypeImplA cloneProtoType = (PrototypeImplA) prototypeImplA.clone();
System.out.println(prototypeImplA.getPhoneList() == cloneProtoType.getPhoneList());//true
}
}
最後一個會輸出true,這是因爲這種克隆方式是淺克隆,對象中如果有引用對象那麼被克隆後的對象依然會指向原對象,如果需要複製兩個獨立的對象,則需要使用深克隆,後面示例2中我們會對比一下兩種克隆方式
原型模式示例2
1.創建一個原型對象,實現Cloneable接口
package com.zwx.design.pattern.prototype.impl;
import java.io.*;
import java.util.List;
public class PrototypeB implements Cloneable {
private String name;
private int age;
private List<String> phoneList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getPhoneList() {
return phoneList;
}
public void setPhoneList(List<String> phoneList) {
this.phoneList = phoneList;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Object類默認有clone()方法,protected級別,且是淺克隆,如果我們需要使用默認的clone()方法,則必須實現一個Cloneable接口(Cloneable是一個標記接口,類似的還有Serializable等接口),否則會拋出異常CloneNotSupportedException
2.編寫測試類
package com.zwx.design.pattern.prototype;
import com.zwx.design.pattern.prototype.impl.PrototypeB;
import java.util.ArrayList;
import java.util.List;
public class ProtoTypeTest2 {
public static void main(String[] args) throws CloneNotSupportedException {
PrototypeB prototypeImplB = new PrototypeB();
prototypeImplB.setAge(18);
prototypeImplB.setName("張三");
List<String> phoneList = new ArrayList<>();
phoneList.add("88888888");
phoneList.add("77777777");
prototypeImplB.setPhoneList(phoneList);
PrototypeB cloneProtoTypeB = (PrototypeB)prototypeImplB.clone();
System.out.println(prototypeImplB.getPhoneList() == cloneProtoTypeB.getPhoneList());//true
}
}
這時候輸出的結果還是true,因爲上面我們用的是Object自帶的clone()方法,默認是淺克隆,那麼如何實現深克隆呢?接下來我們對PrototypeB類進行改寫。
3.改寫PrototypeB 類,實現深克隆
package com.zwx.design.pattern.prototype.impl;
import java.io.*;
import java.util.List;
public class PrototypeB implements Cloneable, Serializable {
private String name;
private int age;
private List<String> phoneList;
public String getName() {
return name;
}
public void setName(String name) throws CloneNotSupportedException{
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getPhoneList() {
return phoneList;
}
public void setPhoneList(List<String> phoneList) {
this.phoneList = phoneList;
}
public Object clone() throws CloneNotSupportedException {
// return super.clone();
return this.deepClone();
}
public PrototypeB 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);
PrototypeB clone = (PrototypeB)ois.readObject();
return clone;
}catch (Exception e){
return null;
}
}
}
多實現了一個Serializable接口,然後clone()方法中返回了自定義的深克隆方法deepClone(),這時候再運行測試類,返回的就是false!
4.深克隆帶來的問題
深克隆會破壞單例對象,所以如果是單例對象我們可以通過兩個辦法防止破壞:
1、不實現Cloneable接口,使得該類不支持克隆
2、重寫clone()方法,並且返回單例對象
原型模式適用場景
1、類初始化消耗資源較多時
2、初始化一個對象時需要非常繁瑣的過程時
3、構造函數比較複雜時
4、循環體中生產大量對象時,可讀性下降時
原型模式優點
1、當創建一個對象比較複雜時,使用原型對象通常效率會更高
原型模式缺點
1、需要爲每個對象配備克隆或者可拷貝的方法
2、對克隆複雜對象或者對克隆出來的對象進行復雜改造時,易帶來風險,深克隆、淺克隆要運用得當
總結
原型模式就是如何快速構建對象的方法總結, 通過簡單工廠將getter、setter封裝到某個方法中,實現快速複製。
原型模式到底應該採用淺克隆還是深克隆?這個應根據實際業務場景具體分析,但是即使淺克隆能滿足需求,也應該實現Cloneable接口,將clone()方法定義爲public,並調用super.clone()返回克隆對象