独孤九剑--设计模式(iOS创建型篇)

独孤九剑--设计模式(iOS结构型篇)
独孤九剑--设计模式(iOS行为型篇)

前言

如果把开发看做是武林世界,底层原理、算法等就是内功心法,那编程语言、设计模式等无疑是外功招式;只专注一门达到精通级别,有人也能独领风骚;内外兼修那才是至尊王者方能称霸武林;

设计模式就是独孤九剑,剑谱由前辈实践总结,一招一式精妙绝伦,照着修炼亦可大有所为;

设计模式

设计模式就是前人经过实践,归纳总结的一套针对特定问题的代码设计解决方案;它就是一套解决特定问题的模板,在遇到类似问题时,可以直接套用对应的模板,高效、高质量的完成开发;

作用
  • 降低开发成本,提高开发效率;
  • 代码实现更优雅、更容易被他人理解;
  • 提高代码复用率、可维护性、可拓展性;
设计原则

设计模式进行设计时需要遵循六大设计原则:

  • 单一职责
    类的设计尽量做到只有一个原因引起变化,一个类只专注做一件事
  • 开闭原则
    软件实体如类、模块和方法应该对扩展开放,对修改关闭;
    功能迭代修改代码时,我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改内部原有的代码来完成变化;
  • 依赖倒置原则
    高层模块不应该依赖低层模块,两者都应该依赖其抽象;
    抽象不应该依赖细节;
    细节应该依赖抽象;
    在iOS中更加精简的定义就是 面向协议编程
  • 接口分离原则
    具体类不应该依赖它不需要的接口;
    类间的依赖关系应该建立在最小的接口上;
    精简的定义就是:接口/协议尽量细分,如UITableViewDelegate/UITableViewDataSource,都是用于tableView的协议,但是系统按照数据源和事件响应细分了2个
  • 里氏替换原则
    所有使用基类的地方必须能透明地使用其子类,也即子类能替代基类使用;
    如NSArray使用的地方都可以用NSMutableArray替代
  • 迪米特法则
    一个对象应该对其他对象有最少的了解;
    A耦合了B,A应该只需知道耦合的对应接口,而不需要知道B更多的细节实现;
UML图

学习、使用设计模式需要读懂UML图(侧重类图),有必要的还需要自己能画图;

类型
  • 创建型
    原型模式、单例模式、工厂模式、建造者模式;
  • 结构型
    适配器模式、代理模式、外观模式、桥接模式、装饰器模式、组合模式、享元模式
  • 行为型
    策略模式、观察者模式、模板方法模式、命令模式、备忘录模式、访问者模式、中介者模式、状态模式、责任链模式

主流的设计模式有20多种,下面只介绍下在iOS中可能比较重要的一些设计模式

单例模式 Singleton Pattern

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

适用场景

  • 系统只需要一个实例对象,如系统要求提供一个唯一的文件管理对象;或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

单例模式不应滥用,单例模式的对象是唯一的且可以全局使用,涉及到数据更改容易导致数据错乱、线程不安全等问题,将很难维护;另一个问题是单例会隐性地让毫不相关的类产生耦合等问题;

单例模式应该是iOS开发者最熟悉、最常用的一种模式,很容易理解,这里就不多费笔墨;但是对于熟悉的东西往往容易忽略一些细节;

Objective-C单例的简单实现

@implementation CameraManager

+ (CameraManager *)defaultManager {
    static CameraManager *shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (shareInstance == nil) {
            shareInstance = [[CameraManager alloc] init];
        }
    });
    
    return  shareInstance;
}

@end

可能很多小伙伴编码就到此结束,但这里有个潜在的问题;其他语言,如C++,java等,构造方法可以隐藏。OC中的方法,实际上都是公开的,虽然我们提供了一个方便的类方法的访问入口,但是里面的alloc方法依旧是可以调用到的。也就是说依旧可以使用alloc的方式创建对象,每次alloc都是一个新的实例,也就违背单例模式只有一个实例的作用。

Objective-C单例的完善

  1. 无论是采用哪种方式创建,保证给出的对象是同一个;
    在对象创建的时候,无论是alloc还是new,都会调用到 allocWithZone方法。在通过拷贝创建对象时,都会会调用到copyWithZonemutableCopyWithZone方法。因此,可以重写这些方法,让创建的对象唯一。
+ (CameraManager *)defaultManager {
....
            shareInstance = [[super allocWithZone:nil] init];
....
    return  shareInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [CameraManager defaultManager];
}
  1. 通过其他方式创建对象时,直接抛错禁止创建
    可以是编译时抛错,还可以是运行时抛出异常;
  • 编译报错
@interface CameraManager : NSObject
+ (CameraManager *)defaultManager;

+ (instancetype) alloc __attribute__((unavailable("call sharedInstance instead")));
+ (instancetype) new __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
- (instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
@end
  • 运行时抛异常
- (instancetype)init {
    NSAssert(NO, @"There can only be one CameraManager instance.");
    // 或者
//    [NSException raise:NSInternalInconsistencyException format:@"There can only be one CameraManager instance."];
    
    return nil;
}

系统库中就有很多单例应用:UIApplication、NSFileManager、NSNotificationCenter 等等...
实测发现NSFileManager、NSNotificationCenter都没有处理其他alloc的情况;UIApplication使用的是运行时抛错的方式禁用其他创建方式;

Swift单例的实现

基于语言特性,Swift实现单例将特别简单、完美

class CameraManager {
    static let defaultManager = CameraManager()
    
    private init() {}
}

工厂模式

工厂模式主要是为创建对象提供过渡接口,将对象的创建具体细节使用工厂类封装屏蔽起来,提高调用的灵活性;另外对象的创建和对象本身业务处理分离,降低了系统的耦合度,使得两者修改起来都相对容易;

工厂模式可以分为三类:简单工厂模式、工厂方法模式、抽象工厂模式

简单工厂模式 Simple Factory Pattern

简单工厂模式专门定义一个工厂类来负责创建其他类的实例,工厂类可以根据参数的不同返回不同类的实例。被创建的实例通常都具有共同的父类(或实现同一个接口/协议)。

UML类图
factory-simple.png
  • Factory:工厂角色
    负责实现创建所有实例的内部逻辑
  • Product:抽象产品角色
    所创建的对象的父类(抽象类、接口、协议)
  • ConcreteProduct:具体产品角色
    所创建的对象具体类的实例。
示例

App需要使用不同的外接相机,根据连接的wifi名区分不同相机并实例化:
Product: 相机公有协议:

@protocol CameraProtocol <NSObject>

@optional
- (void)connect;
- (void)take;

@end

ConcreteProduct: 各个具体相机对象

@implementation XhwCamera
- (void)connect {
    NSLog(@"xhw connect");
}

- (void)take {
    NSLog(@"xhw take");
}
@end

@implementation AzyCamera
- (void)connect {
    NSLog(@"azy connect");
}

- (void)take {
    NSLog(@"azy take");
}
@end
// 相机工厂类 负责创建不同相机对象 (对应Factory)
@implementation CameraFactory
+ (id<CameraProtocol>)createCameraWifi:(NSString *)wifi {
    if ([wifi isEqualToString:@"xhw"]) {
        return [[XhwCamera alloc] init];
    } else if ([wifi isEqualToString:@"azy"]) {
        return [[AzyCamera alloc] init];
    }
    
    return nil;
}
@end

调用方使用:

id<CameraProtocol> camera = [CameraFactory createCameraWifi:@"xhw"];
[camera connect];
[camera take];
适用场景
  • 优点

    1. 工厂类封装了必要的判断逻辑,决定创建哪个类;使用者不必关心具体的创建逻辑,实现了解耦;同时代码也更容易维护
    2. 当存在类似功能的不同类时,使用者使用时容易搞混;交由工厂类统一管理,更方便使用者调用;
  • 缺点

    1. 工厂类集中了所有产品创建逻辑,一旦不能工作,整个系统将会受到影响
    2. 违背“开闭原则”,一旦添加新产品就必须修改工厂类的逻辑;当产品类越来越多,工厂类的逻辑也会更多更乱,不利于系统扩展和维护
    3. 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

基于以上优缺点,简单工厂模式适用场景:

  1. 使用方只知道传入工厂类的参数,并不关心创建对象的逻辑时;
  2. 工厂类负责创建的产品类比较少时。

工厂方法模式 Factory Method Pattern

工厂方法模式是在简单工厂模式的基础上,进一步抽象化和推广的模式。工厂方法模式里不再只由一个工厂类决定实例化哪些产品类,而是把产品类的创建交给抽象工厂的子类去完成。

简单工厂模式中工厂类做了大量的创建逻辑,一个工厂耦合多个产品类;工厂方法模式抽象了工厂类,具体的工厂类只负责创建一个产品类,避免了简单工厂的一些缺点;

UML类图
factory-method.png

Product:抽象产品角色;ConcreteProduct:具体产品角色
Factory:抽象工厂角色;ConcreteFactory:具体工厂角色

示例

还是简单工厂那个例子,现在将其重构为工厂方法:

抽象工厂:

@protocol CameraFactoryProtocol <NSObject>
// 实例方法、类方法均可以

// 实例方法
//- (id<CameraProtocol>)createCamera;

// 类方法 创建具体的相机(产品)
+ (id<CameraProtocol>)createCamera;

@end

具体的工厂(有多少种产品就多少种工厂):

@implementation XhwCameraFactory
+ (id<CameraProtocol>)createCamera {
    return [[XhwCamera alloc] init];
}
@end


@implementation AzyCameraFactory
+ (id<CameraProtocol>)createCamera {
    return [[AzyCamera alloc] init];
}
@end

具体产品类不用更改;
调用方使用:

id<CameraProtocol>camera = [XhwCameraFactory createCamera];
[camera connect];
[camera take];
适用场景
  • 优点

    1. 包含了简单工厂的优点
    2. 符合开闭原则:新增一种产品时,只需要扩展相应的具体产品类和具体的工厂类,无需修改原有工厂类;
    3. 符合单一职责原则:每个具体工厂类只负责创建对应的产品
    4. 可以使用实例工厂方法,可以形成基于继承的等级结构
  • 缺点

    1. 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销
    2. 一个具体工厂只能创建一种具体产品

基于以上优缺点,工厂方法模式适用场景:

  1. 基于简单工厂的适用场景;
  2. 工厂只会对应一种产品;
  3. 后续可能扩展产品

抽象工厂模式 Abstract Factory Pattern

工厂方法模式在一定程度上优化了简单工厂的缺点,但是其一个具体工厂只能创建一种具体产品;抽象工厂模式就是对工厂方法模式的进一步优化;
抽象工厂模式设计了多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类可以派生出多个具体工厂类。每个具体工厂类可以创建多个具体产品的实例;

UML类图
factory-abstrac.png

AbstractFactory:抽象工厂;ConcreteFactory:具体工厂
AbstractProduct:抽象产品;Product:具体产品

示例

基于工厂方法模式的例子,现在增加一个需求:除了连接相机外还需要支持连接不同厂家的音响设备;还是Xhw和Azy两个厂家,按照工厂方法模式的设计,那么就需要新建一套音响抽象工厂类、2个具体的音响工厂类、1个抽象产品类、2个具体产品类;调用方使用时,也同样需要先创建另一个工厂;
其实不管是相机设备,还是音响设备,虽然是不同产品但是都是相同厂家;相机和音响都属于外接设备,完全可以在高一层的抽象出外接设备工厂;这时相当于把相机和音响当做了一个产品族

实现代码:

  1. 抽象工厂 (更高抽象)
@protocol PeripheralFactory <NSObject>

// 外接设备目前支持 Camera、Speaker两种产品
- (id<CameraProtocol>)createCamera;
- (id<SpeakerProtocol>)createSpeaker;

@end
  1. 新建新增的抽象产品
@protocol SpeakerProtocol <NSObject>

- (void)connect;
- (void)play;

@end
  1. 具体工厂 (创建对应的产品)
@implementation XhwFactory
- (nonnull id<CameraProtocol>)createCamera {
    return [[XhwCamera alloc] init];
}

- (nonnull id<SpeakerProtocol>)createSpeaker {
    return [[XhwSpeaker alloc] init];
}
@end


@implementation AzyFactory
- (nonnull id<CameraProtocol>)createCamera {
    return [[AzyCamera alloc] init];
}

- (nonnull id<SpeakerProtocol>)createSpeaker {
    return [[AzySpeaker alloc] init];
}
@end

使用:

AzyFactory *fc = [[AzyFactory alloc] init];
id<CameraProtocol> camera = [fc createCamera];
[camera connect];
id<SpeakerProtocol> speaker = [fc createSpeaker];
[speaker connect];
[speaker play];
适用场景

先引入2个概念:

产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

  • 优点
    1. 包含工厂方法模式的优点
    2. 具体工厂可以创建多个产品
    3. 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式
    4. 符号开闭原则:增加新的具体工厂和产品族很方便,无须修改已有系统。
  • 缺点
    1. 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦),抽象工厂角色中设计了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改;
    2. 产品等级结构、产品族较多时,代码量也成倍增加;

基于以上优缺点,抽象工厂模式适用场景:

  1. 基于工厂方法模式的适用场景;
  2. 系统有多个系列产品,而系统中只消费其中某一系列产品

iOS系统中的工厂模式

iOS中有类簇的概率,其实它就是使用了工厂模式;
如 NSArray,NSMutableArray;NSNumber等;

    id arrAlloc = [NSArray alloc];
    NSArray *arr = [arrAlloc init];
    id marrAlloc = [NSMutableArray alloc];
    NSMutableArray *marr = [marrAlloc init];
    NSLog(@"%@", NSStringFromClass([arrAlloc class]));
    NSLog(@"%@", NSStringFromClass([arr class]));
    NSLog(@"%@", NSStringFromClass([marrAlloc class]));
    NSLog(@"%@", NSStringFromClass([marr class]));

输出结果:

__NSPlaceholderArray
__NSArray0
__NSPlaceholderArray
__NSArrayM

NSArray真实类型是__NSArray0,NSMutableArray真实类型
__NSArrayM;在alloc分配内存后,创建的是一个中间对象__NSPlaceholderArray,等到init时才会动态决定创建对应的对象;

Apple并没有开源Foundation源码,但是可以通过GNUStep的Foundation代码窥探其实现,可以发现的确是将创建对象的工作转交给了中间对象;

造者模式 Builder Pattern

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节;

UML类图
builder.png

Builder:抽象建造者,ConcreteBuilder:具体建造者
Director:指挥者,Product:产品角色

示例

我们再完善下上面示例的外接相机代码;
外接相机在connect前,需要保证内部模块都初始化完成;相机现在分为3大部分:拍摄模块,wifi模块,存储模块;

@interface XhwCamera : NSObject<CameraProtocol>

// 实际开发中这3部分比较复杂, 现在为了演示简单都使用字符串替代
@property (nonatomic, strong) NSString *shootModule;
@property (nonatomic, strong) NSString *wifiModule;
@property (nonatomic, strong) NSString *storageModule;

// 拍摄配置 如分辨率、帧率等
@property (nonatomic, strong) NSString *shootConfig;

@end

简单做法
在Camera对象init时,将这3部分一起初始化;
但想象下,如果shootModule,wifiModule,storageModule都比较复杂,需要写一大堆配置代码时Camera的init将也会臃肿复杂:

implementation XhwCamera

- (instancetype)init {
    //...
    //...
    _shootModule = ...
    
    //...
    //...
    _wifiModule = ...
}

当shootModule,wifiModule,storageModule需要调整依赖关系或者顺序关系时,还有当这些模块初始化需要由使用者传入参数时,XhwCamera将也很难维护;

使用建造者模式

  1. 抽象建造者,声明建造需要的接口
@protocol CameraBuilderProtocol <NSObject>

- (void)buildShootModule;       // 拍摄模块
- (void)buildWifiModule;       // wifi连接模块
- (void)buildStorageModule;   // 存储模块

// 返回构建对象
- (id<CameraProtocol>)build;

@end
  1. 具体建造者:xhw相机
@implementation XhwCameraBuilder

- (instancetype)init {
    if (self = [super init]) {
        _camera = [[XhwCamera alloc] init];
    }
    
    return self;
}

- (void)buildShootModule {
    _camera.shootConfig = @"默认配置";
    //.... 复杂过程
    _camera.shootModule = @"拍摄模块初始完成";
}

- (void)buildStorageModule {
    //.... 复杂过程
    _camera.storageModule = @"存储模块初始完成";
}

- (void)buildWifiModule {
    //.... 复杂过程
    _camera.wifiModule = @"wifi模块初始完成";
}

- (nonnull id<CameraProtocol>)build {
    return _camera;
}

@end
  1. 构建的Director,控制builder项及顺序、依赖等
@implementation CameraDirector

- (void)constuctBuilder:(id<CameraBuilderProtocol>)builder {
    [builder buildShootModule];
    [builder buildWifiModule];
    [builder buildStorageModule];
}

@end
  1. 使用
XhwCameraBuilder *builder = [[XhwCameraBuilder alloc] init];
CameraDirector *director = [[CameraDirector alloc] init];
[director constuctBuilder:builder];
id<CameraProtocol> camera = [builder build];
[camera connect];

完整的建造者模式
如果拍摄的配置需要由客户设置,而不是使用默认的;builder需要支持参数设置;

以拍摄增加 分辨率、帧率 配置为例,代码重构如下:

  1. builder增加设置的接口
- (void)setShootConfig:(NSString *)config {
    _camera.shootConfig = config;
}
  1. CameraDirector增加传入参数
- (void)constuctBuilder:(id<CameraBuilderProtocol>)builder resolution:(NSString *)resolution frameRate:(NSString *)frameRate {
    NSString *config = [NSString stringWithFormat:@"拍摄配置:分辨率=%@,帧率=%@", resolution, frameRate];
    // buildShootModule初始化需要在shootConfig之后 这个由Director控制了
    [builder setShootConfig:config];
    [builder buildShootModule];
    [builder buildWifiModule];
    [builder buildStorageModule];
}

另一种方式,可以为不同的配置创建不同的具体建造者;
在使用时,只需要更换不同的建造者就可以创建不同配置的产品实例;
比如新建一个 高配 的相机builder,相机所有配置都是用更高性能的设置,

@implementation XhwCameraHeightBuilder

- (void)buildShootModule {
    _camera.shootConfig = @"拍摄配置:分辨率=1280x720,帧率=30";
    _camera.shootModule = @"拍摄模块初始完成";
}

- (void)buildStorageModule {
    // ...默认高级配置
    _camera.storageModule = @"存储模块初始完成";
}
适用场景
  • 优点

    1. 易于解耦
      将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品;符号依赖倒置原则。
    2. 易于精确控制对象的创建
      将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
    3. 易于拓展
      增加新的具体建造者无需修改原有类库的代码,易于拓展,符合开闭原则。
  • 缺点
    如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

基于以上优缺点,该模式适用场景:

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个子模块(或成员属性)。
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
  3. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

完整代码


参考:
https://www.jianshu.com/p/6e5eda3a51af
https://design-patterns.readthedocs.io/zh_CN/latest/index.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容