源碼:https://github.com/2020GetGoodOffer/springStudy
原型模式的應用場景
來看一段代碼:
public void setParam(ExamPaperVo vo){
ExamPaper examPaper=new ExamPaper();
//試卷id
examPaper.setId(vo.getId());
//試卷剩餘時間
examPaper.setLeaveTime(vo.getLeaveTime());
//試卷姓名
examPaper.setName(vo.getName());
.....
//省略數十行
}
該代碼非常公正,命名也很規範,註釋也很全,但是這樣的代碼屬於純體力勞動,原型模式就能替我們解決這樣的問題。
原型模式是指原型實例指定創建對象的種類,並且通過複製這些原型創建新的對象。
原型模式主要適用於以下場景:
(1)類初始化消耗資源較多
(2)使用new生成一個對象需要非常繁瑣的過程(數據準備、訪問權限等)
(3)構造方法比較複雜
(4)在循環體中產生大量對象
在Spring中,原型模式應用的非常廣泛。
例如scope=“prototype”,經常使用的JSON.parseObject()也是一種原型模式。
淺克隆
一個標準的原型模式代碼應該是這樣設計的,先創建原型Prototype接口:
public interface Prototype {
Prototype clone();
}
創建具體需要克隆的類ConcretePrototypeA:
public class ConcretePrototypeA implements Prototype {
private int age;
private String name;
private List<String> 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<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public Prototype clone() {
ConcretePrototypeA concretePrototypeA=new ConcretePrototypeA();
concretePrototypeA.setAge(this.age);
concretePrototypeA.setName(this.name);
concretePrototypeA.setHobbies(this.hobbies);
return concretePrototypeA;
}
}
創建Client類:
public class Client {
private Prototype prototype;
public Client(Prototype prototype){
this.prototype=prototype;
}
public Prototype startClone(Prototype prototype){
return prototype.clone();
}
}
測試代碼:
public static void main(String[] args) {
//創建需要克隆的對象
ConcretePrototypeA concretePrototypeA=new ConcretePrototypeA();
//填充屬性
concretePrototypeA.setAge(18);
concretePrototypeA.setName("prototype");
concretePrototypeA.setHobbies(new ArrayList<>());
System.out.println(concretePrototypeA);
//克隆
Client client=new Client(concretePrototypeA);
ConcretePrototypeA concretePrototypeA1= (ConcretePrototypeA) client.startClone();
System.out.println(concretePrototypeA1);
System.out.println("克隆對象中引用類型地址:"+concretePrototypeA1.getHobbies().hashCode());
System.out.println("原對象中引用類型地址:"+concretePrototypeA.getHobbies().hashCode());
}
運行結果:
可以看出引用地址是相同的,意味着複製的不是值,而是引用的地址。這樣的話如果修改任意一個對象的屬性值,另一個對象的hobbies值都會改變。
這就是淺克隆,淺克隆只是完整複製了值類型數據,沒有賦值引用對象。換言之所有引用對象仍然指向原來的對象。
深克隆
我們換一個場景,以孫悟空爲例:
原型猴子類:
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
創建引用對象金箍棒類:
public class Bang implements Serializable {
public float h=100;
public float d=10;
public void big(){
this.h*=2;
this.d*=2;
}
public void small(){
this.h/=2;
this.d/=2;
}
}
創建具體的孫悟空類:
public class SunWuKong extends Monkey implements Cloneable, Serializable {
public Bang bang;
public SunWuKong(){
this.birthday=new Date();
this.bang=new Bang();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
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);
SunWuKong copy= (SunWuKong) ois.readObject();
copy.birthday=new Date();
return copy;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public SunWuKong shallowClone(SunWuKong target){
SunWuKong sunWuKong = new SunWuKong();
sunWuKong.height=target.height;
sunWuKong.weight=target.weight;
sunWuKong.birthday=new Date();
sunWuKong.bang=target.bang;
return sunWuKong;
}
}
測試代碼:
public class Test {
public static void main(String[] args) {
SunWuKong sunWuKong = new SunWuKong();
try{
SunWuKong clone= (SunWuKong) sunWuKong.clone();
System.out.println("深克隆:"+(sunWuKong.bang==clone.bang));
}catch (Exception e){
e.printStackTrace();
}
SunWuKong sunWuKong1 = new SunWuKong();
SunWuKong sunWuKong2 = sunWuKong1.shallowClone(sunWuKong1);
System.out.println("淺克隆:"+(sunWuKong1.bang==sunWuKong2.bang));
}
}
運行結果:
克隆破壞單例模式
如果我們克隆的對象是單例對象,那麼意味着深克隆會破壞單例模式。
實際上防止克隆破壞單例模式的思路很簡單,禁止深克隆即可。
要麼我們的單例類不實現Cloneable接口,要麼我們重寫clone方法,在clone方法中返回單例對象即可,具體代碼如下:
@Override
protected Object clone() throws CloneNotSupportedException{
return INSTANCE;
}
clone方法的源碼
常用的ArrayList實現了Cloneable接口