前言
推薦看這篇文章之前先了解Java日記之設(shè)計(jì)模式初探霎冯。
- 創(chuàng)建型設(shè)計(jì)模式總共有5種
1.單例模式
2.工廠方法模式
3.抽象工廠模式
4.建造者模式
5.原型模式
1. 單例模式
定義:保證一個類只有一個實(shí)例,并提供一個全局的訪問點(diǎn)泰偿。就是整個某些功能在整個應(yīng)用里面不需要實(shí)例化多次,只需要一個就可以了秽荤,并且可以在任何時候訪問甜奄,這就是單例模式,它是我們平常開發(fā)中最常用的模式窃款,其實(shí)也可以說是最復(fù)雜的模式之一了课兄,不是說有多難理解,是因?yàn)閱卫J接卸喾N的寫法晨继,適應(yīng)不同的場景烟阐。
適用場景:想確保任何場景情況下都絕對只有一個實(shí)例。
優(yōu)點(diǎn):在內(nèi)存里只有一個實(shí)例紊扬,減少了內(nèi)存開銷蜒茄,可以避免對資源的多重占用。設(shè)置了全局的訪問點(diǎn)餐屎,嚴(yán)格控制訪問檀葛。
缺點(diǎn):沒有接口,拓展困難腹缩。
餓漢單例模式
public class HungrySingleton{
private static HungrySingleton hungrySingleton = new HungrySingleton();
//設(shè)置權(quán)限訪問這個一定要寫
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
在類加載的時候就完成了初始化屿聋,所以類加載的時候河馬,但是初始化就很快了藏鹊,這種方式是基于類加載的機(jī)制润讥,所也就不存在多線程的同步問題,在類加載的時候完成了實(shí)例化盘寡,缺點(diǎn)就是楚殿,如果始終沒有使用過這個實(shí)例的話,就會造成內(nèi)存的浪費(fèi)(基本沒有這種情況)竿痰。
懶漢單例模式
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
剛剛說的餓漢就是很形象的比喻類馬上加載的時候初始化這個實(shí)例脆粥,懶漢也很形象,類加載的時候沒有初始化菇曲,等需要獲取這個實(shí)例的時候就進(jìn)行初始化冠绢,這樣就不會造成浪費(fèi)了,但是也有一個很大的缺點(diǎn)常潮,在多線程的情況下是很有可能會創(chuàng)建出兩個實(shí)例弟胀,這就違背了單例設(shè)計(jì)模式的原則了,其實(shí)也是有辦法解決的喊式,這就需要雙重檢查單例模式了(DCL)孵户。
雙重檢查單例模式了(DCL)
public class DoubleCheckSingleton {
private DoubleCheckSingleton() {
}
//添加volatile關(guān)鍵字
private volatile static DoubleCheckSingleton doubleCheckSingleton = null;
public static DoubleCheckSingleton getInstance() {
if (doubleCheckSingleton == null) {
//同步synchronized字段
synchronized (DoubleCheckSingleton.class){
if (doubleCheckSingleton == null){
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
首先進(jìn)行兩次判空,第一次是為了不必要的同步岔留,第二次是在doubleCheckSingleton為null的情況下就會創(chuàng)建實(shí)例夏哭,volatile關(guān)鍵字主要是防止重排序。因?yàn)槿绻麤]有加volatile關(guān)鍵字的話很有可能會沒有完成初始化,我們看個例子献联。
public class DoubleCheckedLocking { //1
private static Instance instance; //2
public static Instance getInstance(){ //3
if(instance ==null) { //4:第一次檢查
synchronized (DoubleCheckedLocking.class) { //5:加鎖
if (instance == null) //6:第二次檢查
instance = new Instance(); //7:問題的根源處在這里
} //8
} //9
return instance; //10
} //11
}
我們把第7行的代碼分成3行偽代碼竖配。
memory=allocate(); //1:分配對象的內(nèi)存空間
ctorInstance(memory); //2:初始化對象
instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
這是正常初始化實(shí)例的順序何址,但是在多線程中很有可能會被重排序,就是順序被打亂进胯。
memory=allocate(); //1:分配對象的內(nèi)存空間
instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
//注意用爪,此時對象還沒有被初始化!
ctorInstance(memory); //2:初始化對象
由于單線程內(nèi)要遵守intra-thread semantics,從而能保證A線程的執(zhí)行結(jié)果不會被改變偎血。但是,當(dāng)線程A和B按上圖時序執(zhí)行時盯漂,B線程將看到一個還沒有被初始化的對象颇玷。而解決方式主要是有兩個,一個就是剛剛添加volatile來防止重排序就缆。
另一個就是不允許讓其它線程看到這個“重排序”,也就是下一個要講的靜態(tài)內(nèi)部單例模式竭宰。
靜態(tài)內(nèi)部單例模式
public class StaticInnerClassSingleton {
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
}
JVM在類的初始化階段(即在Class被加載后阿弃,且被線程使用之前),會執(zhí)行類的初始化羞延。在執(zhí)行類的初始化期間渣淳,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化伴箩。這種寫法第一次是不會加載實(shí)例的入愧,只有第一次調(diào)用getInstance()
才會去調(diào)用,又能保證線程安全嗤谚,也能保證唯一性棺蛛。
枚舉單例模式
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
它是《Effective Java》這本書里面推薦用的單例模式,它是線程安全的巩步,在任何情況下都是單例旁赊,這與它是Enum有關(guān),至于為什么椅野,我們接下來會講到單例模式的破壞就會說到终畅。
容器單例模式
public class ContainerSingleton {
private static Map<String,Object> stringObjectMap = new HashMap<>();
public static void putInstance(String key,Object instance){
if (!stringObjectMap.containsKey(key)){
stringObjectMap.put(key,instance);
}
}
public static Object getInstance(String key){
return stringObjectMap.get(key);
}
}
用stringObjectMap 將多種的單例類統(tǒng)一管理,在使用時根據(jù)key獲取對象對應(yīng)類型的對象竟闪。這種方式使得我們可以管理多種類型的單例离福,并且在使用時可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了用戶的使用成本炼蛤,也對用戶隱藏了具體實(shí)現(xiàn)妖爷,降低了耦合度。
線程單例模式
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance>
threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>() {
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance() {
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
這個單例嚴(yán)格來說不能算是單例理朋,因?yàn)樗荒鼙WC全局只有一個實(shí)例絮识,但是可以保證每個線程只有這一個實(shí)例唯一绿聘,這也跟ThreadLocal的特性有關(guān),詳細(xì)可以看Android日記之消息機(jī)制(2)
次舌。
單例模式的破壞
在面試中斜友,光熟悉單例模式的話,其他大家也都會垃它,但是要是能說出一些特別的東西,這就代表有很深入的了解單例模式烹看,單例模式的破壞就是一個很特別的地方国拇,我們都知道,單例模式全局就只能有一個實(shí)例惯殊,但是我們是可以通過一些其他的方式來破壞這個單例的酱吝,比如通過序列化和反序列化,我們用餓漢模式來測試土思。
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//以餓漢模式來舉例
HungrySingleton instance = HungrySingleton.getInstance();
//序列化寫入實(shí)例
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//反序列化讀出實(shí)例
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
//進(jìn)行判斷
System.out.println(instance == newInstance);
}
}
你會發(fā)現(xiàn)兩個對象是不同的务热,這是因?yàn)榉瓷錂C(jī)制佛舱,它反序列化的對象的時候脊僚,也是通過反射的方式用無參的構(gòu)造方法構(gòu)造了一個新的實(shí)例绸罗,主要的邏輯都是在
readOrdinaryObject()
里面實(shí)現(xiàn)的山孔。那有辦法解決這個問題嗎眼虱?其實(shí)反序列化操作提供了readResolve()
方法遍膜,它可以控制對象的反序列化捷雕,這個方法也是通過反射實(shí)現(xiàn)的叁鉴,所以是沒辦法Override的途样,只要名字不會錯江醇,就會通過反射查找的到,然后直接返回實(shí)例對象就好何暇。
public class HungrySingleton implements Serializable {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
//通過反射查找
private Object readResolve(){
return hungrySingleton;
}
}
然而陶夜,破壞單例并不只有序列化和反序列化,其實(shí)也能通過反射來進(jìn)行破壞裆站,我們還是用餓漢來測試条辟。
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
//通過反射把構(gòu)造權(quán)限打開
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
//通過反射構(gòu)造方法來新建實(shí)例
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
這就很明顯的,學(xué)過Java反射的都知道宏胯,通過反射的
setAccessible()
把私有權(quán)限打開捂贿,然后通過newInstance()
來創(chuàng)建一個新的實(shí)例,這樣全局就會有兩個不一樣的實(shí)例了胳嘲,反射這個基本沒辦法解決厂僧,但是枚舉單例模式進(jìn)行反射的話是會報錯的,Java不允許用反射來創(chuàng)建枚舉類型了牛。2.工廠方法模式
簡單工廠模式
講工廠方法模式之前我們就先來講講簡單工廠模式颜屠,它也是屬于創(chuàng)建型模式辰妙,但是并不屬于23種設(shè)計(jì)模式里面,提到它是為了能夠更好的理解講到的工廠方法模式甫窟。
定義:由一個工廠對象決定創(chuàng)建出哪一種蟾皮類型的實(shí)例密浑。
適用場景:工廠類負(fù)責(zé)創(chuàng)建的對象比較少〈志客戶端(應(yīng)用層)只知道傳入工廠的參數(shù)就好尔破,對于具體如何創(chuàng)建對象則不需要關(guān)心。
優(yōu)點(diǎn):只需要傳入一個正確的參數(shù)浇衬,就可以獲取你說需要的對象懒构,而無需知道具體的創(chuàng)建細(xì)節(jié)。
缺點(diǎn):工廠類的職責(zé)相對較重耘擂,增加新的產(chǎn)品胆剧,就要修改工廠類的業(yè)務(wù)邏輯,違背開閉原則醉冤。
代碼舉例:
//接口或者抽象類都可以
public abstract class Video {
public abstract void produce();
}
//實(shí)現(xiàn)類
public class JavaVideo extends Video{
@Override
public void produce() {
System.out.println("錄制java課程視頻");
}
}
public class PythonVideo extends Video{
@Override
public void produce() {
System.out.println("錄制Python視頻");
}
}
//工廠類秩霍,這是重點(diǎn)
public class VideoFactory {
public Video getVideo(Class c) {
Video video = null;
try {
video = (Video) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return video;
}
public Video getVideo(String type) {
if ("java".equalsIgnoreCase(type)) {
return new JavaVideo();
} else if ("python".equalsIgnoreCase(type)) {
return new PythonVideo();
}
return null;
}
}
//測試類
public class Test {
public static void main(String[] args) {
//傳入相關(guān)參數(shù)獲得
VideoFactory factory1 = new VideoFactory();
factory1.getVideo("java");
//傳入具體實(shí)現(xiàn)類獲得
VideoFactory factory2 = new VideoFactory();
Video video = factory2.getVideo(PythonVideo.class);
}
}
最主要的簡單工廠實(shí)現(xiàn)就是在VideoFactory這個類里面,通過getVideo()
方法傳入具體的參數(shù)蚁阳,就可以直接獲得你想要的類了铃绒,擔(dān)任也可以通過反射來實(shí)現(xiàn)相關(guān)并獲得具體的類。
看完這個我們接下來就來看具體的工廠方法模式什么樣的螺捐。
定義:定義一個創(chuàng)建對象的接口匿垄,但讓實(shí)現(xiàn)這個接口的類來決定實(shí)例化哪個類,工廠方法類讓類的實(shí)例化推遲到子類進(jìn)行归粉。
適用場景:創(chuàng)建對象需要大量重復(fù)的代碼椿疗,客戶端(應(yīng)用層)不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、實(shí)現(xiàn)等細(xì)節(jié)糠悼。一個類通過其子類來指定創(chuàng)建哪個對象届榄。
優(yōu)點(diǎn):用戶只需關(guān)心所需產(chǎn)品對應(yīng)的工廠,無需關(guān)心創(chuàng)建細(xì)節(jié)倔喂,以及加入新產(chǎn)品符合開閉原則铝条,提高擴(kuò)張性。
缺點(diǎn):類的個數(shù)容易過多席噩,增加復(fù)雜度班缰。還有增加了系統(tǒng)的抽象和理解難度。
代碼舉例:
//定義接口或者類都可以
public abstract class Video {
public abstract void produce();
}
//實(shí)現(xiàn)的類
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("錄制java課程視頻");
}
}
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("錄制Python視頻");
}
}
//工廠方法的接口悼枢,
public abstract class VideoFactory {
public abstract Video getVideo();
}
//實(shí)現(xiàn)工廠接口的類
public class JavaVideoFactory extends VideoFactory{
@Override
public Video getVideo() {
return new JavaVideo();
}
}
public class PythonVideoFactory extends VideoFactory{
@Override
public Video getVideo() {
return new PythonVideo();
}
}
//測試類
public class Test {
public static void main(String[] args) {
VideoFactory videoFactory = new PythonVideoFactory();
videoFactory.getVideo().produce();
}
}
其實(shí)相比于簡單工廠埠忘,主要就是在VideoFactory這個類進(jìn)行了修改,可以把這個類改成抽象類或者接口都是可以的,這個類就是工廠的公共類莹妒,然后在創(chuàng)建具體的工廠來進(jìn)行實(shí)現(xiàn)名船,比如JavaVideoFactory或者PythonVideoFactory 這些都是。最后通過工廠類的方法來創(chuàng)建實(shí)例旨怠。
3.抽象工廠模式
定義:抽象工廠模式提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口渠驼,而且無需指定它們具體的類。
適用場景:客戶端不依賴于產(chǎn)品實(shí)例如何創(chuàng)建鉴腻、實(shí)現(xiàn)等細(xì)節(jié)迷扇。提供一個產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn)爽哎,從而使客戶端不依賴于具體實(shí)現(xiàn)蜓席。
優(yōu)點(diǎn):具體產(chǎn)品在應(yīng)用層隔離,無需關(guān)心具體細(xì)節(jié)倦青,將一個系列的產(chǎn)品族統(tǒng)一創(chuàng)建。
缺點(diǎn):規(guī)定了所有可能被創(chuàng)建的產(chǎn)品集合盹舞,產(chǎn)品族中擴(kuò)展新的產(chǎn)品困難产镐,需要修改抽象工廠的接口。也增加了理解難度踢步。
以這個圖為例,從左到右的圖形依次代表兼丰,電視玻孟、冰箱和空調(diào)。工廠方法模式針對的就是產(chǎn)品等級結(jié)構(gòu)鳍征,比如說工廠生產(chǎn)出來各種品牌的電視黍翎,海爾電視,美的電視等等艳丛,如果需要冰箱或者空調(diào)匣掸,則又需要一個工廠接口。而抽象工廠針對的就是一個產(chǎn)品族氮双,比如這個工廠里面可以生產(chǎn)同一個牌子的各種型號的東西碰酝,比如海爾電視,海爾冰箱等等戴差。
代碼舉例:
//定義兩個產(chǎn)品產(chǎn)品族送爸,比如電視和冰箱
public abstract class Article {
public abstract void produce();
}
public abstract class Video {
public abstract void produce();
}
//Article實(shí)現(xiàn)類
public class JavaArticle extends Article{
@Override
public void produce() {
System.out.println("編寫java課程手記");
}
}
public class PythonArticle extends Article{
@Override
public void produce() {
System.out.println("編寫Python課程手記");
}
}
//Video實(shí)現(xiàn)類
public class JavaVideo extends Video{
@Override
public void produce() {
System.out.println("錄制java視頻");
}
}
public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("錄制Python視頻");
}
}
//工廠接口
public interface CourseFactory {
Video getVideo();
Article getArticle();
}
//具體實(shí)現(xiàn)的工廠類
public class JavaCourseFactory implements CourseFactory{
@Override
public Video getVideo() {
return new JavaVideo();
}
@Override
public Article getArticle() {
return new JavaArticle();
}
}
public class PythonCourseFactory implements CourseFactory {
@Override
public Video getVideo() {
return new PythonVideo();
}
@Override
public Article getArticle() {
return new PythonArticle();
}
}
//測試類
public class Test {
public static void main(String[] args) {
CourseFactory courseFactory = new JavaCourseFactory();
Video video = courseFactory.getVideo();
Article article = courseFactory.getArticle();
video.produce();
article.produce();
}
}
通過CourseFactory來創(chuàng)建你要的哪種的類型語言(也可以說你要哪種品牌),在然后通過工廠里的具體方法來獲得你要的語言的課程還是手賬(類比你要獲取這個品牌的冰箱還是電視)。如果還需要其他型號的東西碱璃,就直接在創(chuàng)建一個產(chǎn)品類型的接口就好了弄痹。而且應(yīng)用層的創(chuàng)建也不用知道具體的創(chuàng)建細(xì)節(jié)是什么。
4.建造者模式
定義:將一個復(fù)雜的對象的構(gòu)建與它的表示分離嵌器,使的同樣的的構(gòu)建過程可以創(chuàng)建不同的表示肛真,用戶只需指定需要創(chuàng)建的類型就可以獲取到他們,建造細(xì)節(jié)不需要知道爽航。
適用場景:如果一個對象有非常復(fù)雜的內(nèi)部結(jié)構(gòu)(比如要設(shè)置很多屬性)蚓让。想把復(fù)雜對象的創(chuàng)建和使用分離。
優(yōu)點(diǎn):封裝性好讥珍,創(chuàng)建和使用分離历极,擴(kuò)展性好,建造類之間相互獨(dú)立衷佃,一定程度上解耦趟卸。
缺點(diǎn):會產(chǎn)生多余的Builder對象,產(chǎn)品內(nèi)部發(fā)生變化氏义,建造者就需要修改锄列,成本很大。
代碼舉例:
建造者模式的寫法其實(shí)挺多種惯悠,我這里舉例最常用的一種寫法來演示邻邮。
//具體的實(shí)現(xiàn)類,具體的業(yè)務(wù)邏輯寫在這個類里面
public class Course {
private String courseName;
private String coursePPT;
private String courseVideo;
private String courseArticle;
private String courseQA;
public Course(String courseName, String coursePPT, String courseVideo, String courseArticle, String courseQA) {
this.courseName = courseName;
this.coursePPT = coursePPT;
this.courseVideo = courseVideo;
this.courseArticle = courseArticle;
this.courseQA = courseQA;
}
public static CourseBuilder builder() {
return new CourseBuilder();
}
public void get() {
System.out.println("發(fā)送");
}
@Override
public String toString() {
return "Course{" +
"courseName='" + courseName + '\'' +
", coursePPT='" + coursePPT + '\'' +
", courseVideo='" + courseVideo + '\'' +
", courseArticle='" + courseArticle + '\'' +
", courseQA='" + courseQA + '\'' +
'}';
}
}
//要建造的Builder類
public class CourseBuilder {
private String courseName;
private String coursePPT;
private String courseVideo;
private String courseArticle;
private String courseQA;
public CourseBuilder buildCourseName(String courseName){
this.courseName = courseName;
return this;
}
public CourseBuilder buildCoursePPT(String coursePPT){
this.coursePPT = coursePPT;
return this;
}
public CourseBuilder buildCourseVideo(String courseVideo){
this.courseVideo = courseVideo;
return this;
}
public CourseBuilder buildCourseArticle(String courseArticle){
this.courseArticle = courseArticle;
return this;
}
public CourseBuilder buildCourseQA(String courseQA){
this.courseQA = courseQA;
return this;
}
public Course build(){
return new Course(courseName,coursePPT,courseVideo,courseArticle,courseQA);
}
}
//測試類
public class Test {
public static void main(String[] args) {
Course course = Course.builder()
.buildCourseArticle("")
.build();
course.get();
}
}
通過builder()
方法來new一個Builder類克婶,然后在這個類里面進(jìn)行對象的屬性設(shè)置等等參數(shù)筒严,注意設(shè)置屬性的方法要返回本身的對象,這樣就可以實(shí)現(xiàn)鏈?zhǔn)降恼{(diào)用情萤,最后通過build()
返回要創(chuàng)建的對象鸭蛙,這樣子的創(chuàng)建方式也解決了一定程度上的解耦。
5.原型模式
定義:原型實(shí)例指定創(chuàng)建對象的種類筋岛,并且通過拷貝這些原型創(chuàng)建新的對象规惰,特點(diǎn)就是不需要知道任何創(chuàng)建的細(xì)節(jié),不調(diào)用構(gòu)造函數(shù)泉蝌。
適用場景:類初始化消耗較多資源歇万,new產(chǎn)生一個對象需要非常繁瑣的過程(數(shù)據(jù)準(zhǔn)備,訪問權(quán)限等)勋陪,還有構(gòu)造函數(shù)比較復(fù)雜贪磺,循環(huán)體生產(chǎn)大量對象的時候。
優(yōu)點(diǎn):原型模式性能比直接new一個對象性能高诅愚,還有就是可以簡化創(chuàng)建過程寒锚。
缺點(diǎn):必須配備克隆方法劫映,如果沒有重寫Object的克隆方法,那也就不會生效刹前。對克隆復(fù)雜對象或?qū)寺〕龅膶ο筮M(jìn)行復(fù)雜改造的時候泳赋,容易引出風(fēng)險。
代碼舉例:
//繼承克隆接口喇喉,重寫克隆方法
public class Mail implements Cloneable{
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("Mail Class Constructor");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}' + super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
}
public class MailUtil {
public static void sendMail(Mail mail) {
String outPutCOntent = "向{0}同學(xué)祖今,郵件地址:{1},郵件內(nèi)容:{2}拣技,發(fā)送郵件成功";
System.out.println(MessageFormat.format(outPutCOntent, mail.getName(), mail.getEmailAddress(), mail.getContent()));
}
public static void saveOriginMailRecord(Mail mail) {
System.out.println("存儲originMail的記錄千诬,originMail:" + mail.getContent());
}
}
//測試類
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("初始化模板");
System.out.println("初始化Mail:" + mail);
for (int i = 0; i < 5; i++) {
Mail mail1Temp = (Mail) mail.clone();
mail1Temp.setName("姓名" + i);
mail1Temp.setEmailAddress("姓名" + i + "@ju.com");
mail1Temp.setContent("恭喜您中獎");
MailUtil.sendMail(mail1Temp);
System.out.println("克隆的mailTemp:" + mail1Temp);
}
MailUtil.saveOriginMailRecord(mail);
}
}
其實(shí)原型模式很簡單,主要是繼承Cloneable這個接口膏斤,然后重寫clone()
方法來獲得你需要克隆的內(nèi)容徐绑,這里注意的是克隆出來的對象是新的對象,兩個對象是不一樣的莫辨,看運(yùn)行結(jié)果我們也可以看的出來傲茄。
淺克隆和深克隆
雖然原型模式很簡單,但是坑還是有很多的沮榜,比如看接下來的一個例子
package main.java.design.pattern.creational.prototype.clone;
import java.util.Date;
public class Pig implements Cloneable{
private String name;
private Date birthday;
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+ super.toString();
}
}
//測試類
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date brithday = new Date(0L);
Pig pig1 = new Pig("佩奇",brithday);
Pig pig2 = (Pig) pig1.clone();
System.out.println(pig1);
System.out.println(pig2);
pig1.getBirthday().setTime(6666666666L);
System.out.println(pig1);
System.out.println(pig2);
}
}
剛剛也說過了盘榨,克隆出來的對象是新的一個對象,但是從運(yùn)行代碼結(jié)果發(fā)現(xiàn)當(dāng)我們給pig1也就是模板實(shí)例重新設(shè)置時間的時候敞映,會發(fā)現(xiàn)pig2的時間也一起被設(shè)置了较曼,明明就是兩個對象磷斧,接著我們Debug下振愿。
從Debug結(jié)果可以看到,其實(shí)pig對象是克隆的兩個對象弛饭,但是它們里面的Date對象還是使用同一個引用冕末,這就是一個小坑,只克隆了外表的對象侣颂,里面的引用對象是沒有被一起克隆出的新的档桃,這就可以成為原型模式的淺克隆,解決這個問題憔晒,需要修改下pig的
clone()
方法藻肄,修改后就可以實(shí)現(xiàn)內(nèi)部對象的克隆,也可以稱為深克隆拒担。
//進(jìn)行如下修改
@Override
protected Object clone() throws CloneNotSupportedException {
Pig pig = (Pig) super.clone();
//深克隆
pig.birthday = (Date) pig.birthday.clone();
return pig;
// return super.clone();
}
補(bǔ)充:克隆還有一個作用就是可以破壞單例模式嘹屯,因?yàn)?code>clone()返回的是新的對象,解決這個辦法也很簡單从撼,讓單例模式不去繼承Cloneable接口州弟,或者在重寫的
clone()
方法里直接返回唯一的那個單例對象就好。
參考
- Java設(shè)計(jì)模式精講 Debug方式+內(nèi)存分析
- [劉望舒]Android進(jìn)階之光