設(shè)計(jì)模式--建造者模式

目錄

本文的結(jié)構(gòu)如下:

  • 引言
  • 什么是建造者模式
  • 模式的結(jié)構(gòu)
  • 典型代碼
  • 代碼示例
  • Builder模式變種
  • 建造者模式與抽象工廠模式
  • 優(yōu)點(diǎn)和缺點(diǎn)
  • 適用環(huán)境
  • 模式應(yīng)用

一旭从、引言

玩過(guò)游戲的應(yīng)該清楚,游戲中有很多角色,這些角色往往都有不同的外形片任,不同的能量值,不同的服飾,不同的武器等等,所以要?jiǎng)?chuàng)造一個(gè)游戲角色垛孔,就要?jiǎng)?chuàng)造構(gòu)成這個(gè)游戲角色不同組成;再比如電腦施敢,電腦是由CPU周荐,主板,內(nèi)存僵娃,顯示器等組成的概作,電腦生廠商需要用這些部件組裝成一部完整的電腦。

在軟件開發(fā)中默怨,創(chuàng)建一個(gè)由多個(gè)部件組成的復(fù)雜產(chǎn)品讯榕,往往使用建造者模式。

二匙睹、什么是建造者模式

建造者模式又稱為生成器模式愚屁,它是一種較為復(fù)雜、使用頻率也相對(duì)較低的創(chuàng)建型模式痕檬,它將客戶端與包含多個(gè)組成部分(或部件)的復(fù)雜對(duì)象的創(chuàng)建過(guò)程分離霎槐。

建造者模式關(guān)注如何一步一步創(chuàng)建一個(gè)的復(fù)雜對(duì)象,不同的具體建造者定義了不同的創(chuàng)建過(guò)程梦谜,且具體建造者相互獨(dú)立丘跌,增加新的建造者非常方便袭景,無(wú)須修改已有代碼,系統(tǒng)具有較好的擴(kuò)展性碍岔。

建造者模式定義如下:

建造者模式(Builder Pattern):將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離浴讯,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示朵夏。建造者模式是一種對(duì)象創(chuàng)建型模式蔼啦。

建造者模式一步一步創(chuàng)建一個(gè)復(fù)雜的對(duì)象,它允許用戶只通過(guò)指定復(fù)雜對(duì)象的類型和內(nèi)容就可以構(gòu)建它們仰猖,用戶不需要知道內(nèi)部的具體構(gòu)建細(xì)節(jié)捏肢。

三、模式的結(jié)構(gòu)

建造者模式UML類圖如下:

image

在建造者模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Builder(抽象建造者):它為創(chuàng)建一個(gè)產(chǎn)品Product對(duì)象的各個(gè)部件指定抽象接口饥侵,在該接口中一般聲明兩類方法鸵赫,一類方法是buildPartX(),它們用于創(chuàng)建復(fù)雜對(duì)象的各個(gè)部件躏升;另一類方法是getResult()辩棒,它們用于返回復(fù)雜對(duì)象。Builder既可以是抽象類膨疏,也可以是接口一睁。
  • ConcreteBuilder(具體建造者):它實(shí)現(xiàn)了Builder接口,實(shí)現(xiàn)各個(gè)部件的具體構(gòu)造和裝配方法佃却,定義并明確它所創(chuàng)建的復(fù)雜對(duì)象者吁,也可以提供一個(gè)方法返回創(chuàng)建好的復(fù)雜產(chǎn)品對(duì)象。
  • Product(產(chǎn)品角色):它是被構(gòu)建的復(fù)雜對(duì)象饲帅,包含多個(gè)組成部件复凳,具體建造者創(chuàng)建該產(chǎn)品的內(nèi)部表示并定義它的裝配過(guò)程。
  • Director(指揮者):指揮者又稱為導(dǎo)演類灶泵,它負(fù)責(zé)安排復(fù)雜對(duì)象的建造次序育八,指揮者與抽象建造者之間存在關(guān)聯(lián)關(guān)系,可以在其construct()建造方法中調(diào)用建造者對(duì)象的部件構(gòu)造與裝配方法赦邻,完成復(fù)雜對(duì)象的建造单鹿。客戶端一般只需要與指揮者進(jìn)行交互深纲,在客戶端確定具體建造者的類型仲锄,并實(shí)例化具體建造者對(duì)象(也可以通過(guò)配置文件和反射機(jī)制),然后通過(guò)指揮者類的構(gòu)造函數(shù)或者Setter方法將該對(duì)象傳入指揮者類中湃鹊。

復(fù)雜對(duì)象是指那些包含多個(gè)成員屬性的對(duì)象儒喊,這些成員屬性也稱為部件或零件,如汽車包括方向盤币呵、發(fā)動(dòng)機(jī)怀愧、輪胎等部件侨颈,電子郵件包括發(fā)件人、收件人芯义、主題哈垢、內(nèi)容、附件等部件扛拨。

四耘分、典型代碼

復(fù)雜對(duì)象類典型代碼如下:

public class Product {
    //定義部件,部件可以是任意類型绑警,包括值類型和引用類型
    private  String partA;
    private  String partB;
    private  String partC;

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }
}

抽象建造者類定義了產(chǎn)品部件的創(chuàng)建方法和復(fù)雜產(chǎn)品返回方法求泰,典型代碼如下:

public abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();
    public Product getResult(){
        return product;
    }
}

在ConcreteBuilder中實(shí)現(xiàn)了buildPartX()方法,通過(guò)調(diào)用Product的setPartX()方法可以給產(chǎn)品對(duì)象的成員屬性設(shè)值计盒。不同的具體建造者在實(shí)現(xiàn)buildPartX()方法時(shí)將有所區(qū)別渴频,如setPartX()方法的參數(shù)可能不一樣,在有些具體建造者類中某些setPartX()方法無(wú)須實(shí)現(xiàn)(提供一個(gè)空實(shí)現(xiàn))北启。而這些對(duì)于客戶端來(lái)說(shuō)都無(wú)須關(guān)心卜朗,客戶端只需知道具體建造者類型即可。

public class ConcreteBuilder extends Builder {
    public void buildPartA() {
        product.setPartA("part A");
    }

    public void buildPartB() {
        product.setPartA("part B");
    }

    public void buildPartC() {
        product.setPartA("part C");
    }
}

建造者模式的結(jié)構(gòu)中還有一個(gè)指揮者類Director咕村,該類主要有兩個(gè)作用:一是它隔離了客戶與創(chuàng)建過(guò)程场钉;二是它控制產(chǎn)品的創(chuàng)建過(guò)程,包括某個(gè)buildPartX()方法是否被調(diào)用以及多個(gè)buildPartX()方法調(diào)用的先后次序等培廓。指揮者針對(duì)抽象建造者編程惹悄,客戶端只需要知道具體建造者的類型,即可通過(guò)指揮者類調(diào)用建造者的相關(guān)方法肩钠,返回一個(gè)完整的產(chǎn)品對(duì)象泣港。在實(shí)際生活中也存在類似指揮者一樣的角色,如一個(gè)客戶去購(gòu)買電腦价匠,電腦銷售人員相當(dāng)于指揮者当纱,只要客戶確定電腦的類型,電腦銷售人員可以通知電腦組裝人員給客戶組裝一臺(tái)電腦踩窖。指揮者類典型代碼如下:

public class Director {
    private Builder builder;

    public Director(Builder builder){
        this.builder = builder;
    }
    public void setBuilder(Builder builder){
        this.builder = builder;
    }

    public Product construct(){
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
        return builder.getResult();
    }
}

客戶端:

public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        Product product = director.construct();
    }
}

在客戶端代碼中坡氯,無(wú)須關(guān)心產(chǎn)品對(duì)象的具體組裝過(guò)程,只需指定具體建造者的類型即可洋腮◇锪可以通過(guò)配置文件來(lái)存儲(chǔ)具體建造者類ConcreteBuilder的類名,使得更換新的建造者時(shí)無(wú)須修改源代碼啥供,系統(tǒng)擴(kuò)展更為方便悯恍。

五、代碼示例

一款視頻播放軟件伙狐,為了給用戶使用提供方便涮毫,不同場(chǎng)景下有不同界面瞬欧,如完整界面、精簡(jiǎn)界面等罢防。不同的界面該播放器的組成元素有所差異艘虎,如在完整界面下將顯示菜單、播放列表咒吐、主窗口野建、控制條等,在精簡(jiǎn)界面下只顯示主窗口和控制條渤滞。

image

復(fù)雜對(duì)象VideoPlayer:

public class VideoPlayer {
    private String type;
    private String menu;
    private String playerList;
    private String mainWindows;
    private String controlStrip;

    public void setType(String type) {
        this.type = type;
    }

    public void setMenu(String menu) {
        this.menu = menu;
    }

    public void setPlayerList(String playerList) {
        this.playerList = playerList;
    }

    public void setMainWindows(String mainWindows) {
        this.mainWindows = mainWindows;
    }

    public void setControlStrip(String controlStrip) {
        this.controlStrip = controlStrip;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("這是一個(gè) ").append(type).append(" 視頻播放器\n\n");
        sb.append("該播放器擁有:\n");
        if (menu != null) sb.append(menu).append("\n");
        if (playerList != null) sb.append(playerList).append("\n");
        if (mainWindows != null) sb.append(mainWindows).append("\n");
        if (controlStrip != null) sb.append(controlStrip).append("\n");
        return sb.toString();
    }
}

抽象builder:

public abstract class PlayerBuilder {
    protected VideoPlayer player = new VideoPlayer();

    public abstract void buildType();
    public abstract void buildMenu();
    public abstract void buildPlayerList();
    public abstract void buildMainWindows();
    public abstract void buildControlStrip();

    public VideoPlayer getPlayer(){
        return player;
    }
}

具體builder:

public class FullPlayerBuilder extends PlayerBuilder {
    public void buildType() {
        player.setType("完整界面");
    }

    public void buildMenu() {
        player.setMenu("菜單");
    }

    public void buildPlayerList() {
        player.setPlayerList("播放列表");
    }

    public void buildMainWindows() {
        player.setMainWindows("主界面");
    }

    public void buildControlStrip() {
        player.setControlStrip("控制條");
    }
}

public class SimplePlayerBuilder extends PlayerBuilder {
    public void buildType() {
        player.setType("精簡(jiǎn)界面");
    }

    public void buildMenu() {
    }

    public void buildPlayerList() {
    }

    public void buildMainWindows() {
        player.setMainWindows("主界面");
    }

    public void buildControlStrip() {
        player.setControlStrip("控制條");
    }
}

指揮者BuilderDirector:

public class PlayerDirector {
    private PlayerBuilder builder;

    public PlayerDirector(PlayerBuilder builder){
        this.builder = builder;
    }

    public void setBuilder(PlayerBuilder builder){
        this.builder = builder;
    }

    public VideoPlayer construct(){
        builder.buildType();
        builder.buildMenu();
        builder.buildPlayerList();
        builder.buildMainWindows();
        builder.buildControlStrip();
        return builder.getPlayer();
    }
}

客戶端測(cè)試:

public class Client {
    public static void main(String[] args) {
        PlayerBuilder builder = new FullPlayerBuilder();
        PlayerDirector director = new PlayerDirector(builder);
        VideoPlayer player = director.construct();

        System.out.println(player);
    }
}

六贬墩、Builder模式變種

6.1榴嗅、省略Director

Director類在建造者模式中扮演十分重要的作用妄呕,它按一定的順序調(diào)用Builder的buildPartX()方法,像客戶端返回一個(gè)完整的產(chǎn)品嗽测。

但是在有些情況下绪励,為了簡(jiǎn)化系統(tǒng),我們可以將Director和抽象建造者Builder進(jìn)行合并唠粥。

如下:

public abstract class PlayerBuilder {
    protected VideoPlayer player = new VideoPlayer();

    public abstract void buildType();
    public abstract void buildMenu();
    public abstract void buildPlayerList();
    public abstract void buildMainWindows();
    public abstract void buildControlStrip();

    public VideoPlayer getPlayer(){
        buildType();
        buildMenu();
        buildPlayerList();
        buildMainWindows();
        buildControlStrip();
        return player;
    }
}

public class Client {
    public static void main(String[] args) {
        PlayerBuilder builder = new SimplePlayerBuilder();
        VideoPlayer player = builder.getPlayer();

        System.out.println(player);
    }
}

6.2疏魏、內(nèi)部Builder

內(nèi)部Builder用起來(lái)是鏈?zhǔn)秸{(diào)用的形式,在Effective Java中晤愧,第二條有提到遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器大莫,應(yīng)該還是比較熟悉的。

public class VideoPlayer {
    private final String type;
    private final String menu;
    private final String playerList;
    private final String mainWindows;
    private final String controlStrip;

    private VideoPlayer(Builder builder){
        type = builder.type;
        menu = builder.menu;
        playerList = builder.playerList;
        mainWindows = builder.mainWindows;
        controlStrip = builder.controlStrip;
    }

    public static class Builder{
        private String type;
        private String menu;
        private String playerList;
        private String mainWindows;
        private String controlStrip;

        public Builder setType(String type) {
            this.type = type;
            return this;
        }

        public Builder setMenu(String menu){
            this.menu = menu;
            return this;
        }

        public Builder setPlayerList(String playerList){
            this.playerList = playerList;
            return this;
        }

        public Builder setMainWindows(String mainWindows){
            this.mainWindows = mainWindows;
            return this;
        }

        public Builder setControlStrip(String controlStrip){
            this.controlStrip = controlStrip;
            return this;
        }

        public VideoPlayer build(){
            return new VideoPlayer(this);
        }
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("這是一個(gè) ").append(type).append(" 視頻播放器\n\n");
        sb.append("該播放器擁有:\n");
        if (menu != null) sb.append(menu).append("\n");
        if (playerList != null) sb.append(playerList).append("\n");
        if (mainWindows != null) sb.append(mainWindows).append("\n");
        if (controlStrip != null) sb.append(controlStrip).append("\n");
        return sb.toString();
    }
}

客戶端:

public class Client {
    public static void main(String[] args) {
        VideoPlayer player = new VideoPlayer.Builder()
                .setType("簡(jiǎn)單界面")
                .setMainWindows("主界面")
                .setControlStrip("控制條")
                .build();
        System.out.println(player);
    }
}

七官份、建造者模式與抽象工廠模式

建造者模式完成的事只厘,好像都可以通過(guò)抽象工廠模式完成,那么區(qū)別是什么舅巷?

  • 與建造者模式相比羔味,抽象工廠模式返回的是一系列相關(guān)的產(chǎn)品,這些產(chǎn)品位于不同的產(chǎn)品等級(jí)結(jié)構(gòu)钠右,構(gòu)成產(chǎn)品族赋元,而建造者模式返回的是一個(gè)組裝好的完整產(chǎn)品。抽象工廠模式更像一個(gè)汽車零件生產(chǎn)商飒房,生產(chǎn)不同品牌汽車的各種零件搁凸。而建造者模式更像一個(gè)汽車裝配廠,通過(guò)一系列零件的組裝狠毯,最終生產(chǎn)出的是一個(gè)完整的汽車护糖。
  • 抽象工廠模式中,客戶端需實(shí)例化工廠類垃你,然后通過(guò)工廠類獲取所需的產(chǎn)品對(duì)象椅文。而建造者模式更側(cè)重于將復(fù)雜的構(gòu)造對(duì)象的方法交給建造者去做喂很,而客戶端只需要通過(guò)指揮者就能創(chuàng)建一個(gè)完整的產(chǎn)品實(shí)例。

八皆刺、優(yōu)點(diǎn)和缺點(diǎn)

8.1少辣、優(yōu)點(diǎn)

建造者模式的主要優(yōu)點(diǎn)如下:

  • 在建造者模式中,客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié)羡蛾,將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過(guò)程解耦漓帅,使得相同的創(chuàng)建過(guò)程可以創(chuàng)建不同的產(chǎn)品對(duì)象。
  • 每一個(gè)具體建造者都相對(duì)獨(dú)立痴怨,而與其他的具體建造者無(wú)關(guān)忙干,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對(duì)象浪藻。由于指揮者類針對(duì)抽象建造者編程捐迫,增加新的具體建造者無(wú)須修改原有類庫(kù)的代碼,系統(tǒng)擴(kuò)展方便爱葵,符合“開閉原則”施戴。
  • 可以更加精細(xì)地控制產(chǎn)品的創(chuàng)建過(guò)程。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中萌丈,使得創(chuàng)建過(guò)程更加清晰赞哗,也更方便使用程序來(lái)控制創(chuàng)建過(guò)程。

8.2辆雾、缺點(diǎn)

建造者模式的主要缺點(diǎn)如下:

  • 建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn)肪笋,其組成部分相似,如果產(chǎn)品之間的差異性很大度迂,例如很多組成部分都不相同藤乙,不適合使用建造者模式,因此其使用范圍受到一定的限制英岭。
  • 如果產(chǎn)品的內(nèi)部變化復(fù)雜湾盒,可能會(huì)導(dǎo)致需要定義很多具體建造者類來(lái)實(shí)現(xiàn)這種變化,導(dǎo)致系統(tǒng)變得很龐大诅妹,增加系統(tǒng)的理解難度和運(yùn)行成本罚勾。

九、適用環(huán)境

在以下情況下可以考慮使用建造者模式:

  • 需要生成的產(chǎn)品對(duì)象有復(fù)雜的內(nèi)部結(jié)構(gòu)吭狡,這些產(chǎn)品對(duì)象通常包含多個(gè)成員屬性尖殃。
  • 需要生成的產(chǎn)品對(duì)象的屬性相互依賴,需要指定其生成順序划煮。
  • 對(duì)象的創(chuàng)建過(guò)程獨(dú)立于創(chuàng)建該對(duì)象的類送丰。在建造者模式中通過(guò)引入了指揮者類,將創(chuàng)建過(guò)程封裝在指揮者類中弛秋,而不在建造者類和客戶類中器躏。
  • 隔離復(fù)雜對(duì)象的創(chuàng)建和使用俐载,并使得相同的創(chuàng)建過(guò)程可以創(chuàng)建不同的產(chǎn)品。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末登失,一起剝皮案震驚了整個(gè)濱河市遏佣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揽浙,老刑警劉巖状婶,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異馅巷,居然都是意外死亡膛虫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門钓猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)稍刀,“玉大人,你說(shuō)我怎么就攤上這事逗噩〉衾觯” “怎么了跌榔?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵异雁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我僧须,道長(zhǎng)纲刀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任担平,我火速辦了婚禮示绊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暂论。我一直安慰自己面褐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布取胎。 她就那樣靜靜地躺著展哭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闻蛀。 梳的紋絲不亂的頭發(fā)上匪傍,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音觉痛,去河邊找鬼役衡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛薪棒,可吹牛的內(nèi)容都是我干的手蝎。 我是一名探鬼主播榕莺,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棵介!你這毒婦竟也來(lái)了帽撑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鞍时,失蹤者是張志新(化名)和其女友劉穎亏拉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆巍,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡及塘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锐极。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笙僚。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖灵再,靈堂內(nèi)的尸體忽然破棺而出肋层,到底是詐尸還是另有隱情,我是刑警寧澤翎迁,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布栋猖,位于F島的核電站,受9級(jí)特大地震影響汪榔,放射性物質(zhì)發(fā)生泄漏蒲拉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一痴腌、第九天 我趴在偏房一處隱蔽的房頂上張望雌团。 院中可真熱鬧,春花似錦士聪、人聲如沸锦援。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灵寺。三九已至,卻和暖如春懦胞,著一層夾襖步出監(jiān)牢的瞬間替久,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工躏尉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚯根,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像颅拦,于是被迫代替她去往敵國(guó)和親蒂誉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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