Java日記之設(shè)計(jì)模式總結(jié)(創(chuàng)建型)

前言

推薦看這篇文章之前先了解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來防止重排序就缆。


添加volatile關(guān)鍵字后的線程執(zhí)行順序帖渠,侵刪

另一個就是不允許讓其它線程看到這個“重排序”,也就是下一個要講的靜態(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);
        
    }

}

序列化破壞結(jié)果

你會發(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;
    }
}

readResolve()的反射結(jié)果



然而陶夜,破壞單例并不只有序列化和反序列化,其實(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);
    }
}

反射單例模式結(jié)果

這就很明顯的,學(xué)過Java反射的都知道宏胯,通過反射的setAccessible()把私有權(quán)限打開捂贿,然后通過newInstance()來創(chuàng)建一個新的實(shí)例,這樣全局就會有兩個不一樣的實(shí)例了胳嘲,反射這個基本沒辦法解決厂僧,但是枚舉單例模式進(jìn)行反射的話是會報錯的,Java不允許用反射來創(chuàng)建枚舉類型了牛。
反射創(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);

    }
}
簡單工廠的UML

最主要的簡單工廠實(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();
    }
}
工廠方法UML

其實(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)品困難产镐,需要修改抽象工廠的接口。也增加了理解難度踢步。

產(chǎn)品結(jié)構(gòu)圖1癣亚,圖來自慕課網(wǎng)實(shí)戰(zhàn)課程-Java設(shè)計(jì)模式精講 Debug方式+內(nèi)存分析,侵刪

產(chǎn)品結(jié)構(gòu)圖2获印,圖來自慕課網(wǎng)實(shí)戰(zhàn)課程-Java設(shè)計(jì)模式精講 Debug方式+內(nèi)存分析述雾,侵刪

以這個圖為例,從左到右的圖形依次代表兼丰,電視玻孟、冰箱和空調(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();
    }
}

抽象工廠UML

通過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);
    }
}
原型模式結(jié)果如圖

其實(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é)果如圖

剛剛也說過了盘榨,克隆出來的對象是新的一個對象,但是從運(yùn)行代碼結(jié)果發(fā)現(xiàn)當(dāng)我們給pig1也就是模板實(shí)例重新設(shè)置時間的時候敞映,會發(fā)現(xiàn)pig2的時間也一起被設(shè)置了较曼,明明就是兩個對象磷斧,接著我們Debug下振愿。
Debug結(jié)果

從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();
}

修改后的運(yùn)行結(jié)果如上圖

補(bǔ)充:克隆還有一個作用就是可以破壞單例模式嘹屯,因?yàn)?code>clone()返回的是新的對象,解決這個辦法也很簡單从撼,讓單例模式不去繼承Cloneable接口州弟,或者在重寫的clone()方法里直接返回唯一的那個單例對象就好。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市婆翔,隨后出現(xiàn)的幾起案子拯杠,更是在濱河造成了極大的恐慌,老刑警劉巖啃奴,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潭陪,死亡現(xiàn)場離奇詭異,居然都是意外死亡纺腊,警方通過查閱死者的電腦和手機(jī)畔咧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揖膜,“玉大人誓沸,你說我怎么就攤上這事∫妓冢” “怎么了拜隧?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趁仙。 經(jīng)常有香客問我洪添,道長,這世上最難降的妖魔是什么雀费? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任干奢,我火速辦了婚禮,結(jié)果婚禮上盏袄,老公的妹妹穿的比我還像新娘忿峻。我一直安慰自己,他們只是感情好辕羽,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布逛尚。 她就那樣靜靜地躺著,像睡著了一般刁愿。 火紅的嫁衣襯著肌膚如雪绰寞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天铣口,我揣著相機(jī)與錄音滤钱,去河邊找鬼。 笑死脑题,一個胖子當(dāng)著我的面吹牛件缸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旭蠕,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼停团,長吁一口氣:“原來是場噩夢啊……” “哼旷坦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起佑稠,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤秒梅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舌胶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捆蜀,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年幔嫂,在試婚紗的時候發(fā)現(xiàn)自己被綠了辆它。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡履恩,死狀恐怖锰茉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情切心,我是刑警寧澤飒筑,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站绽昏,受9級特大地震影響协屡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜全谤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一肤晓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧认然,春花似錦补憾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卷胯。三九已至子刮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窑睁,已是汗流浹背挺峡。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留担钮,地道東北人橱赠。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像箫津,于是被迫代替她去往敵國和親狭姨。 傳聞我的和親對象是個殘疾皇子宰啦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容