抽象工廠、Builder模式和工廠方法

抽象工廠模式

用途

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
From: Design Patterns: Elements of Reusable Object-Oriented Software

提供一個接口凭峡,使得當創(chuàng)建一系列的相關(guān)或者相互依賴的對象時,不需要一一指定它們具體的類。

例子

下面是一個圖形界面工具包的例子作瞄。像窗口、滾動條等組件危纫,支持不同的樣式宗挥。樣式可以由界面的不同主題決定。這樣种蝶,如果在整個應用中寫死不同主題的不同按鈕樣式契耿,非常不好維護。
為了解決這個問題螃征,使應用便于在各個主題之間切換移植搪桂。我們可以定義一個抽象的WidgetFactory類,在其中定義創(chuàng)建每一種組件的接口盯滚。同樣地踢械,對于每一種組件,我們也有一個抽象類魄藕,每一種具體主題下的組件都是這個類的子類内列。WidgetFactory類中創(chuàng)建組件的接口就是創(chuàng)建一個新的組件實例。
這樣背率,客戶端調(diào)用WidgetFactory類的接口來得到各種組件的實例话瞧,但是嫩与,客戶端并不知道獲得的具體組件類是哪個。因此交排,就實現(xiàn)了調(diào)用方和具體主題的解耦划滋。如下圖。


WidgetFactory

從這里可以看出埃篓,抽象工廠類主要用于產(chǎn)生一系列相關(guān)的類或者一類對象处坪。使用了抽象工廠之后,如果想要切換獲得對象的類別或者系列都许,只需要改動抽象工廠的實例即可稻薇。通過這樣,就改變了所有獲得對象的主題胶征,從而實現(xiàn)了調(diào)用方和具體對象的解耦塞椎。

說明

AbstractFactory defers creation of product objects to its ConcreteFactory subclass.
From: Design Patterns: Elements of Reusable Object-Oriented Software
抽象工廠將產(chǎn)品對象的創(chuàng)建下放到了具體的工廠子類中。

脫離了具體的場景之后睛低,抽象工廠類的示意圖如下:


AbstractFactory

使用抽象工廠案狠,可以用來控制產(chǎn)品的一致性。當多個產(chǎn)品被設置為一個系列之后钱雷,應用使用同一個系列內(nèi)的產(chǎn)品是很重要的骂铁。抽象工廠模式使這種控制變得容易。
使用抽象工廠的壞處是罩抗,支持新的產(chǎn)品比較麻煩拉庵。因為在抽象工廠的接口中已經(jīng)固定了支持哪些產(chǎn)品,新增支持新的產(chǎn)品就需要修改這個接口套蒂。

例子

AbstractFactory.Cpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public abstract class Cpu {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

AbstractFactory.PhoneCpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneCpu extends Cpu {
    public PhoneCpu(String name) {
        super(name);
    }
}

AbstractFactory.ComputerCpu:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerCpu extends Cpu {
    public ComputerCpu(String name) {
        super(name);
    }
}

AbstractFactory.Screen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public abstract class Screen {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

AbstractFactory.PhoneScreen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneScreen extends Screen {
    public PhoneScreen(String name) {
        super(name);
    }
}

AbstractFactory.ComputerScreen:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerScreen extends Screen {
    public ComputerScreen(String name) {
        super(name);
    }
}

AbstractFactory.AbstractComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class AbstractComponentFactory {
    public String getCPU(){
        return "Abstract CPU.";
    }
    public String getScreen(){
        return "Abstract Screen.";
    }
}

AbstractFactory.PhoneComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class PhoneComponentFactory extends AbstractComponentFactory{
    @Override
    public String getCPU(){
        return "Qualcomm Snapdragon CPU: 820, 821, 845, 855 ...";
    }

    @Override
    public String getScreen(){
        return "Computer Screen: JDI LED, BOE OLED, Samsung AMOLED ...";
    }
}

AbstractFactory.ComputerComponentFactory:

package AbstractFactory;

/**
 * Created by chengxia on 2019/8/16.
 */
public class ComputerComponentFactory extends AbstractComponentFactory{
    @Override
    public String getCPU(){
        return "Computer CPU: i3, i5, i7 ...";
    }
    @Override
    public String getScreen(){
        return "Computer Screen: Samsung LCD ...";
    }
}

類圖如下:


AbstactFactoryExample

下面如果有手機或者電腦廠商調(diào)用上面的類的時候钞支,可以如下:
AManufacturer.Xiaomi:

package AManufacturer;

import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.PhoneComponentFactory;

/**
 * Created by chengxia on 2019/8/19.
 */
public class Xiaomi {
    public static void main(String []args){
        AbstractComponentFactory ab = new PhoneComponentFactory();
        System.out.println(ab.getCPU());
        System.out.println(ab.getScreen());
    }
}

AManufacturer.Lenovo:

package AManufacturer;

import AbstractFactory.AbstractComponentFactory;
import AbstractFactory.ComputerComponentFactory;

/**
 * Created by chengxia on 2019/8/19.
 */
public class Lenovo {
    public static void main(String []args){
        AbstractComponentFactory ab = new ComputerComponentFactory();
        System.out.println(ab.getCPU());
        System.out.println(ab.getScreen());
    }
}

Builder模式

用途

Separate the construction of a complex object from its representation so that the same construction process can create different representations.
From: Design Patterns: Elements of Reusable Object-Oriented Software

將復雜對象的創(chuàng)建,從其表示中剝離操刀。同樣的創(chuàng)建過程烁挟,會創(chuàng)建出不同的對象。

例子

一個RTF富文本閱讀器骨坑,應該能夠?qū)⒏晃谋靖袷睫D(zhuǎn)化為各種文本格式撼嗓,如ASCII、純文本等欢唾。所以且警,目標格式多種多樣,而且不確定礁遣。所以振湾,設計上,應該讓添加一種格式轉(zhuǎn)化非常容易亡脸,最好不用修改reader本身的代碼押搪。
一種解決辦法是,讓RTFReader類調(diào)用TextConverter對象(該對象負責將RTF轉(zhuǎn)化為另一種文本表示)浅碾。每當RTFReader認出一個RTF標記符時大州,它向TextConverter發(fā)起調(diào)用來轉(zhuǎn)化這個標記。TextConverter對象既負責格式轉(zhuǎn)化垂谢,也負責將標記表示成特定的格式厦画。各種具體格式的轉(zhuǎn)化和表示,通過TextConverter的子類來實現(xiàn)滥朱。如下圖:


RTFReader

從上圖可以看出根暑,每一個Converter子類,實現(xiàn)了具體的復雜對象的創(chuàng)建和組裝徙邻,將這些復雜的邏輯隱藏在了抽象接口之后排嫌,和調(diào)用方reader隔離開來。這樣缰犁,reader只負責解析RTF文檔就可以了淳地。
這里每一個converter類稱為builder,而reader稱為director帅容。這個例子中颇象,Builder模式實現(xiàn)了RTF解析算法和RTF表示格式創(chuàng)建的分離。這讓我們只需要配置不同的TextConverter子類就能夠復用RTFReader的解析邏輯并徘。

說明

Builder模式適用于:

  • the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled.(創(chuàng)建復雜對象的算法應該和對象組裝表示的過程分離遣钳。)
  • the construction process must allow different representations for the object that's constructed.(創(chuàng)建過程必須兼容對象的不同的表示形式,也就是不同的子類麦乞。)

拋除具體場景之后蕴茴,示意圖如下:


BuilderPattern

進一步說明

上面關(guān)于Builder模式的說明是來自《Design Patterns: Elements of Reusable Object-Oriented Software》。里面提到的例子不是特別好理解路幸〖隹可以參考維基百科的說明。
Builder模式主要解決如下問題:

  • How can a class (the same construction process) create different representations of a complex object?(一個類如何創(chuàng)建不同表示的復雜對象简肴。)
  • How can a class that includes creating a complex object be simplified?(如何讓一個創(chuàng)建復雜對象的類變得簡單晃听?)

直接在類內(nèi)部創(chuàng)建和組裝一個復雜對象是非常不靈活的,這樣會導致類內(nèi)部有大量創(chuàng)建復雜對象表示的代碼砰识,不修改類無法修改對象的表示能扒。
Builder模式如下解決這個問題:

  • Encapsulate creating and assembling the parts of a complex object in a separate object.(將復雜對象的創(chuàng)建和組裝封裝在一個單獨的對象中)
  • A class delegates object creation to a Builder object instead of creating the objects directly.(一個類將對象創(chuàng)建的過程委托給一個單獨的Builder對象,而不是自己直接創(chuàng)建辫狼。)

Builder模式的初衷就是將復雜對象的創(chuàng)建和表示分離開來初斑,通過這樣,可以用同一個創(chuàng)建過程創(chuàng)建成不同表示的對象膨处。(The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.)
Builder模式的優(yōu)勢:

  • Allows you to vary a product’s internal representation.(允許你創(chuàng)建一個產(chǎn)品的不同內(nèi)部表示)
  • Encapsulates code for construction and representation.(實現(xiàn)將創(chuàng)建對象和表示對象的代碼封裝)
  • Provides control over steps of construction process.(實現(xiàn)對象構(gòu)件過程的逐步控制)

Builder模式的劣勢:

  • Requires creating a separate ConcreteBuilder for each different type of product.(對于每一種不同的對象见秤,都需要一個單獨的Builder砂竖。)
  • Requires the builder classes to be mutable.(需要Builder類是可變的)。
  • Data members of class aren't guaranteed to be initialized.(類的數(shù)據(jù)成員不保證都被初始化)
  • Dependency injection may be less supported.(對于依賴注入的支持不好鹃答。)

維基百科給出了如下例子:
WikiBuilder.Car:

package WikiBuilder;

/**
 * Created by chengxia on 2019/8/21.
 */
public class Car {
    int wheel;
    String color;

    public Car(int wheel, String color) {
        this.wheel=wheel;
        this.color=color;
    }

    @Override
    public String toString() {
        return "Car [wheels = " + wheel+ ", color = " + color + "]";
    }
}

WikiBuilder.CarBuilder:

package WikiBuilder;

/**
 * The builder abstraction.
 * */
public class CarBuilder {
    int wheel;
    String color;

    public CarBuilder setWheel(int wheel){
        this.wheel=wheel;
        return this;
    }
    public CarBuilder setColor(String color) {
        this.color=color;
        return this;
    }
    public Car getCar() {
        return new Car(wheel,color);
    }
}

WikiBuilder.Client:

package WikiBuilder;

/**
 * Created by chengxia on 2019/8/21.
 */
public class Client {
    public static void main(String[] arg) {
        Car c= new CarBuilder().setWheel(4).setColor("Black").getCar();
        System.out.println(c);
    }
}

運行上面的測試類乎澄,輸出如下:

Car [wheels = 4, color = Black]

Process finished with exit code 0

Builder模式和靜態(tài)內(nèi)部類

builder模式的作用將一個復雜對象的構(gòu)建與他的表示分離,使用者可以一步一步的構(gòu)建一個比較復雜的對象测摔。因此置济,在好多時候,我們可以通過創(chuàng)建一個靜態(tài)內(nèi)部類(區(qū)別于一般的內(nèi)部類锋八,靜態(tài)內(nèi)部類屬于所嵌入的外部類浙于,而不是所嵌入外部類的對象,可以直接通過外部類訪問實例化挟纱,不需要依賴于外部對象)羞酗,來實現(xiàn)Builder模式。如下:
StaticInnerBuilder.Product:

package StaticInnerBuilder;

/**
 * Created by chengxia on 2019/8/22.
 */
public class Product {
    private final int id;
    private final String name;
    private final int type;
    private final float price;

    private Product(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.type = builder.type;
        this.price = builder.price;
    }

    @Override
    public String toString(){
        return "Builder{id:"+id+", name:"+name+", type:"+type+", price:"+price+"}";
    }

    public static class Builder {
        private int id;
        private String name;
        private int type;
        private float price;

        public Builder id(int id) {
            this.id = id;
            return this;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Builder type(int type) {
            this.type = type;
            return this;
        }
        public Builder price(float price) {
            this.price = price;
            return this;
        }

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

}

StaticInnerBuilder.Test:

package StaticInnerBuilder;

/**
 * Created by chengxia on 2019/8/22.
 */
public class Test {
    public static void main(String []args){
        Product p = new Product.Builder().id(1).name("Test").type(3).price(3.14f).build();
        System.out.println(p);
    }
}

從這個例子中樊销,可以看出整慎,這樣寫的時候,可以在構(gòu)造對象的時候采用鏈式寫法围苫,比較有意思裤园。運行前面的測試類輸出如下:

Builder{id:1, name:Test, type:3, price:3.14}

Process finished with exit code 0

工廠方法模式

用途

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
From: Design Patterns: Elements of Reusable Object-Oriented Software

定義一個創(chuàng)建對象的接口,但是讓子類去決定實例化哪個具體類剂府。工廠方法讓一個類將實例化的過程下放到子類中拧揽。

例子

以一個能夠開發(fā)給用戶展示多種Document的應用框架為例。這個框架的兩個重要的類是Application和Document腺占,這兩個類都是抽象的淤袜,使用時必須要根據(jù)具體的應用場景做具體的實現(xiàn)。當我們開發(fā)一個繪圖應用時衰伯,我們要自己實現(xiàn)DrawingApplication和DrawingDocument铡羡。這個Application類負責管理Document,比如當用戶點擊菜單新建時意鲸,需要創(chuàng)建一個新的Document類烦周。
由于具體的要實例化哪個Application子類,是由具體要實現(xiàn)的應用確定的怎顾。Application類并不知道要實例化哪個Document子類读慎,他只知道什么時候創(chuàng)建,僅此而已槐雾。這就進退兩難了:框架需要實例化類夭委,但是它只知道抽象類,但是這個類不能被實例化募强。
這時候株灸,工廠方法提供了一種解決方案:它對創(chuàng)建哪個Document類的子類做了封裝崇摄,然后將這一過程移到框架外。(It encapsulates the knowledge of which Document subclass to create and moves this knowledge out of the framework.)如下圖蚂且。


Document

Application subclasses redefine an abstract CreateDocument operation on Application to return the appropriate Document subclass. Once an Application subclass is instantiated, it can then instantiate application-specific Documents
without knowing their class. We call CreateDocument a factory method because it's
responsible for "manufacturing" an object.
Application類的子類重新寫了一個Application的CreateDocument方法配猫,用來返回一個合適的Document子類。一旦這個Application類的子類實例化完成杏死,它就能夠?qū)嵗虾踹@個Application子類的Document類,而不用知道具體的它們(Document類)具體的類捆交。
我們之所以叫CreateDocument工廠方法淑翼,是因為它負責“生產(chǎn)”對象。

說明

工廠方法模式適用于:

  • 一個類不能預測它將要創(chuàng)建的類對象時品追。
  • 一個類想讓它的子類確定具體實例化哪個類玄括。
  • classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.(這句話翻不了,直接拿過來吧)

拋除具體場景后肉瓦,類圖如下:


FactoryMethod

工廠方法使得無需在代碼中綁定具體應用的類遭京,在代碼中,只需和Product接口打交道泞莉。因此哪雕,能夠適用于任何用戶自定義的具體ConcreteProduct類。
工廠方法一個不好的地方在于鲫趁,用戶必須要創(chuàng)建一個Creator類的子類斯嚎,來創(chuàng)建一個具體的ConcreteProduct對象。如果本來就有這樣的繼承層次還好挨厚,否則有些麻煩堡僻。
前面的例子中,工廠方法只通過Creator調(diào)用疫剃。但是钉疫,有時候,會出現(xiàn)類的平行繼承關(guān)系巢价。如下:


FigureManipulator

在這個例子中牲阁,通過工廠方法,實際上連接了兩個平行的繼承層次:Figure和Manipulator蹄溉。工廠方法定義了兩個類層次之間的聯(lián)系咨油,通過它,實際上實現(xiàn)了類之間聯(lián)系的內(nèi)聚柒爵。

例子

代碼如下:
FactoryMethod.Shape:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public abstract class Shape {

    public String name;
    public Shape(String aName){
        name = aName;
    }
    //畫shape
    public abstract void draw();
    //擦去shape
    public abstract void erase();
}

FactoryMethod.Square:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//方形子類
public class Square extends Shape {

    //構(gòu)造函數(shù)
    public Square(String aName){
        super(aName);
    }

    public void draw() {
        System.out.println("It will draw a Square.");
    }
    public void erase() {
        System.out.println("It will erase a Square.");
    }
}

FactoryMethod.Circle:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
// 圓形子類
public class Circle extends Shape {

    // 構(gòu)造函數(shù)
    public Circle(String aName){
        super(aName);
    }

    public void draw() {
        System.out.println("It will draw a Circle.");
    }
    public void erase() {
        System.out.println("It will erase a Circle.");
    }
}

FactoryMethod.ShapeFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public abstract class ShapeFactory {
    protected abstract Shape factoryMethod(String aName);
    //在operOnShape中定義對Shape的一系列使用操作
    public void operOnShape(String aName){
        Shape s = factoryMethod(aName);
        System.out.println("The current using shape is: " + s.name);
        s.draw();
        s.erase();
    }
}

FactoryMethod.SquareFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//定義返回Square實例的SquareFactory
public class SquareFactory extends ShapeFactory {
    //重載factoryMethod方法,返回Circle對象
    protected Shape factoryMethod(String aName) {
        return new Square(aName + " (created by SquareFactory)");
    }
}

FactoryMethod.CircleFactory:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
//定義返回Circle 實例的CircleFactory
public class CircleFactory extends ShapeFactory {
    //重載factoryMethod方法,返回Circle對象
    protected Shape factoryMethod(String aName) {
        return new Circle(aName + " (created by CircleFactory)");
    }
}

類圖如下:
[圖片上傳失敗...(image-7f4400-1566435759539)]
接下來寫一個測試類役电,來調(diào)動上面的程序結(jié)構(gòu),如下棉胀。
FactoryMethod.TestFactoryMethod:

package FactoryMethod;

/**
 * Created by chengxia on 2019/8/19.
 */
public class TestFactoryMethod {
    public static void main(String[] args){
        ShapeFactory sf1 = new SquareFactory();
        ShapeFactory sf2 = new CircleFactory();
        sf1.operOnShape("Shape one");
        sf2.operOnShape("Shape two");
    }
}

從這個例子可以看出法瑟,ShapeFactory實際上將實例化Shape的職能下放到了子類SquareFactory和CircleFactory冀膝。這樣,它就只需要關(guān)注具體Shape實例的行為霎挟,關(guān)注對于Shape實例的操作窝剖,而無需關(guān)注該實例是如何實例化的。

運行上面的測試例子酥夭,結(jié)果如下:

The current using shape is: Shape one (created by SquareFactory)
It will draw a Square.
It will erase a Square.
The current using shape is: Shape two (created by CircleFactory)
It will draw a Circle.
It will erase a Circle.

Process finished with exit code 0

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赐纱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子熬北,更是在濱河造成了極大的恐慌疙描,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讶隐,死亡現(xiàn)場離奇詭異起胰,居然都是意外死亡,警方通過查閱死者的電腦和手機巫延,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門效五,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炉峰,你說我怎么就攤上這事畏妖。” “怎么了讲冠?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵瓜客,是天一觀的道長。 經(jīng)常有香客問我竿开,道長谱仪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任否彩,我火速辦了婚禮疯攒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘列荔。我一直安慰自己敬尺,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布贴浙。 她就那樣靜靜地躺著砂吞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崎溃。 梳的紋絲不亂的頭發(fā)上蜻直,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼概而。 笑死呼巷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的赎瑰。 我是一名探鬼主播王悍,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼餐曼!你這毒婦竟也來了压储?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤源譬,失蹤者是張志新(化名)和其女友劉穎渠脉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓶佳,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年鳞青,在試婚紗的時候發(fā)現(xiàn)自己被綠了霸饲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡臂拓,死狀恐怖厚脉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胶惰,我是刑警寧澤傻工,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站孵滞,受9級特大地震影響中捆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坊饶,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一泄伪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匿级,春花似錦蟋滴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孤页,卻和暖如春尔苦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工蕉堰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凌净,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓屋讶,卻偏偏與公主長得像冰寻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子皿渗,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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