1.業務需求
? 大家好,我是菠菜啊,前倆天有點忙,今天繼續更新了。今天給大家介紹克隆對象——原型模式。老規矩,在介紹這期之前,我們先來看看這樣的需求:《西游記》中每次孫悟空拔出一撮猴毛吹一下,變出一大批猴子加入戰斗,他到底是怎么變的?如果我們幫他實現這個功能,代碼怎么設計?
2.代碼實現
首先先說第一個問題,怎么變的我也不知道。
但是第二個問題,可以嘗試一下。
實現初步思路:
? 我們新建一個猴子類,并且實例化多個猴子對象不就行了,太簡單了。
Monkey類:
//猴子
public class Monkey {private String name;private String sex;private int age;private Weapon weapon;public Monkey(String name, String sex, int age, Weapon weapon) {this.name = name;this.sex = sex;this.age = age;this.weapon = weapon;}public Weapon getWeapon() {return weapon;}public void setWeapon(Weapon weapon) {this.weapon = weapon;}@Overridepublic String toString() {return "Monkey{" +"name='" + name + '\'' +", sex='" + sex + '\'' +", age=" + age +", weapon=" + weapon +'}';}
}
Weapon類:
//武器
public class Weapon {private String name;private String color;public Weapon(String name, String color) {this.name = name;this.color = color;}@Overridepublic String toString() {return "Weapon{" +"name='" + name + '\'' +", color='" + color + '\'' +'}';}
}
Client類:
public class Client {public static void main(String[] args) {Weapon weapon=new Weapon("金箍棒","金色");Monkey monkey=new Monkey("孫悟空","公",20,weapon);Weapon weapon2=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());Monkey monkey2=new Monkey("猴小弟",monkey.getSex(),monkey.getAge(),weapon2);Weapon weapon3=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());Monkey monkey3=new Monkey("猴小小弟",monkey.getSex(),monkey.getAge(),weapon3);System.out.println(monkey);System.out.println(monkey2);System.out.println(monkey3);}
}
思考:上述代碼比較簡單,功能是實現了,但是在克隆猴哥的時候,我們要將新建一個相同類的對象。 然后, 我還要必須遍歷原始對象的所有成員變量, 并將成員變量值復制到新對象中。這也太麻煩了,如果屬性是上千上萬個,那么猴哥還沒變出猴子,師傅就被妖怪給吃了。 而且并非所有對象都能通過這種方式進行復制, 因為有些對象可能擁有私有成員變量, 它們在對象本身以外是不可見的。而且克隆對象,要知道該對象類的所有依賴類才行,這樣設計也太不符合迪米特法則了(詳細見***《設計模式——設計原則介紹》***一文)。
3.方案改進
? Java中提供了一個Cloneable接口,其中有一個clone()方法,我們只要實現這個方法就行了。
? 實現代碼結構圖:
Monkey接口:
//猴子
public class Monkey implements Cloneable{//......@Overrideprotected Monkey clone() throws CloneNotSupportedException {return (Monkey)super.clone();}}
Client類:
public class Client {public static void main(String[] args) throws CloneNotSupportedException {Weapon weapon=new Weapon("金箍棒","金色");Monkey monkey=new Monkey("孫悟空","公",20,weapon);/* Weapon weapon2=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());Monkey monkey2=new Monkey("猴小弟",monkey.getSex(),monkey.getAge(),weapon2);Weapon weapon3=new Weapon(monkey.getWeapon().getName(),monkey.getWeapon().getColor());Monkey monkey3=new Monkey("猴小小弟",monkey.getSex(),monkey.getAge(),weapon3);*/Monkey monkey2=monkey.clone();monkey2.setName("猴小弟");Monkey monkey3=monkey.clone();monkey3.setName("猴小小弟");System.out.println(monkey);System.out.println(monkey2);System.out.println(monkey3);}
}
? 思考:這樣我們就可以快速克隆對象,并且不需要知道對象創建的細節,又大大提高了性能,我們把這種設計模式叫做原型模式(Prototype )。上述代碼還是有問題,可以繼續往下看。
拓展:淺克隆和深克隆
? 我們把上述代碼稍微修改一下,看的就明顯了。
Client修改后:
public class Client {public static void main(String[] args) throws CloneNotSupportedException {Weapon weapon=new Weapon("金箍棒","金色");Monkey monkey=new Monkey("孫悟空","公",20,weapon);Monkey monkey2=monkey.clone();monkey2.setName("猴小弟");Monkey monkey3=monkey.clone();monkey3.setName("猴小小弟");System.out.println("修改武器前:"+monkey);System.out.println("修改武器前:"+monkey2);System.out.println("修改武器前:"+monkey3);//修改各自的武器裝備monkey.getWeapon().setColor("紅色");monkey2.getWeapon().setColor("白色");monkey3.getWeapon().setColor("綠色");System.out.println("++++++修改武器+++++");System.out.println("修改武器后:"+monkey);System.out.println("修改武器后:"+monkey2);System.out.println("修改武器后:"+monkey3);}
}
**預期結果:**猴子們的武器顏色分別是紅白綠。
實際結果:猴子們的武器都被綠了(一不小心開車了)。
排查原因發現,super.clone(),如果字段是值類型的,就復制值,如果字段是引用類型的,復制引用而不復制引用的對象(String是特殊的引用對象),因此猴子們引用的武器對象是一個。被復制的對象的所有變量值都含有原來對象相同的值,但是其它對象的引用仍然執行原來的對象,叫做淺克隆**。反之,把引用對象的變量指向復制過的新對象,這種叫做深克隆。
我們如果要完成深復制,只需做如下修改:
Weapon類:
//武器
public class Weapon implements Cloneable{//...@Overrideprotected Weapon clone() throws CloneNotSupportedException {return (Weapon)super.clone();}}
Monkey類:
public class Monkey implements Cloneable{//...@Overrideprotected Monkey clone() throws CloneNotSupportedException {Monkey clone= (Monkey)super.clone();clone.weapon=this.weapon.clone();return clone;}}
**思考:**如果要深克隆,必須重寫clone方法,如果克隆對象依賴對象的層級嵌套一多,代碼較復雜。
4.定義和組成結構
? 原型模式(Prototype):從一個對象創建一個可定制的對象,而不需要知道任何創建細節。
? 原型模式包含以下主要角色。
- 抽象原型類(Prototype):規定了具體原型對象必須實現的接口。
- 具體原型類(ConcretePrototype):實現抽象原型類的 clone() 方法,它是可被復制的對象。
- 訪問類(Acess):使用具體原型類中的 clone() 方法來復制新的對象。
5.優缺點以及應用場景
優點:
- 通過克隆一個已有的對象,簡化對象的創建過程,不用關注對象的內部創建細節,符合迪米特法則
- 流的方式比new一個對象克隆對象效率更高
缺點:
- 克隆對象類必須要重寫clone方法
- 如果克隆對象依賴對象的嵌套層級較多,并且要達到深克隆,代碼較復雜
- clone 方法位于類的內部,當對已有類進行改造的時候,可能需要修改代碼,違背了開閉原則
適用場景:
- 保存對象的狀態并且對象占用內存較少
- 對象創建成本高,耗用的資源比較多
- 對象初始化復雜
你的收藏和點贊就是我最大的創作動力,關注我我會持續輸出更新!
友情提示:請尊重作者勞動成果,如需轉載本博客文章請注明出處!謝謝合作!
【作者:我愛吃菠菜 】