iOS 抽象工厂模式
iOS 抽象工厂模式
什么是抽象工厂模式
简单了解一下
按照惯例,我们先了解一下什么是抽象工厂模式。抽象工厂模式和工厂方法模式很相似,但是抽象工厂模式将抽象发挥的更加极致,是三种工厂模式中最抽象的一种设计模式。抽象工厂模式,也叫做Kit模式,提供了创建一系列相关抽象子类的接口,而无需指定它们具体的类型。
抽象工厂模式中定义了抽象工厂类,抽象工厂类中定义了每个系列的抽象子类创建所需的方法,这些方法对应着不同类型的抽象子类实例化过程。每个工厂子类都对应着一个系列,工厂子类通过重写这些方法来实例化当前系列的抽象子类。
工厂方法模式中抽象子类都是基于同一个抽象类的,是同一个类型的抽象子类,例如加、减、乘、除都属于运算类型。而抽象工厂模式可能会有多个类型的抽象类,抽象子类分别继承自对应类型的抽象类,相同类型的抽象子类都是属于不同系列的。
抽象工厂模式包含四部分:
- 抽象工厂类:定义创建抽象子类的具体行为,根据系列中不同类型的抽象子类可能会有多种行为。
- 工厂子类:继承自抽象工厂类,根据当前抽象子类对应的系列,重写父类定义的对应行为。对应的抽象子类系列不同,行为的实现方式也不同。
- 抽象类:定义当前类型抽象子类的操作,子类继承父类完成具体的操作。在抽象工厂模式中,可能会有多种抽象类的定义。
- 抽象子类:根据当前类型继承自对应的抽象类,并根据系列的不同重写抽象类定义的实现。
我打算先讲一个例子
我们上面讲了系列的概念,这里将会用一个例子来理解系列和抽象类的关系。假设现在需要用Sqlite和CoreData两种不同的方式进行本地持久化,持久化的内容都是用户信息、搜索信息、设置信息三部分。
就拿Sqlite持久化方式来说,Sqlite就是使用Sqlite数据库持久化方式的系列,下面对应着用户信息、搜索信息、设置信息三个类型,每个类型就是一个抽象类。除了Sqlite这种持久化方式外,还有CoreData这种持久化方式,这是两个不同的持久化方式,所以属于两个不同的系列。
Sqlite和CoreData都代表着不同的系列,其下面都分别对应着用户信息、搜索信息、设置信息三个类型的层级,在这种层级关系中,Sqlite的用户信息抽象子类对应着CoreData的用户信息抽象子类,这两个抽象子类都属于同一个类型,继承自同一个抽象类,分别被不同系列的工厂子类创建。在抽象设计模式中,不同系列相同类型的抽象子类都是一一对应的。
Sqlite和CoreData属于不同的系列,所以是两个不同的工厂子类,这两个工厂子类具有相同的行为,就是用户信息、搜索信息、设置信息三部分的数据持久化,这就是三种不同的持久化类型,也就是我们上面说的类型。这三个行为定义在抽象工厂类中,抽象工厂类中定义每个系列的抽象子类创建方法,Sqlite和CoreData继承自抽象工厂类,并分别实现继承过来的抽象子类创建方法。
通过上面的例子,我们可以清晰的理解工厂类、抽象类、系列三者之间的关系,理解这三者的关系可以有助于我们更好的理解抽象设计模式。
和工厂方法模式有什么不同?
在工厂方法模式中,工厂子类负责抽象子类的实例化,每个工厂子类对应着一个抽象子类,且具有唯一性。而在抽象工厂模式中,一个工厂子类代表一个系列,工厂子类根据当前系列对不同类型的抽象子类进行创建。工厂方法模式中工厂子类对应的是一个类型的抽象子类,抽象工厂模式对应的是一个系列的抽象子类。
工厂方法模式一个工厂子类对应一个抽象子类的设计,会有很大的浪费,产生了过多的类。而抽象工厂模式更好的利用了工厂子类,使每个工厂子类对应着一个系列的抽象子类,这种设计非常适用于两个具有相同结构关系,但是分属于不同系列的系列之间的切换。
总之就是,工厂方法模式是针对单个类型的抽象类,而抽象工厂模式是针对具有相同结构的一系列类型的抽象类。
业务场景
在上面讲到了数据持久化的例子,我们的业务场景也根据上面的例子提出。
在iOS中比较常用的数据持久化方案,应该就包括Sqlite和CoreData了,可能Sqlite的灵活性使其更加受欢迎。业务就是需要用Sqlite和CoreData两种不同的方式进行本地持久化,持久化的内容是用户信息、搜索信息、设置信息三部分。
通过抽象工厂模式实现上面的需求,可以很方便的进行本地持久化方案的切换,下面的例子中将会演示一行代码切换数据持久化方案的例子。
UML类图
我们根据上面的业务场景画了一个UML类图,下面类图中为了让大家看得更清晰,所以用不同颜色的线区分开了对应的类和功能。
下面的黑色箭头是抽象子类和抽象类的继承关系;红色是用户工厂子类对应的抽象子类;黄色是搜索工厂子类对应的抽象子类;绿色是设置工厂子类对应的抽象子类。
抽象工厂模式
在这个UML类图中,我们可以清晰的看出,之前工厂方法模式的工厂子类对应的是单一类型的抽象子类,上面抽象工厂模式的工厂子类对应的是同一系列多个类型的抽象子类,更好的利用了工厂子类,适合更加复杂的业务需求。抽象工厂类的方法定义也和工厂方法模式不太一样,由于工厂方法模式只创建一个抽象子类,所以直接用的类方法定义,抽象方法模式可能会创建多个类型的抽象子类,所以用的实例方法定义。
普通方式代码实现
这里代码实现按照上面举的例子,代码结构也完全按照上面UML类图中画的结构,使整篇文章可以更加统一,更深刻的理解这个设计模式。
代码量比较多,但是为了更好的体现出抽象工厂模式,所以就全贴出来了。
首先创建两个Model类,这两个Model类并不属于抽象工厂模式结构的一部分,只是为了更好的体现出面向模型开发。
//.h
@interface User : NSObject
@property (nonatomic, copy ) NSString *userName;
@property (nonatomic, assign) NSInteger userAge;
@end
//.m
@implementation User
@end
//.h
@interface City : NSObject
@property (nonatomic, copy) NSString *cityName;
@property (nonatomic, copy) NSString *cityCode;
@end
//.m
@implementation City
@end
用户信息系列相关类
//.h
@interface UserInfo : NSObject
- (void)setUserName:(User *)name;
@end
//.m
@implementation UserInfo
- (void)setUserName:(User *)name {}
@end
//.h
@interface SqliteUserInfo : UserInfo
@end
//.m
@implementation SqliteUserInfo
- (void)setUserName:(User *)name {
NSLog(@"这里编写数据库持久化方案");
}
@end
//.h
@interface CoreDataUserInfo : UserInfo
@end
//.m
@implementation CoreDataUserInfo
- (void)setUserName:(User *)name {
NSLog(@"这里编写CoreData持久化方案");
}
@end
搜索信息系列相关类
//.h
@interface SearchInfo : NSObject
- (void)setSearchCity:(City *)city;
@end
//.m
@implementation SearchInfo
- (void)setSearchCity:(City *)city {}
@end
//.h
@interface SqliteSearchInfo : SearchInfo
@end
//.m
@implementation SqliteSearchInfo
- (void)setSearchCity:(City *)city {
NSLog(@"这里编写数据库持久化方案");
}
@end
//.h
@interface CoreDataSearchInfo : SearchInfo
@end
//.m
@implementation CoreDataSearchInfo
- (void)setSearchCity:(City *)city {
NSLog(@"这里编写CoreData持久化方案");
}
@end
设置信息系列相关类
//.h
@interface SettingsInfo : NSObject
- (void)resetAllSettings;
@end
//.m
@implementation SettingsInfo
- (void)resetAllSettings {}
@end
//.h
@interface SqliteSettingsInfo : SettingsInfo
@end
//.m
@implementation SqliteSettingsInfo
- (void)resetAllSettings {
NSLog(@"重置数据库设置信息");
}
@end
//.h
@interface CoreDataSettingsInfo : SettingsInfo
@end
//.m
@implementation CoreDataSettingsInfo
- (void)resetAllSettings {
NSLog(@"重置CoreData设置信息");
}
@end
工厂抽象相关类
//.h
@interface Factory : NSObject
- (UserInfo *)CreateUserInfo;
- (SearchInfo *)CreateSearchInfo;
- (SettingsInfo *)CreateSettingsInfo;
@end
//.m
@implementation Factory
- (UserInfo *)CreateUserInfo {
return nil;
}
- (SearchInfo *)CreateSearchInfo {
return nil;
}
- (SettingsInfo *)CreateSettingsInfo {
return nil;
}
@end
//.h
@interface SqliteFactory : Factory
@end
//.m
@implementation SqliteFactory
- (UserInfo *)CreateUserInfo {
return [SqliteUserInfo new];
}
- (SearchInfo *)CreateSearchInfo {
return [SqliteSearchInfo new];
}
- (SettingsInfo *)CreateSettingsInfo {
return [SqliteSettingsInfo new];
}
@end
//.h
@interface CoreDataFactory : Factory
@end
//.m
@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];
}
到此为止我们就编写完抽象工厂设计模式的代码了,上面抽象工厂模式的示例中定义了三个类型的抽象类UserInfo、SearchInfo、SettingsInfo,抽象子类分别继承不同类型的抽象类,并实现不同系列的持久化代码。这三个类型的抽象类中定义了两种不同的数据持久化方案,分别是Sqlite存储和CoreData存储,这就是两种数据持久化系列,分别用SqliteFactory和CoreDataFactory表示这两个系列。
代码中定义了抽象工厂类Factory类,并定义了三个抽象接口用来实例化不同类型的抽象子类,两个工厂子类SqliteFactory和CoreDataFactory继承自抽象工厂类,内部分别实现了两种不同系列的抽象子类实例化,例如SqliteFactory中会实例化关于Sqlite存储方式的抽象子类,并通过抽象工厂类中定义的抽象接口返回给外界使用。
统一控制工厂子类的切换
这三种工厂设计模式中除了简单工厂模式,工厂方法模式和抽象工厂模式都需要外界实例化不同的工厂子类,这种在外界实例化工厂子类的代码可能会出现在多个地方,而在很多业务需求中都需要我们统一切换某个功能,从代码上来说就是切换工厂子类,怎样可以做到统一切换工厂子类的需求呢?
就拿上面的持久化方案的例子来说,我们定义了两种持久化方案,通过SqliteFactory和CoreDataFactory工厂子类来创建不同的持久化方案。假设现在我们项目比较庞大,代码量比较多,并且在多个地方用到了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。
抽象工厂模式的优缺点
优点
抽象工厂模式正如其名字一样,理解起来非常抽象,正是因为这种抽象,使得抽象工厂模式非常强大和灵活,比其他两种工厂设计模式要强大很多。抽象工厂模式可以创建多个系列,并且每个系列抽象子类一一对应,这种强大的功能是其他两种工厂模式都不能实现的。
通过抽象工厂模式统一控制多个系列的抽象子类,可以用多个系列的抽象子类完成一些复杂的需求。例如我们文中说到的本地持久化方案的切换,最后通过我们的不断优化,做到只需要修改一个预编译指令的参数即可切换整个数据持久化方案,这是其他工厂模式所不能完成的。
抽象工厂模式延续了工厂模式的优点,外界接触不到任何类型的抽象子类,而只需要知道不同类型的抽象类即可,抽象子类的创建过程都在工厂子类中。这种设计方式充分的利用了面向对象语言中的多态特性,使灵活性大大提升。而且抽象工厂模式是非常符合开放封闭原则的,对扩展的开放以及对修改的封闭都完美支持。
缺点
抽象工厂模式带来的缺点也是显而易见的,最明显的缺点就是模式比较庞大,所以需要在适合的业务场景使用这种模式,否则会适得其反。
iOS 抽象工厂模式的更多相关文章
- IOS设计模式浅析之抽象工厂模式(Abstract Factory)
概述 在前面两章中,分别介绍了简单工厂模式和工厂方法模式,我们知道简单工厂模式的优点是去除了客户端与具体产品的依赖,缺点是违反了“开放-关闭原则”:工厂方法模式克服了简单工厂模式的缺点,将产品的创建工 ...
- iOS常用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)
1. 简单工厂模式 如何理解简单工厂,工厂方法, 抽象工厂三种设计模式? 简单工厂方法包含:父类拥有共同基础接口,具体子类实现子类特殊功能,工厂类根据参数区分创建不同子类实例.该场景对应的UML图如下 ...
- iOS经常使用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)
1. 简单工厂模式 怎样理解简单工厂,工厂方法. 抽象工厂三种设计模式? 简单工厂的生活场景.卖早点的小摊贩.他给你提供包子,馒头,地沟油烙的煎饼等,小贩是一个工厂.它生产包子,馒头,地沟油烙的煎饼. ...
- iOS 简单工厂模式
iOS 简单工厂模式 什么是简单工厂模式? 简单工厂模式中定义一个抽象类,抽象类中声明公共的特征及属性,抽象子类继承自抽象类,去实现具体的操作.工厂类根据外界需求,在工厂类中创建对应的抽象子类实例并传 ...
- Python—程序设计:抽象工厂模式
抽象工厂模式 内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象. 例:生产一部手机,需要手机壳.CPU.操作系统三类对象进行组装,其中每类对象都有不同的种类.对每个具体工厂,分别生 ...
- PHP设计模式(三)抽象工厂模式(Abstract Factory For PHP)
一.什么是抽象工厂模式 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件: 系统中有多个产品族,而系统一次只可能消费其中一族产品. 同 ...
- 面向对象设计模式纵横谈:Abstract Factory 抽象工厂模式(笔记记录)
今天是设计模式的第二讲,抽象工厂的设计模式,我们还是延续老办法,一步一步的.演变的来讲,先来看看一个对象创建的问题. 1.如何创建一个对象 常规的对象创建方法: 这样的创建对象没有任何问题, ...
- Objective-C 工厂模式(下) -- 抽象工厂模式
相比简单工厂模式, 只有一个工厂 能生产的手机也是固定的 抽象工厂模式类似于有很多家工厂, 当用户要买什么手机就创建对应的工厂去生产 比如用户要买iPhone就创建一个Apple工厂来生产手机, 要买 ...
- Net设计模式实例之抽象工厂模式(Abstract Factory Pattern)
一.抽象工厂模式简介(Bref Introduction) 抽象工厂模式(Abstract Factory Pattern),提供一个创建一系列相关或者相互依赖对象的接口,而无需制定他们的具体类.优点 ...
随机推荐
- Lua中的协同程序 coroutine
Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...
- 爬虫技术 -- 基础学习(一)HTML规范化(附特殊字符编码表)
最近在做网页信息提取这方面的,由于没接触过这系列的知识点,所以逛博客,看文档~~看着finallyly大神的博文和文档,边看边学习边总结~~ 对网站页面进行信息提取,需要进行页面解析,解析的方法有以下 ...
- Android获取屏幕长宽
总结了下,我遇到的获取Android屏幕长宽的方式总共有三种.大同小异,重点在于如何获取系统中的WindowManager管理类对象,方可对数据的操作: 代码如下 /** * @return 屏幕的长 ...
- TFS(Team Foundation Server)敏捷使用教程(四):工作项跟踪(1)
工作项跟踪(1) 可跟踪性是软件过程的重要能力,TFS主要是以工作项来实现过程的可跟踪性.曾有人问:"你们实际项目里的工作项是怎么样的?能不能让我们看看?"我也一直很好奇别的公司T ...
- Vue基础---->VueJS的使用(一)
Vue.js是一个构建数据驱动的web界面的库.它的目标是通过尽可能简单的API 实现响应的数据绑定和组合的视图组件,今天我们就开始vue.js的学习. vue的安装及使用 一.vue的下载地址:ht ...
- 鼠标经过(hover)事件的延时处理
关于鼠标hover事件及延时 鼠标经过事件为web页面上最常见的事件之一.简单的hover可以用CSS :hover伪类实现,复杂点的用js. 一般情况下,我们是不对鼠标hover事件进行延时处理.但 ...
- SQL Server里因丢失索引造成的死锁
在今天的文章里我想演示下SQL Server里在表上丢失索引如何引起死锁(deadlock)的.为了准备测试场景,下列代码会创建2个表,然后2个表都插入4条记录. -- Create a table ...
- SQL语句技巧:查询时巧用OR实现逻辑判断
首先看以下SQL逻辑语句块: ) ) SET @fieldname='chassisno' --这里可传入chassisno,plateno,owner,contacttelno其中之一或不传 SET ...
- Weblogic魔法堂:AdminServer.lok被锁导致启动、关闭域失败
一.判断AdminServer.lok被其进程锁死 >weblogic.management.ManagementException: Unable to obtain lock on **** ...
- [Latex]生成Vertical Timeline
Vertical TimeLine 用Latex生成一个竖直的VerticalTimeline的想法来源于今天翻看王老师的教师寄语,有感于学院走过的操作系统实验的艰辛之路,遂产生了写一个"小 ...