六、門(mén)面模式與裝飾器模式詳解

8.門(mén)面模式

8.1.課程目標(biāo)

1跟继、掌握門(mén)面模式和裝飾器模式的特征和應(yīng)用場(chǎng)景

2种冬、理解裝飾器模式和代理模式的根本區(qū)別。

3舔糖、了解門(mén)面模式的優(yōu)娱两、缺點(diǎn)。

4金吗、了解裝飾器模式的優(yōu)十兢、缺點(diǎn)。

8.2.內(nèi)容定位

1摇庙、定位高級(jí)課程旱物,不太適合接觸業(yè)務(wù)場(chǎng)景比較單一的人群。

2卫袒、深刻了解門(mén)面模式和裝飾器模式的應(yīng)用場(chǎng)景宵呛。

8.3.門(mén)面模式定義

門(mén)面模式(Facade Pattern)又叫外觀模式,提供了一個(gè)統(tǒng)一的接口夕凝,用來(lái)訪問(wèn)子系統(tǒng)中的一群接
口烤蜕。其主要特征是定義了一個(gè)高層接口,讓子系統(tǒng)更容易使用迹冤,屬于結(jié)構(gòu)性模式。

原文: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.

解釋?zhuān)阂笠粋€(gè)子系統(tǒng)的外部與其內(nèi)部的通信必須通過(guò)一個(gè)同一的對(duì)象進(jìn)行虎忌。門(mén)面模式提供一個(gè)高層次的接口泡徙,使得 子系統(tǒng)更易于使用。

其實(shí)膜蠢,在我們?nèi)粘5木幋a工作中堪藐,我們都在有意無(wú)意地大量使用門(mén)面模式莉兰,但凡只要高層模塊需要
調(diào)度多個(gè)子系統(tǒng)(2個(gè)以上類(lèi)對(duì)象),我們都會(huì)自覺(jué)地創(chuàng)建一個(gè)新類(lèi)封裝這些子系統(tǒng)礁竞,提供精簡(jiǎn)接口糖荒,
讓高層模塊可以更加容易間接調(diào)用這些子系統(tǒng)的功能。尤其是現(xiàn)階段各種第三方SDK模捂,各種開(kāi)源類(lèi)庫(kù)捶朵,
很大概率都會(huì)使用門(mén)面模式。尤其是你覺(jué)得調(diào)用越方便的狂男,門(mén)面模式使用的一般更多综看。

8.4.門(mén)面模式的應(yīng)用場(chǎng)景

1、子系統(tǒng)越來(lái)越復(fù)雜岖食,增加門(mén)面模式提供簡(jiǎn)單接口

2红碑、構(gòu)建多層系統(tǒng)結(jié)構(gòu),利用門(mén)面對(duì)象作為每層的入口泡垃,簡(jiǎn)化層間調(diào)用析珊。

8.5.門(mén)面模式的通用寫(xiě)法

首先來(lái)看門(mén)面模式的UML類(lèi)圖:

<img src="https://miion.me/image-hosting/image-20200306150145819.png" alt="image-20200306150145819" style="zoom:50%;" />

門(mén)面模式主要包含2種角色:

外觀角色(Facade):也稱(chēng)門(mén)面角色,系統(tǒng)對(duì)外的統(tǒng)一接口蔑穴;

子系統(tǒng)角色(SubSystem):可以同時(shí)有一個(gè)或多個(gè) SubSystem忠寻。每個(gè) SubSytem 都不是一個(gè)單獨(dú)
的類(lèi),而是一個(gè)類(lèi)的集合澎剥。 SubSystem 并不知道 Facade 的存在锡溯,對(duì)于 SubSystem 而言, Facade 只
是另一個(gè)客戶端而已(即 Facade 對(duì) SubSystem 透明)哑姚。

下面是門(mén)面模式的通用代碼祭饭,首先分別創(chuàng)建3個(gè)子系統(tǒng)的業(yè)務(wù)邏輯SubSystemA、SubSystemB叙量、
SubSystemC倡蝙,代碼很簡(jiǎn)單:

// 子系統(tǒng)
public class SubSystemA {
    public void doA() {
        System.out.println("doing A stuff");
    }
}
// 子系統(tǒng)
public class SubSystemB {
    public void doB() {
        System.out.println("doing B stuff");
    }
}
// 子系統(tǒng)
public class SubSystemC {
    public void doC() {
        System.out.println("doing C stuff");
    }
}

來(lái)看客戶端代碼:

// 外觀角色 Facade
public class Facade {
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();

    // 對(duì)外接口
    public void doA() {
        this.a.doA();
    }

    // 對(duì)外接口
    public void doB() {
        this.b.doB();
    }

    // 對(duì)外接口
    public void doC() {
        this.c.doC();
    }
}

8.6.門(mén)面模式業(yè)務(wù)場(chǎng)景實(shí)例

Gper社區(qū)上線了一個(gè)積分兌換禮品的商城,這禮品商城中的大部分功能并不是全部重新開(kāi)發(fā)的绞佩,而是要去對(duì)接已有的各個(gè)子系統(tǒng)(如下圖所示):

<img src="https://miion.me/image-hosting/image-20200306163027892.png" alt="image-20200306163027892" style="zoom:50%;" />

這些子系統(tǒng)可能涉及到積分系統(tǒng)寺鸥、支付系統(tǒng)、物流系統(tǒng)的接口調(diào)用品山。如果所有的接口調(diào)用全部由前
端發(fā)送網(wǎng)絡(luò)請(qǐng)求去調(diào)用現(xiàn)有接口的話胆建,一則會(huì)增加前端開(kāi)發(fā)人員的難度,二則會(huì)增加一些網(wǎng)絡(luò)請(qǐng)求影響
頁(yè)面性能肘交。這個(gè)時(shí)候就可以發(fā)揮門(mén)面模式的優(yōu)勢(shì)了笆载。將所有現(xiàn)成的接口全部整合到一個(gè)類(lèi)中,由后端提
供統(tǒng)一的接口給前端調(diào)用,這樣前端開(kāi)發(fā)人員就不需要關(guān)心各接口的業(yè)務(wù)關(guān)系凉驻,只需要把精力集中在頁(yè)
面交互上腻要。下面我們用代碼來(lái)模擬一下這個(gè)場(chǎng)景。

首先涝登,創(chuàng)建禮品的實(shí)體類(lèi)GiftInfo:

public class GiftInfo {

    private String name;

    public GiftInfo(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

然后雄家,編寫(xiě)各個(gè)子系統(tǒng)的業(yè)務(wù)邏輯代碼,分別創(chuàng)建積分系統(tǒng)QualifyService類(lèi):

public class QualifyService {

    public boolean isAvailable(GiftInfo giftInfo){
        System.out.println("校驗(yàn)" +giftInfo.getName() + "積分通過(guò),庫(kù)存通過(guò)胀滚。");
        return true;
    }
}

支付系統(tǒng)PaymentService類(lèi):

public class PaymentService {

    public boolean pay(GiftInfo giftInfo) {
        System.out.println("扣減" + giftInfo.getName() + " 積分成功");
        return true;
    }
}

物流系統(tǒng)ShippingService類(lèi):

public class ShippingService {
    public String delivery(GiftInfo giftInfo){
        System.out.println(giftInfo.getName() + "進(jìn)入物流系統(tǒng)");
        String shippingNo = "666";
        return shippingNo;
    }
}

然后創(chuàng)建外觀角色GiftFacdeService 類(lèi)趟济,對(duì)外只開(kāi)放一個(gè)兌換禮物的exchange()方法,在 exchange() 方法內(nèi)部整合3個(gè)子系統(tǒng)的所有功能蛛淋。

public class FacadeService {
    private QualifyService qualifyService = new QualifyService();
    private PaymentService paymentService = new PaymentService();
    private ShippingService shippingService = new ShippingService();

    //兌換
    public void exchange(GiftInfo giftInfo){
        //資格校驗(yàn)通過(guò)
        if(qualifyService.isAvailable(giftInfo)){
            //如果支付積分成功
            if(paymentService.pay(giftInfo)){
                String shippingNo = shippingService.delivery(giftInfo);
                System.out.println("物流系統(tǒng)下單成功咙好,物流單號(hào)是:" + shippingNo);
            }
        }
    }
}

最后,來(lái)看客戶端代碼:

public class Test {
    public static void main(String[] args) {
        FacadeService facadeService = new FacadeService();
        GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
        facadeService.exchange(giftInfo);
    }
}

運(yùn)行結(jié)果如下:

校驗(yàn)《Spring 5核心原理》積分通過(guò),庫(kù)存通過(guò)褐荷。
扣減《Spring 5核心原理》 積分成功
《Spring 5核心原理》進(jìn)入物流系統(tǒng)
物流系統(tǒng)下單成功勾效,物流單號(hào)是:666

通過(guò)這樣一個(gè)案例對(duì)比之后,相信大家對(duì)門(mén)面模式的印象非常深刻了叛甫。

8.7.門(mén)面模式在源碼中的應(yīng)用

下面我們來(lái)門(mén)面模式在源碼中的應(yīng)用层宫,先來(lái)看Spring JDBC模塊下的JdbcUtils類(lèi),它封裝了和
JDBC相關(guān)的所有操作其监,它一個(gè)代碼片段:

public abstract class JdbcUtils {

    /**
     * Constant that indicates an unknown (or unspecified) SQL type.
     * @see java.sql.Types
     */
    public static final int TYPE_UNKNOWN = Integer.MIN_VALUE;


    private static final Log logger = LogFactory.getLog(JdbcUtils.class);


    /**
     * Close the given JDBC Connection and ignore any thrown exception.
     * This is useful for typical finally blocks in manual JDBC code.
     * @param con the JDBC Connection to close (may be {@code null})
     */
    public static void closeConnection(Connection con) {
        if (con != null) {
            try {
                con.close();
            }
            catch (SQLException ex) {
                logger.debug("Could not close JDBC Connection", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.debug("Unexpected exception on closing JDBC Connection", ex);
            }
        }
    }

    /**
     * Close the given JDBC Statement and ignore any thrown exception.
     * This is useful for typical finally blocks in manual JDBC code.
     * @param stmt the JDBC Statement to close (may be {@code null})
     */
    public static void closeStatement(Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC Statement", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC Statement", ex);
            }
        }
    }

    /**
     * Close the given JDBC ResultSet and ignore any thrown exception.
     * This is useful for typical finally blocks in manual JDBC code.
     * @param rs the JDBC ResultSet to close (may be {@code null})
     */
    public static void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC ResultSet", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
            }
        }
    }
    ...
}

其他更多的操作萌腿,看它的結(jié)構(gòu)就非常清楚了:

<img src="https://miion.me/image-hosting/image-20200306175133730.png" alt="image-20200306175133730" style="zoom:50%;" />

再來(lái)看一個(gè)MyBatis中的Configuration類(lèi)。它其中有很多new開(kāi)頭的方法抖苦,來(lái)看一下源代碼:

public MetaObject newMetaObject(Object object) {
  return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
    ResultHandler resultHandler, BoundSql boundSql) {
  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
  return newExecutor(transaction, defaultExecutorType);
}

上面的這些方法都是對(duì)JDBC中關(guān)鍵組件操作的封裝毁菱。另外地在Tomcat的源碼中也有體現(xiàn),也非常的
有意思锌历。舉個(gè)例子RequestFacade類(lèi)贮庞,來(lái)看源碼:

@SuppressWarnings("deprecation")
public class RequestFacade implements HttpServletRequest {
    ...
    @Override
    public String getContentType() {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return request.getContentType();
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        return request.getInputStream();
    }


    @Override
    public String getParameter(String name) {

        if (request == null) {
            throw new IllegalStateException(
                            sm.getString("requestFacade.nullRequest"));
        }

        if (Globals.IS_SECURITY_ENABLED){
            return AccessController.doPrivileged(
                new GetParameterPrivilegedAction(name));
        } else {
            return request.getParameter(name);
        }
    }
    ...
}

我們看名字就知道它用了門(mén)面模式。它封裝了非常多的request的操作究西,也整合了很多servlet-api以
外的一些內(nèi)容窗慎,給用戶使用提供了很大便捷。同樣卤材,Tomcat對(duì)Response和Session當(dāng)也封裝了
ResponseFacade和StandardSessionFacade類(lèi)遮斥,感興趣的小伙伴可以去深入了解一下。

8.8.門(mén)面模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

1扇丛、簡(jiǎn)化了調(diào)用過(guò)程术吗,無(wú)需深入了解子系統(tǒng),以防給子系統(tǒng)帶來(lái)風(fēng)險(xiǎn)帆精。

2藐翎、減少系統(tǒng)依賴(lài)材蹬、松散耦合

3、更好地劃分訪問(wèn)層次吝镣,提高了安全性

4、遵循迪米特法則昆庇,即最少知道原則末贾。

缺點(diǎn):

1、當(dāng)增加子系統(tǒng)和擴(kuò)展子系統(tǒng)行為時(shí)整吆,可能容易帶來(lái)未知風(fēng)險(xiǎn)

2拱撵、不符合開(kāi)閉原則

3、某些情況下可能違背單一職責(zé)原則表蝙。

9.裝飾器模式

9.1.裝飾器模式定義

裝飾器模式(Decorator Pattern),也稱(chēng)為包裝模式(Wrapper Pattern)是指在不改變?cè)袑?duì)象
的基礎(chǔ)之上拴测,將功能附加到對(duì)象上,提供了比繼承更有彈性的替代方案(擴(kuò)展原有對(duì)象的功能)府蛇,屬于
結(jié)構(gòu)型模式集索。

原文:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
解釋?zhuān)簞?dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō)汇跨,裝飾器模式相比生成子類(lèi)更為靈活务荆。

裝飾器模式的核心是功能擴(kuò)展。使用裝飾器模式可以透明且動(dòng)態(tài)地?cái)U(kuò)展類(lèi)的功能穷遂。

裝飾器模式主要用于透明且動(dòng)態(tài)地?cái)U(kuò)展類(lèi)的功能函匕。其實(shí)現(xiàn)原理為:讓裝飾器實(shí)現(xiàn)被包裝類(lèi)(Concrete
Component)相同的接口(Component)(使得裝飾器與被擴(kuò)展類(lèi)類(lèi)型一致),并在構(gòu)造函數(shù)中傳入
該接口(Component)對(duì)象蚪黑,然后就可以在接口需要實(shí)現(xiàn)的方法中在被包裝類(lèi)對(duì)象的現(xiàn)有功能上添加
新功能了盅惜。而且由于裝飾器與被包裝類(lèi)屬于同一類(lèi)型(均為Component),且構(gòu)造函數(shù)的參數(shù)為其實(shí)
現(xiàn)接口類(lèi)(Component)忌穿,因此裝飾器模式具備嵌套擴(kuò)展功能抒寂,這樣我們就能使用裝飾器模式一層一
層的對(duì)最底層被包裝類(lèi)進(jìn)行功能擴(kuò)展了。

首先看下裝飾器模式的通用UML類(lèi)圖:

從 UML 類(lèi)圖中伴网,我們可以看到蓬推,裝飾器模式 主要包含四種角色:

<img src="https://miion.me/image-hosting/image-20200306204330480.png" alt="image-20200306204330480" style="zoom:50%;" />

抽象組件(Component):可以是一個(gè)接口或者抽象類(lèi),其充當(dāng)被裝飾類(lèi)的原始對(duì)象澡腾,規(guī)定了被
裝飾對(duì)象的行為沸伏;

具體組件(ConcreteComponent):實(shí)現(xiàn)/繼承Component的一個(gè)具體對(duì)象,也即被裝飾對(duì)象动分;

抽象裝飾器(Decorator):通用的裝飾ConcreteComponent的裝飾器毅糟,其內(nèi)部必然有一個(gè)屬性
指向 Component抽象組件;其實(shí)現(xiàn)一般是一個(gè)抽象類(lèi)澜公,主要是為了讓其子類(lèi)按照其構(gòu)造形式傳入一
個(gè) Component 抽象組件姆另,這是強(qiáng)制的通用行為(當(dāng)然喇肋,如果系統(tǒng)中裝飾邏輯單一,并不需要實(shí)現(xiàn)許
多裝飾器迹辐,那么我們可以直接省略該類(lèi)蝶防,而直接實(shí)現(xiàn)一個(gè)具體裝飾器(ConcreteDecorator)即可);

具體裝飾器(ConcreteDecorator):Decorator 的具體實(shí)現(xiàn)類(lèi)明吩,理論上间学,每個(gè) ConcreteDecorator
都擴(kuò)展了Component對(duì)象的一種功能;

總結(jié):裝飾器模式角色分配符合設(shè)計(jì)模式里氏替換原則印荔,依賴(lài)倒置原則低葫,從而使得其具備很強(qiáng)的擴(kuò)展性,最終滿足開(kāi) 閉原則仍律。

9.2.裝飾器模式的應(yīng)用場(chǎng)景

裝飾器模式在我們生活中應(yīng)用也比較多如給煎餅加雞蛋嘿悬;給蛋糕加上一些水果;給房子裝修等水泉,為對(duì)象擴(kuò)展一些額外的職責(zé)善涨。裝飾器在代碼程序中適用于以下場(chǎng)景:

1、用于擴(kuò)展一個(gè)類(lèi)的功能或給一個(gè)類(lèi)添加附加職責(zé)茶行。

2躯概、動(dòng)態(tài)的給一個(gè)對(duì)象添加功能,這些功能可以再動(dòng)態(tài)的撤銷(xiāo)畔师。

3娶靡、需要為一批的兄弟類(lèi)進(jìn)行改裝或加裝功能。

來(lái)看一個(gè)這樣的場(chǎng)景看锉,上班族白領(lǐng)其實(shí)大多有睡懶覺(jué)的習(xí)慣姿锭,每天早上上班都是踩點(diǎn),于是很多小
伙伴為了多賴(lài)一會(huì)兒床都不吃早餐伯铣。那么呻此,也有些小伙伴可能在上班路上碰到賣(mài)煎餅的路邊攤,都會(huì)順
帶一個(gè)到公司茶水間吃早餐腔寡。賣(mài)煎餅的大姐可以給你的煎餅加雞蛋焚鲜,也可以加香腸(如下圖,PS:我買(mǎi)
煎餅一般都要求不加生菜)放前。

<img src="https://miion.me/image-hosting/image-20200306205431629.png" alt="image-20200306205431629" style="zoom:50%;" />

下面我們用代碼還原一下碼農(nóng)的生活忿磅。首先創(chuàng)建一個(gè)煎餅Battercake類(lèi):

public class Battercake {

    protected String getMsg(){ return "煎餅";}

    public int getPrice(){ return 5;}
}

創(chuàng)建一個(gè)加雞蛋的煎餅BattercakeWithEgg類(lèi):

public class BattercakeWithEgg extends Battercake {
    @Override 
    protected String getMsg(){ return super.getMsg() + "+1個(gè)雞蛋";}

    @Override 
    //加一個(gè)雞蛋加 1 塊錢(qián) 
    public int getPrice(){ return super.getPrice() + 1;}
}

再創(chuàng)建一個(gè)既加雞蛋又加香腸的BattercakeWithEggAndSausage類(lèi):

public class BattercakeWithEggAndSauage extends BattercakeWithEgg {
    @Override
    protected String getMsg(){ return super.getMsg() + "+1根香腸";}

    @Override
    //加一個(gè)香腸加 2 塊錢(qián)
    public int getPrice(){ return super.getPrice() + 2;}
}

編寫(xiě)客戶端測(cè)試代碼:

public class Test {
    public static void main(String[] args) {
        Battercake battercake = new Battercake();
        System.out.println(battercake.getMsg() + ",總價(jià):" + battercake.getPrice());

        BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ",總價(jià):" + battercakeWithEgg.getPrice());

        BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();
        System.out.println(battercakeWithEggAndSauage.getMsg() + ",總價(jià):" + battercakeWithEggAndSauage.getPrice());
    }
}

運(yùn)行結(jié)果:

煎餅,總價(jià):5
煎餅+1個(gè)雞蛋,總價(jià):6
煎餅+1個(gè)雞蛋+1根香腸,總價(jià):8

運(yùn)行結(jié)果沒(méi)有問(wèn)題。

但是凭语,如果用戶需要一個(gè)加2個(gè)雞蛋加1根香腸的煎餅葱她,那么用我們現(xiàn)在的類(lèi)
結(jié)構(gòu)是創(chuàng)建不出來(lái)的,也無(wú)法自動(dòng)計(jì)算出價(jià)格似扔,除非再創(chuàng)建一個(gè)類(lèi)做定制吨些。如果需求再變搓谆,一直加定制
顯然是不科學(xué)的。那么下面我們就用裝飾器模式來(lái)解決上面的問(wèn)題豪墅。

首先創(chuàng)建一個(gè)建煎餅的抽象
Battercake類(lèi):

public abstract class Battercake {

    protected abstract String getMsg();

    protected abstract int getPrice();
}

創(chuàng)建一個(gè)基本的煎餅(或者叫基礎(chǔ)套餐)BaseBattercake:

public class BaseBattercake extends Battercake{

    protected String getMsg(){ return "煎餅";}

    public int getPrice(){ return 5;}
}

然后泉手,再創(chuàng)建一個(gè)擴(kuò)展套餐的抽象裝飾器BattercakeDecotator類(lèi):

public class BattercakeDecorator extends Battercake{
    //靜態(tài)代理,委派 
    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {
        this.battercake = battercake;
    }
    
    @Override 
    protected String getMsg(){ return this.battercake.getMsg();}
    
    @Override 
    public int getPrice(){ return this.battercake.getPrice();}
}

然后偶器,創(chuàng)建雞蛋裝飾器EggDecorator類(lèi):

public class EggDecorator extends BattercakeDecorator{

    public EggDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    protected String getMsg(){ return super.getMsg() + "1個(gè)雞蛋";}

    @Override
    public int getPrice(){ return super.getPrice() + 1;}
}

創(chuàng)建香腸裝飾器SausageDecorator類(lèi):

public class SauageDecorator extends BattercakeDecorator{

    public SauageDecorator(Battercake battercake) {
        super(battercake);
    }

    protected String getMsg(){ return super.getMsg() + "1根香腸";}

    public int getPrice(){ return super.getPrice() + 2;}
}

編寫(xiě)客戶端測(cè)試代碼:

public class Test {
    public static void main(String[] args) {
        //路邊攤買(mǎi)一個(gè)煎餅
        Battercake battercake = new BaseBattercake();
        //煎餅有點(diǎn)小螃诅,想再加一個(gè)雞蛋
        battercake = new EggDecorator(battercake);
        //再加一個(gè)雞蛋
        battercake = new EggDecorator(battercake);
        //很餓,再加根香腸
        battercake = new SauageDecorator(battercake);
        //跟靜態(tài)代理最大區(qū)別就是職責(zé)不同 
        //靜態(tài)代理不一定要滿足 is-a 的關(guān)系 
        //靜態(tài)代理會(huì)做功能增強(qiáng)状囱,同一個(gè)職責(zé)變得不一樣
        //裝飾器更多考慮是擴(kuò)展 
        System.out.println(battercake.getMsg() + ",總價(jià)" + battercake.getPrice());
    }
}

運(yùn)行結(jié)果:

煎餅,總價(jià):5
煎餅+1個(gè)雞蛋,總價(jià):6
煎餅+1個(gè)雞蛋+1根香腸,總價(jià):8

來(lái)看一下類(lèi)圖:

<img src="https://miion.me/image-hosting/image-20200306213549376.png" alt="image-20200306213549376" style="zoom:50%;" />

為了加深印象,我們?cè)賮?lái)看一個(gè)應(yīng)用場(chǎng)景倘是。需求大致是這樣亭枷,系統(tǒng)采用的是sls服務(wù)監(jiān)控項(xiàng)目日志,
以Json的格式解析搀崭,所以需要將項(xiàng)目中的日志封裝成json格式再打印∵墩常現(xiàn)有的日志體系采用了log4j+
slf4j框架搭建而成×龆茫客戶端調(diào)用是這樣的:

private static final Logger logger = LoggerFactory.getLogger(Component.class); 
logger.error(string);

這樣打印出來(lái)的是毫無(wú)規(guī)則的一行行字符串升敲。在考慮將其轉(zhuǎn)換成json格式時(shí),我采用了裝飾器模式轰传。
目前有的是統(tǒng)一接口 Logger 和其具體實(shí)現(xiàn)類(lèi)驴党,我要加的就是一個(gè)裝飾類(lèi)和真正封裝成 Json格式的裝
飾產(chǎn)品類(lèi)。創(chuàng)建裝飾器類(lèi)LoggerDecorator:

public class LoggerDecorator implements Logger {

    protected Logger logger;

    public LoggerDecorator(Logger logger) {
        this.logger = logger;
    }

    public void error(String s) {
    }

    public void error(String s, Object o) {
    }
    //省略其他默認(rèn)實(shí)現(xiàn)
}

創(chuàng)建具體組件JasonLogger類(lèi)實(shí)現(xiàn)代碼如下:

public class JsonLogger extends LoggerDecorator {
    public JsonLogger(Logger logger) {
        super(logger);
    }

    @Override
    public void info(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }

    @Override
    public void error(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }

    public void error(Exception e){
        JSONObject result = newJsonObject();
        result.put("exception",e.getClass().getName());
        String trace = Arrays.toString(e.getStackTrace());
        result.put("starckTrace",trace);
        logger.info(result.toString());
    }

    private JSONObject newJsonObject(){
        //拼裝了一些運(yùn)行時(shí)信息
        return new JSONObject();
    }
}

可以看到获茬,在JsonLogger中港庄,對(duì)于Logger的各種接口,我都用JsonObject對(duì)象進(jìn)行一層封裝恕曲。
在打印的時(shí)候鹏氧,最終還是調(diào)用原生接口 logger.error(string),只是這個(gè) string 參數(shù)已經(jīng)被我們裝飾過(guò)
了佩谣。如果有額外的需求把还,我們也可以再寫(xiě)一個(gè)函數(shù)去實(shí)現(xiàn)。比如 error(Exception e)茸俭,只傳入一個(gè)異常
對(duì)象吊履,這樣在調(diào)用時(shí)就非常方便了。

另外瓣履,為了在新老交替的過(guò)程中盡量不改變太多的代碼和使用方式率翅。我又在JsonLogger 中加入了
一個(gè)內(nèi)部的工廠類(lèi)JsonLoggerFactory(這個(gè)類(lèi)轉(zhuǎn)移到DecoratorLogger 中可能更好一些),他包含
一個(gè)靜態(tài)方法袖迎,用于提供對(duì)應(yīng)的JsonLogger實(shí)例冕臭。最終在新的日志體系中腺晾,使用方式如下:

public class Test {
    private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        logger.error("系統(tǒng)錯(cuò)誤");
    }
}

對(duì)于客戶端而言,唯一與原先不同的地方就是將 LoggerFactory 改為JsonLoggerFactory 即可辜贵,
這樣的實(shí)現(xiàn)悯蝉,也會(huì)被更快更方便的被其他開(kāi)發(fā)者接受和習(xí)慣。最后看一下類(lèi)圖:

<img src="https://miion.me/image-hosting/image-20200306215623864.png" alt="image-20200306215623864" style="zoom:50%;" />

裝飾器模式最本質(zhì)的特征是講原有類(lèi)的附加功能抽離出來(lái)托慨,簡(jiǎn)化原有類(lèi)的邏輯鼻由。通過(guò)這樣兩個(gè)案例,我們可以總結(jié)出來(lái)厚棵,其實(shí)抽象的裝飾器是可有可無(wú)的蕉世,具體可以根據(jù)業(yè)務(wù)模型來(lái)選擇。

9.3.裝飾器模式在源碼中的應(yīng)用

裝飾器模式在源碼中也應(yīng)用得非常多婆硬,在 JDK 中體現(xiàn)最明顯的類(lèi)就是 IO 相關(guān)的類(lèi)狠轻,如
BufferedReader、InputStream彬犯、OutputStream向楼,看一下常用的InputStream的類(lèi)結(jié)構(gòu)圖:

<img src="https://miion.me/image-hosting/image-20200306221314640.png" alt="image-20200306221314640" style="zoom: 50%;" />

在Spring 中的 TransactionAwareCacheDecorator類(lèi)我們也可以來(lái)嘗試?yán)斫庖幌拢@個(gè)類(lèi)主要是用來(lái)處理事務(wù)緩存的谐区,來(lái)看一下代碼:

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;

    public TransactionAwareCacheDecorator(Cache targetCache) {
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }

    public Cache getTargetCache() {
        return this.targetCache;
    }
    ...
}

TransactionAwareCacheDecorator就是對(duì)Cache的一個(gè)包裝湖蜕。再來(lái)看一個(gè) MVC中的裝飾器模式
HttpHeadResponseDecorator類(lèi):

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
    public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
        super(delegate);
    }
    ...
}

最后,看看 MyBatis中的一段處理緩存的設(shè)計(jì) org.apache.ibatis.cache.Cache 類(lèi)宋列,找到它的包定
位:

image-20200306222504353

從名字上來(lái)看其實(shí)更容易理解了昭抒。比如 FifoCache先入先出算法的緩存;LruCache最近最少使用
的緩存虚茶;TransactionlCache事務(wù)相關(guān)的緩存戈鲁,都是采用裝飾器模式。MyBatis源碼在我們后續(xù)的課程
也會(huì)深入講解嘹叫,感興趣的小伙伴可以詳細(xì)看看這塊的源碼婆殿,也可以好好學(xué)習(xí)一下MyBatis的命名方式,
今天我們還是把重點(diǎn)放到設(shè)計(jì)模式上罩扇。

9.4.裝飾器模式和代理模式對(duì)比

從代理模式的 UML類(lèi)圖和通用代碼實(shí)現(xiàn)上看婆芦,代理模式與裝飾器模式幾乎一模一樣。代理模式的
Subject 對(duì) 應(yīng) 裝 飾 器 模 式 的 Component 喂饥, 代 理 模 式 的 RealSubject 對(duì) 應(yīng) 裝 飾 器 模 式 的
ConcreteComponent消约,代理模式的 Proxy對(duì)應(yīng)裝飾器模式的Decorator。確實(shí)员帮,從代碼實(shí)現(xiàn)上看或粮,代
理模式的確與裝飾器模式是一樣的(其實(shí)裝飾器模式就是代理模式的一個(gè)特殊應(yīng)用),但是這兩種設(shè)計(jì)
模式所面向的功能擴(kuò)展面是不一樣的:

裝飾器模式強(qiáng)調(diào)自身功能的擴(kuò)展捞高。Decorator所做的就是增強(qiáng)ConcreteComponent的功能(也有
可能減弱功能)氯材,主體對(duì)象為ConcreteComponent渣锦,著重類(lèi)功能的變化;

代理模式強(qiáng)調(diào)對(duì)代理過(guò)程的控制氢哮。Proxy 完全掌握對(duì) RealSubject的訪問(wèn)控制袋毙,因此,Proxy 可以決定對(duì)RealSubject 進(jìn)行功能擴(kuò)展冗尤,功能縮減甚至功能散失(不調(diào)用RealSubject方法)听盖,主體對(duì)象為
Proxy;

簡(jiǎn)單來(lái)講裂七,假設(shè)現(xiàn)在小明想租房皆看,那么勢(shì)必會(huì)有一些事務(wù)發(fā)生:房源搜索,聯(lián)系房東談價(jià)格····

假設(shè)我們按照代理模式進(jìn)行思考背零,那么小明只需找到一個(gè)房產(chǎn)中介悬蔽,讓他去干房源搜索,聯(lián)系房東
談價(jià)格這些事情捉兴,小明只需等待通知然后付點(diǎn)中介費(fèi)就行了;

而如果采用裝飾器模式進(jìn)行思考录语,因?yàn)檠b飾器模式強(qiáng)調(diào)的是自身功能擴(kuò)展倍啥,也就是說(shuō),如果要找房
子澎埠,小明自身就要增加房源搜索能力擴(kuò)展虽缕,聯(lián)系房東談價(jià)格能力擴(kuò)展,通過(guò)相應(yīng)的裝飾器蒲稳,提升自身能
力氮趋,一個(gè)人做滿所有的事情。

9.5.裝飾器模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

1江耀、裝飾器是繼承的有力補(bǔ)充剩胁,比繼承靈活,不改變?cè)袑?duì)象的情況下動(dòng)態(tài)地給一個(gè)對(duì)象擴(kuò)展功能祥国,即插即用昵观。

2、通過(guò)使用不同裝飾類(lèi)以及這些裝飾類(lèi)的排列組合舌稀,可以實(shí)現(xiàn)不同效果啊犬。

3、裝飾器完全遵守開(kāi)閉原則壁查。

缺點(diǎn):

1觉至、會(huì)出現(xiàn)更多的代碼,更多的類(lèi)睡腿,增加程序復(fù)雜性语御。

2峻贮、動(dòng)態(tài)裝飾時(shí),多層裝飾時(shí)會(huì)更復(fù)雜沃暗。

那么裝飾器模式我們就講解到這里月洛,希望小伙伴們認(rèn)真體會(huì),加深理解孽锥。

9.6.作業(yè)

1嚼黔、請(qǐng)舉例3-5個(gè)使用門(mén)面模式的應(yīng)用場(chǎng)景。

1.解決易用性問(wèn)題

門(mén)面模式可以用來(lái)封裝系統(tǒng)的底層實(shí)現(xiàn)惜辑,隱藏系統(tǒng)的復(fù)雜性唬涧,提供一組更加簡(jiǎn)單易用、更高層的接口盛撑。

比如碎节,Linux 系統(tǒng)調(diào)用函數(shù)就可以看作一種“門(mén)面”。它是 Linux 操作系統(tǒng)暴露給開(kāi)發(fā)者的一組“特殊”的編程接口抵卫,它封裝了底層更基礎(chǔ)的 Linux 內(nèi)核調(diào)用狮荔。

再比如,Linux 的 Shell 命令介粘,實(shí)際上也可以看作一種門(mén)面模式的應(yīng)用殖氏。它繼續(xù)封裝系統(tǒng)調(diào)用,提供更加友好姻采、簡(jiǎn)單的命令雅采,讓我們可以直接通過(guò)執(zhí)行命令來(lái)跟操作系統(tǒng)交互。

2.解決性能問(wèn)題

我們通過(guò)將多個(gè)接口調(diào)用替換為一個(gè)門(mén)面接口調(diào)用慨亲,減少網(wǎng)絡(luò)通信成本婚瓜,提高 App 客戶端的響應(yīng)速度。

3.解決分布式事務(wù)問(wèn)題

在用戶注冊(cè)的時(shí)候刑棵,我們不僅會(huì)創(chuàng)建用戶(在數(shù)據(jù)庫(kù) User 表中)巴刻,還會(huì)給用戶創(chuàng)建一個(gè)錢(qián)包(在數(shù)據(jù)庫(kù)的 Wallet 表中)。用戶注冊(cè)需要支持事務(wù)蛉签,也就是說(shuō)冈涧,創(chuàng)建用戶和錢(qián)包的兩個(gè)操作,要么都成功正蛙,要么都失敗督弓,不能一個(gè)成功、一個(gè)失敗乒验。

最簡(jiǎn)單的解決方案是愚隧,利用數(shù)據(jù)庫(kù)事務(wù)或者 Spring 框架提供的事務(wù)(如果是 Java 語(yǔ)言的話),在一個(gè)事務(wù)中,執(zhí)行創(chuàng)建用戶和創(chuàng)建錢(qián)包這兩個(gè) SQL 操作狂塘。這就要求兩個(gè) SQL 操作要在一個(gè)接口中完成录煤,所以,我們可以借鑒門(mén)面模式的思想荞胡,再設(shè)計(jì)一個(gè)包裹這兩個(gè)操作的新接口妈踊,讓新接口在一個(gè)事務(wù)中執(zhí)行兩個(gè) SQL 操作。

2泪漂、使用裝飾器模式實(shí)現(xiàn)一個(gè)可根據(jù)權(quán)限動(dòng)態(tài)擴(kuò)展功能的導(dǎo)航條廊营。

例如:GPer社區(qū)未登錄狀態(tài)下的導(dǎo)航條

image-20200307180017756

GPer社區(qū)登錄狀態(tài)下的導(dǎo)航條

image-20200307180102423

整體思路:用戶分為未登錄會(huì)員、VIP會(huì)員萝勤、管理員分別展示不同的導(dǎo)航條露筒。

1.創(chuàng)建抽象導(dǎo)航條類(lèi),抽象方法展示導(dǎo)航條敌卓。

public abstract class AbstractNavDecorator{
    /**
     * 展示導(dǎo)航條
     * @return 導(dǎo)航條
     */
    protected abstract String showNavs();
}

2.創(chuàng)建基礎(chǔ)導(dǎo)航條類(lèi)

public class BaseNavDecorator extends AbstractNavDecorator {
    @Override
    public String showNavs() {
        return "問(wèn)答-文章-精品課-冒泡-商城";
    }
}

3.創(chuàng)建導(dǎo)航條裝飾器

public class NavDecorator extends AbstractNav {

    private AbstractNav abstractNav;

    public NavDecorator(AbstractNav abstractNav) {
        this.abstractNav = abstractNav;
    }

    @Override
    protected String showNavs() {
        return this.abstractNav.showNavs();
    }
}

4.創(chuàng)建作業(yè)裝飾器

public class HomeworkNavDecorator extends NavDecorator {

    public HomeworkNavDecorator(AbstractNav abstractNav) {
        super(abstractNav);
    }

    @Override
    public String showNavs() {
        return super.showNavs() + "-作業(yè)";
    }
}

5.創(chuàng)建題庫(kù)裝飾器

public class QuizNavDecorator extends NavDecorator {

    public QuizNavDecorator(AbstractNav abstractNav) {
        super(abstractNav);
    }

    @Override
    public String showNavs() {
        return super.showNavs() + "-題庫(kù)";
    }
}

6.創(chuàng)建成長(zhǎng)墻裝飾器

public class WallNavDecorator extends NavDecorator {

    public WallNavDecorator(AbstractNav abstractNav) {
        super(abstractNav);
    }

    @Override
    public String showNavs() {
        return super.showNavs() + "-成長(zhǎng)墻";
    }
}

7.創(chuàng)建管理裝飾器

public class AdminNavDecorator extends NavDecorator {

    public AdminNavDecorator(AbstractNav abstractNav) {
        super(abstractNav);
    }

    @Override
    protected String showNavs() {
        return super.showNavs() + "-管理";
    }
}

8.創(chuàng)建用戶枚舉

public enum User {
    NOT_LOGIN(1, "未登錄用戶"),
    VIP(2, "會(huì)員"),
    ADMIN(3, "管理員")
    ;

    User(int code, String name) {
        this.code = code;
        this.type = name;
    }

    /**
     * 編碼
     */
    private int code;

    /**
     * 用戶類(lèi)型
     */
    private String type;
}

9.創(chuàng)建根據(jù)用戶創(chuàng)建不同導(dǎo)航條簡(jiǎn)單工廠與測(cè)試類(lèi)

public class PermissionFactory {

    String showPermission(User user) {
        AbstractNav baseNavDecorator = new BaseNav();
        switch (user) {
            case NOT_LOGIN:
                return baseNavDecorator.showNavs();
            case VIP:
                return new WallNavDecorator(new QuizNavDecorator(new HomeworkNavDecorator(baseNavDecorator))).showNavs();
            case ADMIN:
                return new AdminNavDecorator(new WallNavDecorator(new QuizNavDecorator(new HomeworkNavDecorator(baseNavDecorator)))).showNavs();
            default:
                return null;
        }
    }

    public static void main(String[] args){
        PermissionFactory permissionFactory = new PermissionFactory();
        System.out.println("未登錄用戶導(dǎo)航條展示:");
        System.out.println(permissionFactory.showPermission(NOT_LOGIN));
        System.out.println("VIP用戶導(dǎo)航條展示:");
        System.out.println(permissionFactory.showPermission(VIP));
        System.out.println("管理員導(dǎo)航條展示:");
        System.out.println(permissionFactory.showPermission(ADMIN));
    }
}

10.運(yùn)行效果如下:

未登錄用戶導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城
VIP用戶導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城-作業(yè)-題庫(kù)-成長(zhǎng)墻
管理員導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城-作業(yè)-題庫(kù)-成長(zhǎng)墻-管理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冒萄,一起剝皮案震驚了整個(gè)濱河市寓娩,隨后出現(xiàn)的幾起案子翔忽,更是在濱河造成了極大的恐慌卓研,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜗巧,死亡現(xiàn)場(chǎng)離奇詭異肪虎,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)惧蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刑枝,“玉大人香嗓,你說(shuō)我怎么就攤上這事∽俺” “怎么了靠娱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掠兄。 經(jīng)常有香客問(wèn)我像云,道長(zhǎng),這世上最難降的妖魔是什么蚂夕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任迅诬,我火速辦了婚禮,結(jié)果婚禮上婿牍,老公的妹妹穿的比我還像新娘侈贷。我一直安慰自己,他們只是感情好等脂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布俏蛮。 她就那樣靜靜地躺著撑蚌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搏屑。 梳的紋絲不亂的頭發(fā)上争涌,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音辣恋,去河邊找鬼亮垫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抑党,可吹牛的內(nèi)容都是我干的包警。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼底靠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼害晦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起暑中,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤壹瘟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鳄逾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體稻轨,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年雕凹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殴俱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枚抵,死狀恐怖线欲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汽摹,我是刑警寧澤李丰,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站逼泣,受9級(jí)特大地震影響趴泌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拉庶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一嗜憔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧氏仗,春花似錦痹筛、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谣旁。三九已至,卻和暖如春滋早,著一層夾襖步出監(jiān)牢的瞬間榄审,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工杆麸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搁进,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓昔头,卻偏偏與公主長(zhǎng)得像饼问,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揭斧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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