工厂模式三部曲 - 抽象工厂模式

该文章属于刘小壮原创,转载请注明:刘小壮

配图

这是工厂模式三部曲中的最后一篇了,在这篇文章中将会讲述抽象工厂模式,抽象工厂模式正如其名字一样,非常抽象。但是抽象工厂模式的功能却十分强大,对抽象工厂的利用也非常好。

这篇文章中会像本系列第一篇一样,给出普通实现方式和使用了反射机制的实现两种代码,并且会说明这两种实现方式的区别。并且在文章的最后,会将这三种模式放在一起,对这三种工厂模式进行总结。

本人理解可能不够深刻,这一系列文章中存在的问题,欢迎大家提出,谢谢!😊


什么是抽象工厂模式

简单了解一下

按照惯例,我们先了解一下什么是抽象工厂模式。抽象工厂模式和工厂方法模式很相似,但是抽象工厂模式将抽象发挥的更加极致,是三种工厂模式中最抽象的一种设计模式。抽象工厂模式,也叫做Kit模式,提供了创建一系列相关抽象子类的接口,而无需指定它们具体的类型。

抽象工厂模式中定义了抽象工厂类,抽象工厂类中定义了每个系列的抽象子类创建所需的方法,这些方法对应着不同类型的抽象子类实例化过程。每个工厂子类都对应着一个系列,工厂子类通过重写这些方法来实例化当前系列的抽象子类。

工厂方法模式中抽象子类都是基于同一个抽象类的,是同一个类型的抽象子类,例如加、减、乘、除都属于运算类型。而抽象工厂模式可能会有多个类型的抽象类,抽象子类分别继承自对应类型的抽象类,相同类型的抽象子类都是属于不同系列的

抽象工厂模式包含四部分:
  • 抽象工厂类:定义创建抽象子类的具体行为,根据系列中不同类型的抽象子类可能会有多种行为。
  • 工厂子类:继承自抽象工厂类,根据当前抽象子类对应的系列,重写父类定义的对应行为。对应的抽象子类系列不同,行为的实现方式也不同。
  • 抽象类:定义当前类型抽象子类的操作,子类继承父类完成具体的操作。在抽象工厂模式中,可能会有多种抽象类的定义。
  • 抽象子类:根据当前类型继承自对应的抽象类,并根据系列的不同重写抽象类定义的实现。
我打算先讲一个例子

我们上面讲了系列的概念,这里将会用一个例子来理解系列和抽象类的关系。假设现在需要用SqliteCoreData两种不同的方式进行本地持久化,持久化的内容都是用户信息、搜索信息、设置信息三部分。

就拿Sqlite持久化方式来说,Sqlite就是使用Sqlite数据库持久化方式的系列,下面对应着用户信息、搜索信息、设置信息三个类型,每个类型就是一个抽象类。除了Sqlite这种持久化方式外,还有CoreData这种持久化方式,这是两个不同的持久化方式,所以属于两个不同的系列。

SqliteCoreData都代表着不同的系列,其下面都分别对应着用户信息、搜索信息、设置信息三个类型的层级,在这种层级关系中,Sqlite的用户信息抽象子类对应着CoreData的用户信息抽象子类,这两个抽象子类都属于同一个类型,继承自同一个抽象类,分别被不同系列的工厂子类创建。在抽象设计模式中,不同系列相同类型的抽象子类都是一一对应的

SqliteCoreData属于不同的系列,所以是两个不同的工厂子类,这两个工厂子类具有相同的行为,就是用户信息、搜索信息、设置信息三部分的数据持久化,这就是三种不同的持久化类型,也就是我们上面说的类型。这三个行为定义在抽象工厂类中,抽象工厂类中定义每个系列的抽象子类创建方法,SqliteCoreData继承自抽象工厂类,并分别实现继承过来的抽象子类创建方法。

通过上面的例子,我们可以清晰的理解工厂类、抽象类、系列三者之间的关系,理解这三者的关系可以有助于我们更好的理解抽象设计模式。

和工厂方法模式有什么不同?

在工厂方法模式中,工厂子类负责抽象子类的实例化,每个工厂子类对应着一个抽象子类,且具有唯一性。而在抽象工厂模式中,一个工厂子类代表一个系列,工厂子类根据当前系列对不同类型的抽象子类进行创建。工厂方法模式中工厂子类对应的是一个类型的抽象子类,抽象工厂模式对应的是一个系列的抽象子类

工厂方法模式一个工厂子类对应一个抽象子类的设计,会有很大的浪费,产生了过多的类。而抽象工厂模式更好的利用了工厂子类,使每个工厂子类对应着一个系列的抽象子类,这种设计非常适用于两个具有相同结构关系,但是分属于不同系列的系列之间的切换。

总之就是,工厂方法模式是针对单个类型的抽象类,而抽象工厂模式是针对具有相同结构的一系列类型的抽象类


业务场景

在上面讲到了数据持久化的例子,我们的业务场景也根据上面的例子提出。

iOS中比较常用的数据持久化方案,应该就包括SqliteCoreData了,可能Sqlite的灵活性使其更加受欢迎。业务就是需要用SqliteCoreData两种不同的方式进行本地持久化,持久化的内容是用户信息、搜索信息、设置信息三部分。

通过抽象工厂模式实现上面的需求,可以很方便的进行本地持久化方案的切换,下面的例子中将会演示一行代码切换数据持久化方案的例子。

UML类图

我们根据上面的业务场景画了一个UML类图,下面类图中为了让大家看得更清晰,所以用不同颜色的线区分开了对应的类和功能

下面的黑色箭头是抽象子类和抽象类的继承关系;红色是用户工厂子类对应的抽象子类;黄色是搜索工厂子类对应的抽象子类;绿色是设置工厂子类对应的抽象子类。

抽象工厂模式

在这个UML类图中,我们可以清晰的看出,之前工厂方法模式的工厂子类对应的是单一类型的抽象子类,上面抽象工厂模式的工厂子类对应的是同一系列多个类型的抽象子类,更好的利用了工厂子类,适合更加复杂的业务需求。抽象工厂类的方法定义也和工厂方法模式不太一样,由于工厂方法模式只创建一个抽象子类,所以直接用的类方法定义,抽象方法模式可能会创建多个类型的抽象子类,所以用的实例方法定义。

普通方式代码实现

这里代码实现按照上面举的例子,代码结构也完全按照上面UML类图中画的结构,使整篇文章可以更加统一,更深刻的理解这个设计模式。

代码量比较多,但是为了更好的体现出抽象工厂模式,所以就全贴出来了。

首先创建两个Model类,这两个Model类并不属于抽象工厂模式结构的一部分,只是为了更好的体现出面向模型开发。
@interface User : NSObject
@property (nonatomic, copy  ) NSString  *userName;
@property (nonatomic, assign) NSInteger userAge;
@end
@implementation User
@end

@interface City : NSObject
@property (nonatomic, copy) NSString *cityName;
@property (nonatomic, copy) NSString *cityCode;
@end
@implementation City
@end
用户信息系列相关类
@interface UserInfo : NSObject
- (void)setUserName:(User *)name;
@end
@implementation UserInfo
- (void)setUserName:(User *)name {}
@end

@interface SqliteUserInfo : UserInfo
@end
@implementation SqliteUserInfo
- (void)setUserName:(User *)name {
    NSLog(@"这里编写数据库持久化方案");
}
@end

@interface CoreDataUserInfo : UserInfo
@end
@implementation CoreDataUserInfo
- (void)setUserName:(User *)name {
    NSLog(@"这里编写CoreData持久化方案");
}
@end
搜索信息系列相关类
@interface SearchInfo : NSObject
- (void)setSearchCity:(City *)city;
@end
@implementation SearchInfo
- (void)setSearchCity:(City *)city {}
@end

@interface SqliteSearchInfo : SearchInfo
@end
@implementation SqliteSearchInfo
- (void)setSearchCity:(City *)city {
    NSLog(@"这里编写数据库持久化方案");
}
@end

@interface CoreDataSearchInfo : SearchInfo
@end
@implementation CoreDataSearchInfo
- (void)setSearchCity:(City *)city {
    NSLog(@"这里编写CoreData持久化方案");
}
@end
设置信息系列相关类
@interface SettingsInfo : NSObject
- (void)resetAllSettings;
@end
@implementation SettingsInfo
- (void)resetAllSettings {}
@end

@interface SqliteSettingsInfo : SettingsInfo
@end
@implementation SqliteSettingsInfo
- (void)resetAllSettings {
    NSLog(@"重置数据库设置信息");
}
@end

@interface CoreDataSettingsInfo : SettingsInfo
@end
@implementation CoreDataSettingsInfo
- (void)resetAllSettings {
    NSLog(@"重置CoreData设置信息");
}
@end
工厂抽象相关类
@interface Factory : NSObject
- (UserInfo *)CreateUserInfo;
- (SearchInfo *)CreateSearchInfo;
- (SettingsInfo *)CreateSettingsInfo;
@end
@implementation Factory
- (UserInfo *)CreateUserInfo {
    return nil;
}
- (SearchInfo *)CreateSearchInfo {
    return nil;
}
- (SettingsInfo *)CreateSettingsInfo {
    return nil;
}
@end

@interface SqliteFactory : Factory
@end
@implementation SqliteFactory
- (UserInfo *)CreateUserInfo {
    return [SqliteUserInfo new];
}
- (SearchInfo *)CreateSearchInfo {
    return [SqliteSearchInfo new];
}
- (SettingsInfo *)CreateSettingsInfo {
    return [SqliteSettingsInfo new];
}
@end

@interface CoreDataFactory : Factory
@end
@implementation CoreDataFactory
- (UserInfo *)CreateUserInfo {
    return [CoreDataUserInfo new];
}
- (SearchInfo *)CreateSearchInfo {
    return [CoreDataSearchInfo new];
}
- (SettingsInfo *)CreateSettingsInfo {
    return [CoreDataSettingsInfo new];
}
@end
外界使用
- (void)viewDidLoad {
    User *user = [User new];
    City *city = [City new];

    Factory *factory = [SqliteFactory new];
    UserInfo *userInfo = [factory CreateUserInfo];
    SearchInfo *searchInfo = [factory CreateSearchInfo];
    SettingsInfo *settingsInfo = [factory CreateSettingsInfo];

    [userInfo setUserName:user];
    [searchInfo setSearchCity:city];
    [settingsInfo resetAllSettings];
}

到此为止我们就编写完抽象工厂设计模式的代码了,上面抽象工厂模式的示例中定义了三个类型的抽象类UserInfoSearchInfoSettingsInfo,抽象子类分别继承不同类型的抽象类,并实现不同系列的持久化代码。这三个类型的抽象类中定义了两种不同的数据持久化方案,分别是Sqlite存储和CoreData存储,这就是两种数据持久化系列,分别用SqliteFactoryCoreDataFactory表示这两个系列。

代码中定义了抽象工厂类Factory类,并定义了三个抽象接口用来实例化不同类型的抽象子类,两个工厂子类SqliteFactoryCoreDataFactory继承自抽象工厂类,内部分别实现了两种不同系列的抽象子类实例化,例如SqliteFactory中会实例化关于Sqlite存储方式的抽象子类,并通过抽象工厂类中定义的抽象接口返回给外界使用。

统一控制工厂子类的切换

这三种工厂设计模式中除了简单工厂模式,工厂方法模式和抽象工厂模式都需要外界实例化不同的工厂子类,这种在外界实例化工厂子类的代码可能会出现在多个地方,而在很多业务需求中都需要我们统一切换某个功能,从代码上来说就是切换工厂子类,怎样可以做到统一切换工厂子类的需求呢?

就拿上面的持久化方案的例子来说,我们定义了两种持久化方案,通过SqliteFactoryCoreDataFactory工厂子类来创建不同的持久化方案。假设现在我们项目比较庞大,代码量比较多,并且在多个地方用到了SqliteFactory工厂子类,现在需求是切换为CoreDataFactory的持久化方案,我们应该怎样快速的切换持久化方案?

其实我们可以通过typedef定义别名的方式切换工厂子类,在其他地方只需要使用我们typedef定义的别名就可以,例如下面代码就可以做到修改一处typedef定义,就修改了整个项目的持久化方案。

还是按照上面的抽象工厂模式的代码,这里只写出外界使用的代码部分
typedef SqliteFactory SaveFactory; //定义的工厂子类别名

@implementation TestTableViewController
- (void)viewDidLoad {
    User *user = [User new];
    City *city = [City new];

    Factory *factory = [SaveFactory new];
    UserInfo *userInfo = [factory CreateUserInfo];
    SearchInfo *searchInfo = [factory CreateSearchInfo];
    SettingsInfo *settingsInfo = [factory CreateSettingsInfo];

    [userInfo setUserName:user];
    [searchInfo setSearchCity:city];
    [settingsInfo resetAllSettings];
}

从上面的代码可以看到,我们定义了一个SaveFactory的工厂子类别名,下面直接通过这个别名进行的工厂子类的实例化。因为如果存储相同的内容,项目中只会出现一种持久化方案,所以我们只需要修改typedef的定义,就可以切换整个项目的持久化方案

配合反射机制优化代码

对于抽象工厂模式的反射机制,实现方式和之前的简单工厂模式不太一样,我采用的是预编译指令加常量字符串类名反射的方式实现的。别的不多说,先上代码来看看,我这里贴出了主要代码,其他一样的地方我就不往外贴了,不浪费大家时间。

还是按照上面的业务场景,我定义了两种同名不同值的字符串常量,这些常量字符串对应的就是抽象子类的类名,一个条件分支就是一个系列的抽象子类,通过预编译指令#if来进行不同系列的抽象子类间的统一切换,定义了SAVE_DATA_MODE_SQLITE宏定义来控制系列的切换。这种定义方式可以更方便的进行不同系列间的切换,从使用上来看非常像我们用预编译指令替换了之前的工厂子类,实际上从代码的角度上来说这种方式对系列间的切换更加统一和方便。

#define SAVE_DATA_MODE_SQLITE 1

#if SAVE_DATA_MODE_SQLITE
static NSString * const kUserInfoClass     = @"SqliteUserInfo";
static NSString * const kSearchInfoClass   = @"SqliteSearchInfo";
static NSString * const kSettingsInfoClass = @"SqliteSettingsInfo";
#else
static NSString * const kUserInfoClass     = @"CoreDataUserInfo";
static NSString * const kSearchInfoClass   = @"CoreDataSearchInfo";
static NSString * const kSettingsInfoClass = @"CoreDataSettingsInfo";
#endif

下面是工厂类的定义,使用反射机制的抽象工厂模式刨除了工厂子类,只用一个工厂类来进行操作子类的实例化,这种方式和之前的简单工厂模式非常相似。不同的是之前的简单工厂模式只需要初始化一个类型的抽象子类,而抽象工厂模式需要初始化多个类型的抽象子类

由于我们采用了反射机制,并且由预编译指令进行系列间的切换,所以这里就直接使用类方法了,哪里用就直接实例化抽象子类即可,不存在工厂子类之间的选择问题了。

@interface Factory : NSObject
+ (UserInfo *)CreateUserInfo;
+ (SearchInfo *)CreateSearchInfo;
+ (SettingsInfo *)CreateSettingsInfo;
@end

@implementation Factory
+ (UserInfo *)CreateUserInfo {
    return [[NSClassFromString(kUserInfoClass) alloc] init];
}
+ (SearchInfo *)CreateSearchInfo {
    return [[NSClassFromString(kSearchInfoClass) alloc] init];
}
+ (SettingsInfo *)CreateSettingsInfo {
    return [[NSClassFromString(kSettingsInfoClass) alloc] init];
}
@end

通过这种方式进行不同系列的切换,只需要修改一个宏定义的值即可,也就是SAVE_DATA_MODE_SQLITE后面的1切换为0的步骤,这种方式是符合我们开放封闭原则的。以后每个系列增加新的类型后,只需要将新增加的两个类名对应添加在预编译指令中,在工厂方法中扩展一个实例化新类型的方法即可。这种方式对扩展是开放的,对修改是关闭的

对于上面的示例代码的编写需要注意一下,按照苹果的命名规范,常量的作用域如果只在一个类中,前面就用小写k修饰,如果修饰的常量在其他类中用到,也就是.h文件中用extern修饰的常量,不需要用小写k修饰。我们在苹果的很多官方代码和Kit中也可以看到相同的定义,宏定义也是相同的规则。(extern修饰的常量在.m中不要用static修饰)

项目中如果用到任何预编译指令,在修改重新运行前,一定要clear一下,清除缓存,否则会因为缓存导致bug

抽象工厂模式的优缺点

优点

抽象工厂模式正如其名字一样,理解起来非常抽象,正是因为这种抽象,使得抽象工厂模式非常强大和灵活,比其他两种工厂设计模式要强大很多。抽象工厂模式可以创建多个系列,并且每个系列抽象子类一一对应,这种强大的功能是其他两种工厂模式都不能实现的。

通过抽象工厂模式统一控制多个系列的抽象子类,可以用多个系列的抽象子类完成一些复杂的需求。例如我们文中说到的本地持久化方案的切换,最后通过我们的不断优化,做到只需要修改一个预编译指令的参数即可切换整个数据持久化方案,这是其他工厂模式所不能完成的。

抽象工厂模式延续了工厂模式的优点,外界接触不到任何类型的抽象子类,而只需要知道不同类型的抽象类即可,抽象子类的创建过程都在工厂子类中。这种设计方式充分的利用了面向对象语言中的多态特性,使灵活性大大提升。而且抽象工厂模式是非常符合开放封闭原则的,对扩展的开放以及对修改的封闭都完美支持

缺点

抽象工厂模式带来的缺点也是显而易见的,最明显的缺点就是模式比较庞大,所以需要在适合的业务场景使用这种模式,否则会适得其反。


工厂模式三部曲总结

示例

到目前为止,工厂模式三部曲中的三种工厂模式都已经讲完了,在这里我们将简单回顾和总结一下这三种设计模式。首先,我将根据三种工厂模式画三张同样需求的UML类图,看完这三张类图大家就对三种工厂模式清晰明了了。

需求就以现在比较火的乐视系列的乐视TV、乐视手机,小米系列的小米TV、小米手机来作为需求,这三张图主要体现工厂模式的整体架构。

简单工厂模式
工厂方法模式
抽象工厂模式

从这三张图中可以看出,简单工厂模式和工厂方法模式对应的是同一类型的操作结构,在当前例子中就是手机类型,因为只有一个类型,所以还没有系列的概念。在需求不太复杂,并且不需要多个系列间的切换时,可以考虑使用这两种设计模式。

之前的业务只有手机一种类型,在业务复杂之后出现了一个新类型的产品-电视,这时候工厂类就需要增加一种类型。由于需求更加复杂,这时候就出现了系列的概念(之前工厂方法模式中类型单一,所以不需要系列的概念),乐视系列和小米系列,工厂子类变成了每个工厂子类对应一个系列的设计,每个系列中对应不同类型的产品。

抽象工厂模式对应多个类型的操作结构,分属于不同的系列。这种结构比较适合复杂的业务需求,例如文中将的Sqlite数据库和CoreData两种存储方式的切换,通过抽象工厂模式就非常好实现。

工厂模式总结

在这三种设计模式中都有一个共同的特点,就是继承自抽象类的抽象子类或工厂子类,都必须对抽象类定义的方法给出对应的实现(可以相同,也可以不同),这种模式才叫做工厂模式。工厂模式的核心就是抽象和多态,抽象子类继承自抽象类,对抽象类中定义的方法和属性给出不同的实现方式,通过多态的方式进行方法实现和调用,构成了工厂模式的核心。

在工厂类中对开放封闭原则有着完美的体现,对扩展的开放以及对修改的封闭。例如最抽象的抽象工厂模式,抽象工厂模式中增加新的系列,直接扩展一个工厂子类及对应的抽象子类,对整个模式框架不会带来其他影响。如果增加一个新的类型,创建新的类型对应的类,并对整个抽象工厂类及其子类进行方法扩展。

在外界使用抽象子类的功能时,不需要知道任何关于抽象子类的特征,抽象子类也不会出现在外界,外界只需要和抽象类打交道就可以。工厂模式将抽象子类的创建和实现分离,具体的创建操作由工厂类来进行,抽象子类只需要关注业务即可,外界不需要知道抽象子类实例化的过程。这种方式非常灵活并易于扩展,而且在大型项目中尤为明显,可以很好的避免代码量过多的问题。

对于这三种工厂模式的选择,我建议如果是像Sqlite数据库和CoreData切换,这样业务中存在多个系列的需求,使用抽象工厂模式。如果比较简单的创建单个类型的抽象子类,这种方式建议用简单工厂模式或工厂方法模式。三种设计模式的选择还是要看需求和项目复杂度,用得好的话可以给代码带来极大的灵活性和扩展性

总结

设计模式主要是一种思想方面的东西,没有任何一种设计模式是万能的,并适应于各种业务场景的设计模式。所以在不同的地方使用对应的设计模式,或者说根据业务需要设计一种适合当前业务场景的设计模式,这才是最理想的设计模式用法。


前段时间写了关于工厂模式的系列文章,这系列文章理解起来比较难懂。应广大读者的需要,这段时间专门给这系列文章补了Demo

Demo只是来辅助读者更好的理解文章中的内容,应该博客结合Demo一起学习,只看Demo还是不能理解更深层的原理Demo中代码都会有注释,各位可以打断点跟着Demo执行流程走一遍,看看各个阶段变量的值。

Demo地址刘小壮的Github

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

推荐阅读更多精彩内容