不學(xué)無數(shù)——組合模式

組合模式

在DebugMybatis的源碼時,在DynamicSqlSource.getBoundSql動態(tài)獲取sql的時候郁岩,Debug會發(fā)現(xiàn)相同的方法但是進(jìn)去的實現(xiàn)類卻不相同滔驶,不明白為什么會這樣春哨,于是上網(wǎng)查了資料說是運(yùn)用了組合的設(shè)計模式嗤形。

1. 數(shù)據(jù)結(jié)構(gòu)

聊組合模式為什么會聊到數(shù)據(jù)結(jié)構(gòu)呢屋吨?看到最后你應(yīng)該就會明白了

相信大家都知道數(shù)據(jù)結(jié)構(gòu)這門學(xué)科桑逝,在數(shù)據(jù)結(jié)構(gòu)中有樹這樣的概念棘劣,樹中會有根節(jié)點、葉子節(jié)點等等楞遏。樹狀的結(jié)構(gòu)在現(xiàn)實生活中應(yīng)用廣泛茬暇,例如我們熟知的XML格式就是一個樹形的結(jié)構(gòu)

說個簡單的例子首昔,在我們身邊常見的,公司的人事管理就是一個典型的樹形結(jié)構(gòu)糙俗。

普遍的公司組織架構(gòu)

根據(jù)這個樹形結(jié)構(gòu)沙廉,我們可以抽象出來兩種不同性質(zhì)的點:

  • 有分支的點

    1. 根節(jié)點
    2. 樹枝節(jié)點
  • 無分支的點:葉子節(jié)點

因此按照我們的思路走下去的,那么可以簡單的抽象出三個接口臼节。

數(shù)據(jù)結(jié)構(gòu)類圖

這是最直接能夠想到的類圖表示信息撬陵,但是這個類圖信息目前表示是有些問題的,如果你已經(jīng)看出來這個類圖的缺陷的話网缝,那么這一小部分就可以一目十行跳讀過去了巨税。首先我們先寫出三個接口的代碼:

--根節(jié)點
interface IRoot{
     //得到總經(jīng)理的信息
    String getInfo();
    //根節(jié)點下添加節(jié)點,例如總經(jīng)理下面添加研發(fā)部經(jīng)理
    void add(IBranch branch);
    //根節(jié)點下添加葉子節(jié)點粉臊,比如添加秘書
    void add(ILeaf leaf);
    //遍歷手下所有人的信息
    List getSubordinateInfo();
}
--樹枝節(jié)點草添,信息同上
interface IBranch{
    String getInfo();
    void add(IBranch branch);
    void add(ILeaf leaf);
    List getSubordinateInfo();
}
--葉子節(jié)點,因為葉子節(jié)點已經(jīng)是最底層的了扼仲,所以不能增加任何信息远寸,只能獲得自身的信息
interface ILeaf{
    String getInfo();
}

然后看下IRoot的實現(xiàn)類

class Root implements IRoot{
     //保存根節(jié)點下的子節(jié)點信息
    private List subordinateList=new ArrayList();
    //節(jié)點名稱
    private String name;
    //節(jié)點的薪資
    private Integer salary;
    //節(jié)點的職位
    private String position;

    public Root(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名稱: "+this.name;
        info = info + " 職位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return null;
    }
    //增加樹枝節(jié)點
    @Override
    public void add(IBranch branch) {
        subordinateList.add(branch);
    }
    //增加子節(jié)點
    @Override
    public void add(ILeaf leaf) {
        subordinateList.add(leaf);
    }
    //得到下級的所有信息
    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

樹枝節(jié)點Branch和葉子節(jié)點Leaf的實現(xiàn)和Root的實現(xiàn)方式一樣,這里就不一一展示了屠凶。然后我們所有的節(jié)點信息都寫完了驰后,最后我們的工作就是進(jìn)行組裝成一個樹狀結(jié)構(gòu)并且遍歷這棵樹。代碼如下

public static void main(String[] args) {
        //根節(jié)點
        IRoot ceo = new Root("王大麻子",100000,"總經(jīng)理");
        //部門經(jīng)理,樹枝節(jié)點
        IBranch developCeo = new Branch("劉大瘸子",50000,"研發(fā)部經(jīng)理");
        IBranch saleCeo = new Branch("馬二愣子",50000,"銷售部經(jīng)理");
        IBranch finaceCeo = new Branch("趙三駝子",50000,"財務(wù)部經(jīng)理");
        //組長,樹枝節(jié)點
        IBranch developOne = new Branch("吳大棒槌",20000,"研發(fā)一組組長");
        IBranch developTwo = new Branch("鄭老六",20000,"研發(fā)二組組長");
        //員工,葉子節(jié)點
        ILeaf a = new Leaf("開發(fā)人員A",1000,"開發(fā)");
        ILeaf b = new Leaf("開發(fā)人員B",1000,"開發(fā)");
        ILeaf c = new Leaf("開發(fā)人員C",1000,"開發(fā)");
        ILeaf d = new Leaf("開發(fā)人員D",1000,"開發(fā)");
        ILeaf e = new Leaf("開發(fā)人員E",1000,"開發(fā)");
        ILeaf f = new Leaf("開發(fā)人員F",1000,"開發(fā)");
        ILeaf g = new Leaf("銷售人員G",1000,"銷售");
        ILeaf h = new Leaf("銷售人員H",1000,"銷售");
        ILeaf i = new Leaf("財務(wù)人員I",1000,"財務(wù)");
        ILeaf j = new Leaf("秘書J",1000,"秘書");
        //進(jìn)行組裝這個組織架構(gòu)
        //總經(jīng)理下的三大得力干將
        ceo.add(developCeo);
        ceo.add(saleCeo);
        ceo.add(finaceCeo);
        //總經(jīng)理下的秘書
        ceo.add(j);
        //研發(fā)部經(jīng)理下的組長
        developCeo.add(developOne);
        developCeo.add(developTwo);
        //銷售部經(jīng)理下的員工
        saleCeo.add(g);
        saleCeo.add(h);
        //財務(wù)部經(jīng)理下的員工
        finaceCeo.add(i);
        //研發(fā)一組下的員工
        developOne.add(a);
        developOne.add(b);
        developOne.add(c);
        //研發(fā)二組下的員工
        developTwo.add(d);
        developTwo.add(e);
        developTwo.add(f);
        System.out.println(ceo.getInfo());
        //遍歷總經(jīng)理下的所有信息
        getAllSubordinateInfo(ceo.getSubordinateInfo());
    }

    public static void getAllSubordinateInfo(List subordinateList){
        for (int i = 0; i < subordinateList.size(); i++) {
            Object object = subordinateList.get(i);
            if ( object instanceof ILeaf){
                ILeaf leaf = (ILeaf) object;
                System.out.println(leaf.getInfo());
            }
            else {
                IBranch branch = (IBranch) object;
                System.out.println(branch.getInfo());
                //遞歸調(diào)用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }

這樣我們就得到了我們想要的樹形結(jié)構(gòu)矗愧,打印信息如下

名稱: 王大麻子 職位是: 總經(jīng)理 薪水是: 100000
名稱: 劉大瘸子 職位是: 研發(fā)部經(jīng)理 薪水是: 50000
名稱: 吳大棒槌 職位是: 研發(fā)一組組長 薪水是: 20000
名稱: 開發(fā)人員A 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員B 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員C 職位是: 開發(fā) 薪水是: 1000
名稱: 鄭老六 職位是: 研發(fā)二組組長 薪水是: 20000
名稱: 開發(fā)人員D 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員E 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員F 職位是: 開發(fā) 薪水是: 1000
名稱: 馬二愣子 職位是: 銷售部經(jīng)理 薪水是: 50000
名稱: 銷售人員G 職位是: 銷售 薪水是: 1000
名稱: 銷售人員H 職位是: 銷售 薪水是: 1000
名稱: 趙三駝子 職位是: 財務(wù)部經(jīng)理 薪水是: 50000
名稱: 財務(wù)人員I 職位是: 財務(wù) 薪水是: 1000
名稱: 秘書J 職位是: 秘書 薪水是: 1000

此時我們會發(fā)現(xiàn)灶芝,我們有一大坨的代碼都是公用的,例如每個類中都有getInfo()方法唉韭,我們?yōu)槭裁床话阉橄蟪鰜砟匾固椋€有為什么要分根節(jié)點和樹枝節(jié)點呢,根節(jié)點本質(zhì)上也是和樹枝節(jié)點是一樣的属愤。此時我們就能將之前的接口抽象成如下的女器。

簡化的類圖

接口信息如下

interface Info{
    String getInfo();
}

interface ILeafNew extends Info{

}

interface IBranchNew extends Info{
    void add(Info info);
    List getSubordinateInfo();
}

其中BranchNew如下

class BranchNew implements IBranchNew{
    private List subordinateList=new ArrayList();
    private String name;
    private Integer salary;
    private String position;

    public BranchNew(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名稱: "+this.name;
        info = info + " 職位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
    //此處將之前的兩個add方法合成了一個,因為葉子節(jié)點和樹枝節(jié)點都實現(xiàn)了一樣的接口
    @Override
    public void add(Info info) {
        subordinateList.add(info);
    }

    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

其中LeafNew如下

class LeafNew implements ILeafNew{
    private String name;
    private Integer salary;
    private String position;

    public LeafNew(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名稱: "+this.name;
        info = info + " 職位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
}

此時我們經(jīng)過上面的優(yōu)化以后還會覺得有些冗雜,因為在LeafNewBranchNew中還有有一模一樣的代碼住诸。即兩個類中都有重復(fù)的getInfo()方法驾胆,實現(xiàn)方式也一樣,此時我們完全可以將其抽象出來只壳。類圖表示如下

再次精簡的類圖

看見這個圖俏拱,似乎已經(jīng)是最完美的了,因為減少了很多的工作量吼句,接口也沒了锅必,改成了抽象類。省了很多的代碼。具體看代碼如下

首先看一下抽象類抽象出來的公共東西

abstract class Info{
    private String name;
    private Integer salary;
    private String position;

    public Info(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }
    public String getInfo() {
        String info = "";
        info = "名稱: "+this.name;
        info = info + " 職位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
}

抽象類的下面的兩個子類

class BranchNew extends Info{
    private List<Info> subordinateList=new ArrayList();

    public BranchNew(String name, Integer salary, String position) {
       super(name,salary,position);
    }

    //此處將之前的兩個add方法合成了一個,因為葉子節(jié)點和樹枝節(jié)點都實現(xiàn)了一樣的接口
    public void add(Info info) {
        subordinateList.add(info);
    }

    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

class LeafNew extends Info{
    public LeafNew(String name, Integer salary, String position) {
       super(name,salary,position);
    }
}

而此時在創(chuàng)建樹形結(jié)構(gòu)的時候如下搞隐,和之前創(chuàng)建的沒多大的差別驹愚。

public static void main(String[] args) {
        BranchNew ceo = new BranchNew("王大麻子",100000,"總經(jīng)理");
        //部門經(jīng)理,樹枝節(jié)點
        BranchNew developCeo = new BranchNew("劉大瘸子",50000,"研發(fā)部經(jīng)理");
        BranchNew saleCeo = new BranchNew("馬二愣子",50000,"銷售部經(jīng)理");
        BranchNew finaceCeo = new BranchNew("趙三駝子",50000,"財務(wù)部經(jīng)理");
        //組長,樹枝節(jié)點
        BranchNew developOne = new BranchNew("吳大棒槌",20000,"研發(fā)一組組長");
        BranchNew developTwo = new BranchNew("鄭老六",20000,"研發(fā)二組組長");
        //員工,葉子節(jié)點
        LeafNew a = new LeafNew("開發(fā)人員A",1000,"開發(fā)");
        LeafNew b = new LeafNew("開發(fā)人員B",1000,"開發(fā)");
        LeafNew c = new LeafNew("開發(fā)人員C",1000,"開發(fā)");
        LeafNew d = new LeafNew("開發(fā)人員D",1000,"開發(fā)");
        LeafNew e = new LeafNew("開發(fā)人員E",1000,"開發(fā)");
        LeafNew f = new LeafNew("開發(fā)人員F",1000,"開發(fā)");
        LeafNew g = new LeafNew("銷售人員G",1000,"銷售");
        LeafNew h = new LeafNew("銷售人員H",1000,"銷售");
        LeafNew i = new LeafNew("財務(wù)人員I",1000,"財務(wù)");
        LeafNew j = new LeafNew("秘書J",1000,"秘書");
        //進(jìn)行組裝這個組織架構(gòu)
        //總經(jīng)理下的三大得力干將
        ceo.add(developCeo);
        ceo.add(saleCeo);
        ceo.add(finaceCeo);
        //總經(jīng)理下的秘書
        ceo.add(j);
        //研發(fā)部經(jīng)理下的組長
        developCeo.add(developOne);
        developCeo.add(developTwo);
        //銷售部經(jīng)理下的員工
        saleCeo.add(g);
        saleCeo.add(h);
        //財務(wù)部經(jīng)理下的員工
        finaceCeo.add(i);
        //研發(fā)一組下的員工
        developOne.add(a);
        developOne.add(b);
        developOne.add(c);
        //研發(fā)二組下的員工
        developTwo.add(d);
        developTwo.add(e);
        developTwo.add(f);
        System.out.println(ceo.getInfo());
        getAllList(ceo);
    }

遍歷的代碼稍微有些變化

public static void getAllList(BranchNew branchNew){
    List<Info> subordinateInfo = branchNew.getSubordinateInfo();
    for (Info info:subordinateInfo){
        if (info instanceof LeafNew){
            System.out.println(info.getInfo());
        }else {
            System.out.println(info.getInfo());
            getAllList((BranchNew) info);
        }
    }
}

此時發(fā)現(xiàn)運(yùn)行結(jié)果和之前的結(jié)果一模一樣,這就是組合模式

2. 什么是組合模式

在剛才的數(shù)據(jù)結(jié)構(gòu)中我們用代碼實現(xiàn)了樹形結(jié)構(gòu)劣纲。這個就是組合模式逢捺。組合模式主要是用來描述部分與整體的關(guān)系。

將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)癞季,使得用戶對單個對象和組合對象的使用具有一致性

2.1 組合模式的組成

其實我們在上面已經(jīng)實現(xiàn)了一個組合模式劫瞳,組合模式的組合就是數(shù)據(jù)結(jié)構(gòu)中樹形結(jié)構(gòu)的組成并且將其代碼簡化,抽象出來樹枝節(jié)點和葉子節(jié)點的公共部分形成抽象類或者接口绷柒,并且通過調(diào)用此抽象類或者接口將組合對象和簡單對象進(jìn)行一致的處理志于。

組合模式的類圖

其中組合模式涉及到了三個角色

  • Component:抽象構(gòu)件,定義了參加組合對象的共有方法和屬性废睦。當(dāng)然也可以定義為接口
  • Leaf:葉子節(jié)點構(gòu)件伺绽,組合模式中最小的遍歷單位
  • Composite:樹枝節(jié)點構(gòu)件,與葉子節(jié)點構(gòu)成一個樹形結(jié)構(gòu)

接下來我們可以寫出實際的組合模式代碼示例嗜湃,首先可以先看抽象的構(gòu)建奈应,它是組合模式的精髓所在

public abstract class Component{
    //無論是個體還是整體都是共享此代碼的
    public void doSomething(){
    //具體的業(yè)務(wù)邏輯代碼
    }
}

Composite

class Composite extends Component{
    List<Component> list = new ArrayList<>();
    void add(Component component){
        list.add(component);
    }
    void remove(Component component){
        list.remove(component);
    }
    List<Component> getChild(){
        return list;
    }
}

通用Leaf類可以重寫父類的方法。

通過創(chuàng)建場景類模擬創(chuàng)建樹狀的數(shù)據(jù)結(jié)構(gòu)购披,并且通過遞歸的方式遍歷整個樹

public static void main(String[] args) {
    Composite root = new Composite();
    root.doSomething();
    LeafM leafM = new LeafM();
    Composite branch = new Composite();
    root.add(branch);
    branch.add(leafM);
}
//通過遞歸遍歷樹
public static void display(Composite composite){
    for (Component component : composite.getChild()){
        if (component instanceof LeafM){
            component.doSomething();
        }else {
            display((Composite) component);
        }
    }
}

2.2 透明組合模式

組合模式分為兩種杖挣,一種是安全模式,一種是透明模式今瀑。我們上面講的是安全模式程梦,那么透明模式是什么呢?可以看下透明模式的類圖橘荠。

透明模式類圖

通過類圖的對比我們便可知道,透明模式是將方法都放在抽象類中或者接口中郎逃。透明模式下的葉子節(jié)點和樹枝節(jié)點都會有相同的結(jié)構(gòu)哥童,通過判斷是否他下面還有子節(jié)點可以知道是葉子節(jié)點還是樹枝節(jié)點。

3. MyBatis中的組合模式應(yīng)用

此時我們學(xué)完了組合模式以后就知道了在Mybatis中動態(tài)組裝Sql中用到了組合模式褒翰,那么Mybatis是如何應(yīng)用的呢贮懈。比如下面的一段Sql。

<select id="queryAllDown" resultType="map" parameterType="String">
    select * from 表名 where  cpt_pro=#{cpt}
    <if test="cpt!=''">
    and cpt_pro=#{cpt}
    </if>
</select>

Mybatis在進(jìn)行XML解析的時候會解析兩個標(biāo)簽优训,一個是select一個是if朵你,然后通過SqlNode進(jìn)行解析標(biāo)簽中的內(nèi)容,下面是SqlNode中的實現(xiàn)類

SqlNode中的實現(xiàn)類

這些類就構(gòu)成了SqlNode樹形結(jié)構(gòu)中的各個節(jié)點揣非。所有的子節(jié)點都是同一類節(jié)點抡医,可以遞歸的向下執(zhí)行。例如StaticTextSqlNode是最底層的節(jié)點,因此它直接將Sql拼接到sqlBuilder中忌傻。

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

而如果是碰到了if標(biāo)簽大脉,那么可以看IfSqlNode,在IfSqlNode中會先做表達(dá)式的判斷水孩,如果通過的話镰矿,那么進(jìn)行調(diào)用遞歸解析。如果不通過就直接跳過俘种。

@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
  contents.apply(context);
  return true;
}
return false;
}

因此Mybatis就是通過組合模式以一致的方式處理個別對象或者是帶有標(biāo)簽的對象秤标。

4. 參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宙刘,隨后出現(xiàn)的幾起案子苍姜,更是在濱河造成了極大的恐慌,老刑警劉巖荐类,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怖现,死亡現(xiàn)場離奇詭異,居然都是意外死亡玉罐,警方通過查閱死者的電腦和手機(jī)屈嗤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吊输,“玉大人饶号,你說我怎么就攤上這事〖韭欤” “怎么了茫船?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扭屁。 經(jīng)常有香客問我算谈,道長,這世上最難降的妖魔是什么料滥? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任然眼,我火速辦了婚禮,結(jié)果婚禮上葵腹,老公的妹妹穿的比我還像新娘高每。我一直安慰自己,他們只是感情好践宴,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布鲸匿。 她就那樣靜靜地躺著,像睡著了一般阻肩。 火紅的嫁衣襯著肌膚如雪带欢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音洪囤,去河邊找鬼徒坡。 笑死,一個胖子當(dāng)著我的面吹牛瘤缩,可吹牛的內(nèi)容都是我干的喇完。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼剥啤,長吁一口氣:“原來是場噩夢啊……” “哼锦溪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起府怯,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤刻诊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牺丙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體则涯,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年冲簿,在試婚紗的時候發(fā)現(xiàn)自己被綠了粟判。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峦剔,死狀恐怖档礁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吝沫,我是刑警寧澤呻澜,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站惨险,受9級特大地震影響羹幸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辫愉,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一睹欲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧一屋,春花似錦、人聲如沸袋哼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涛贯。三九已至诽嘉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虫腋。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工骄酗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悦冀。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓趋翻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盒蟆。 傳聞我的和親對象是個殘疾皇子踏烙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

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