设计模式第四篇:工厂设计模式
重中之重:代码里的工厂设计模式其实就是由现实中的工厂给抽象出来的。
所以如果你想要了解工厂设计模式是什么,并且灵活的应用工厂设计模式,我的建议是不要直接去看什么工厂设计模式的一堆鬼概念以及用法,而是先了解一下工厂设计模式是怎么由现实中的工厂抽象出来的,这将非常有利于你理解上面两个问题,为什么这么说呢?一来是那些概念有点抽象难懂,越看越蒙圈、越看越烦;二来是如果你根本没有了解工厂设计模式是什么,仅仅是套着别人代码里工厂设计模式的格式来使用,这就纯粹是为了设计模式而设计模式了,死板、达不到举一反三的效果。而先了解工厂设计模式的来源,可以一通百通。
因此本篇主要的内容将是:演示工厂设计模式是如何从现实中的工厂抽象出来的,并优化直接抽象出来的工厂设计模式,最后总结出什么是工厂设计模式、工厂设计模式的好处、怎么使用工厂设计模式及其实际应用场景。
目录
一、什么是工厂设计模式
二、简单工厂模式
1、简单工厂模式举例
2、使用反射机制+静态不可变字符串优化简单工厂模式
3、简单工厂模式总结
三、抽象工厂模式
1、抽象工厂模式举例
2、使用编译指令+反射机制+静态不可变字符串优化抽象工厂模式
3、抽象工厂模式总结
一、什么是工厂设计模式
工厂设计模式是用在创建实例阶段的一种设计模式,它的实现基于多态特性,它的核心设计思想就是你给我一个不同的类型,我就给你创建出不同的类或者系列类。
工厂设计模式可以分为简单工厂模式和抽象工厂模式,下面我们一一介绍。
二、简单工厂模式
1、简单工厂模式举例:演示简单工厂模式是如何从现实中抽象出来的
现实场景:
-
我们以苹果工厂为例,它能制造iPhone和iPad。
-
iPhone和iPad都有它们自己的UDID,而且都可以用来打电话和发短信。
-
然后客户想要造什么,只需要告诉苹果工厂他想要造什么就可以了。
把现实场景抽象成代码:
-
很自然,我们会为苹果工厂创建一个类,命名为AppleFactory,并且为这个类添加制造iPhone和iPad的方法。(但我们是否有必要添加两个方法,分别用来制造iPhone和iPad呢,有待商榷,详见下面第三点)
-
也很自然,我们会创建iPhone和iPad类,但是在为它俩添加属性和方法的时候,我们发现它俩的属性和方法是相同的,哦那我们是不是要为它俩抽象出一个父类AppleProduct来,为它们提供这些共有的东西,然后由它们自己分别去实现呢?这样可以避免为iPhone和iPad两个类写很多重复的代码,会好一些。
-
然后我们知道客户想要造什么的时候,只需要告诉苹果工厂他要造什么就可以了,不需要再做任何额外的事情。考虑到这一点,那么如果像第一点里说的苹果工厂为外界提供了两个方法,分别用来创建iPhone和iPad,那客户就得在想要造什么的时候调用相应的创建方法了,而不是只告诉苹果工厂他要造什么这么个简单操作了,这就违反了现实场景。所以我们只为AppleFactory添加一个创建AppleProduct的方法,只不过这个方法会有一个参数来让客户选择他要造什么,当然AppleFactory也会提供一个枚举来表明它能造什么,供客户选择,同时因为这个方法不需要AppleFactory的实例来调用,所以我们把它定义类方法。
代码如下:
-----------AppleFactory.h-----------
#import <Foundation/Foundation.h>
@class AppleProduct;
typedef NS_ENUM(NSUInteger, AppleProductType) {
AppleProductType_iPhone,
AppleProductType_iPad
};
@interface AppleFactory : NSObject
+ (AppleProduct *)createProductWithType:(AppleProductType)productType;
@end
-----------AppleFactory.m-----------
#import "AppleFactory.h"
#import "iPhone.h"
#import "iPad.h"
@implementation AppleFactory
+ (AppleProduct *)createProductWithType:(AppleProductType)productType {
switch (productType) {
case AppleProductType_iPhone:
return [[iPhone alloc] init];
break;
case AppleProductType_iPad:
return [[iPad alloc] init];
break;
default:
return nil;
break;
}
}
@end
-----------AppleProduct.h-----------
#import <Foundation/Foundation.h>
@interface AppleProduct : NSObject
@property (nonatomic, copy) NSString *udid;
- (void)makeACall;
- (void)sendAMessage;
@end
-----------AppleProduct.m-----------
#import "AppleProduct.h"
@implementation AppleProduct
- (void)makeACall {
}
- (void)sendAMessage {
}
@end
-----------iPhone.h-----------
#import "AppleProduct.h"
@interface iPhone : AppleProduct
@end
-----------iPhone.m-----------
#import "iPhone.h"
@implementation iPhone
- (void)makeACall {
NSLog(@"我用iPhone打电话");
}
- (void)sendAMessage {
NSLog(@"我用iPhone发短信");
}
@end
-----------iPad.h-----------
#import "AppleProduct.h"
@interface iPad : AppleProduct
@end
-----------iPad.m-----------
#import "iPad.h"
@implementation iPad
- (void)makeACall {
NSLog(@"我用iPad打电话");
}
- (void)sendAMessage {
NSLog(@"我用iPad发短信");
}
@end
现在假设ViewController就是客户,那么他如果想要造iPhone就是下面这样:
- (void)viewDidLoad {
[super viewDidLoad];
iPhone *my_iPhone = [AppleFactory produceProductWithType:(AppleProductType_iPhone)];
my_iPhone.udid = @"1234567890";
[my_iPhone makeACall];
[my_iPhone sendAMessage];
}
以上,就完整的模拟了一个苹果工厂的现实场景。
如果是在现实中,这个场景就到此为止了,但是在代码的世界里,我们还可以更进一步。因为如果我们细心去感受,会发现这样创建iPhone的方式好像还不如最原始的alloc、init方式呢,明明就是白白的多写了一个工厂类嘛,创建的时候还和原来差不多,那又何必要这个工厂呢?
的确是这样,但是现在考虑一下,如果现在用户不想创建iPhone了,而是要换成造iPad,那么我们就得把原来的代码删掉,并且重写一份代码为这样:
- (void)viewDidLoad {
[super viewDidLoad];
iPad *my_iPad = [AppleFactory createProductWithType:(AppleProductType_iPad)];
my_iPad.udid = @"1234567890";
[my_iPad makeACall];
[my_iPad sendAMessage];
}
好了,通过这个操作我们这就看到,每切换一次产品,就得删除一次代码并重写一份代码,这时就可以体现出工厂设计模式真正的好处来了,那就是只创建AppleProduct,而不创建具体的iPhone或iPad,这样在切换产品的时候就只需要改变一下产品类型就ok了。如下:
- (void)viewDidLoad {
[super viewDidLoad];
AppleProduct *my_AppleProduct = [AppleFactory produceProductWithType:(AppleProductType_iPhone)];
my_AppleProduct.udid = @"1234567890";
[my_AppleProduct makeACall];
[my_AppleProduct sendAMessage];
}
如果要创建iPad,则只需要把AppleProductType_iPhone
换成AppleProductType_iPad
就可以,其它地方不用动,是不是很舒服?
2、使用反射机制+静态不可变字符串优化简单工厂模式
好了,上面已经已经实现了简单工厂模式,但是现在再考虑一个问题,如果苹果工厂现在新增创建MacBook的功能,那我们除了不可避免的要新增MacBook类之外,我们还需要修改工厂类的工厂方法,新增一个switch的分支。
+ (AppleProduct *)createProductWithType:(AppleProductType)productType {
switch (productType) {
case AppleProductType_iPhone:
return [[iPhone alloc] init];
break;
case AppleProductType_iPad:
return [[iPad alloc] init];
break;
case AppleProductType_MacBook:
return [[MacBook alloc] init];
break;
default:
return nil;
break;
}
}
这很明显是我们不想要的,这违反了开放封闭原则,即当外界代码发生变化时,只能对现有代码进行扩展,不能对原有代码进行修改。所以此处我们可以使用反射机制+静态不可变字符串来对工厂类做一下优化。
- 用静态不可变字符串代替原来的枚举,因为反射机制只能用字符串才能反射出它对应的类
#import <Foundation/Foundation.h>
@class AppleProduct;
// 之所以要写成静态的,是因为万一有人继承使用了该类,如果变量不是静态变量,则子类文件也能访问该变量,就会报重复定义
static NSString * const AppleProductType_iPhone = @"iPhone";
static NSString * const AppleProductType_iPad = @"iPad";
@interface AppleFactory : NSObject
+ (AppleProduct *)createProductWithType:(NSString *)productType;
@end
- 使用反射机制获取类,并创建相应的对象
#import "AppleFactory.h"
@implementation AppleFactory
+ (AppleProduct *)createProductWithType:(NSString *)productType {
Class class = NSClassFromString(productType);
return [[class alloc] init];
}
@end
这样,无论外界新增了多少子类,我们只需要为它们新增一些静态不可变的字符串就可以了,工厂方法的实现不用做变动。
3、简单工厂模式总结
到此,我们就可以对简单工厂模式做一个总结了:
- 1、什么是工厂设计模式
简单工厂模式是由三个部分组成的,工厂类、抽象父类和子类,主要用来创建一些具有相同属性和方法的类。
工厂类:用来创建对象;它会提供一个工厂方法来创建对象(工厂方法是个类方法),该方法的返回值永远都是抽象父类而不是具体的子类,并且该方法会提供一个type参数来让外界选择它要创建什么;工厂类会利用反射机制和静态不可变字符串来实现外界一键切换要创建的类。
抽象父类:这个类是被众多的子类抽象出来的,主要用来为子类提供它们共有的属性和方法。
子类:本来子类是主角,现在倒好,出来了工厂类和抽象父类,一下子整的子类一点存在感都没有了,外界甚至都可以不知道有这么一堆子类存在,子类的用途就是做抽象父类方法的自由实现。
- 2、简单工厂模式的好处
一句话:使用简单工厂模式,再配合反射机制和静态不可变字符串,外界可以实现一键切换要创建的类。
- 3、怎么使用简单工厂模式
首先我们要确定实际的业务中,是否出现了有一些类具备相同属性和方法这种情况,如果出现来才使用简单工厂,否则不使用。在使用简单工厂的时候,我们只需要根据实际的业务,先确定好各个子类,然后由各个子类抽象出抽象父类,再然后创建一个工厂类,确定好这三大组成部分,并按着上面例子的套路来就可以了。
- 4、简单工厂模式的应用场景
如果我们在开发中发现有一些类的结构和行为相同,只是行为的具体实现不同,那我们就可以考虑采用简单工厂来创建它们。(比如说iPhone和iPad都拥有UDID,都可以打电话、发短信,那我们就可以用简单工厂来创建它们。)
三、抽象工厂模式
1、抽象工厂模式举例:演示简单工厂模式是如何从现实中抽象出来的
现实场景:
-
我们以苹果工厂和华为工厂为例,苹果工厂能制造iPhone和iPhone充电器,华为工厂能制造huaPhone和huaPhone充电器。
-
iPhone和huaPhone都有它们自己的UDID,都可以用来打电话;iPhone充电器和huaPhone充电器都可以用来给相应的手机充电。
-
客户先选定想造苹果系列的产品还是华为的系列产品(这里假设要造就得造一套,不能单独造手机或充电器),然后告诉相应的工厂帮他造就可以了。
把现实场景抽象成代码:
-
自然的,我们会想到要创建AppleFactory和HuaWeiFactory,但是由于它俩的行为相同----都是制造手机和充电器,只是具体的实现不同-----一个制造iPhone和iPhone充电器,另一个制造huaPhone和huaPhone充电器。所以我们会为它俩抽象出一个抽象父类ProductFactory来为它俩提供共有的行为,然后再由它俩分别去实现。
-
也自然的,我们会创建iPhone类和huaPhone类,并抽象出它俩的抽象父类Phone类来为它俩提供共有的属性和行为;我们会创建iPhone充电器类和huaPhone充电器类,并抽象出它俩的抽象父类Phone充电器类来为它俩提供共有的属性和行为。
-
因为每个工厂创建的是不同的产品----如苹果工厂创建iPhone和iPhone充电器,这样的话我们就不能像简单工厂那样提供带有type参数的创建方法了,因为iPhone和iPhone充电器根本就不是同一类型,没法再工厂方法里使用多态,所以只能添加直接创建的工厂方法。
代码如下:
-----------Factory----------
@interface ProductFactory : NSObject
- (Phone *)createPhone;
- (PhoneCharger *)createPhoneCharger;
@end
@implementation ProductFactory
- (Phone *)createPhone {
return nil;
}
- (PhoneCharger *)createPhoneCharger {
return nil;
}
@end
@interface AppleFactory : ProductFactory
@end
@implementation AppleFactory
- (Phone *)createPhone {
return [iPhone new];
}
- (iPhoneCharger *)createPhoneCharger {
return [iPhoneCharger new];
}
@end
@interface HuaWeiFactory : ProductFactory
@end
@implementation HuaWeiFactory
- (Phone *)createPhone {
return [huaPhone new];
}
- (huaPhoneCharger *)createPhoneCharger {
return [huaPhoneCharger new];
}
@end
-----------Phone-----------
@interface Phone : NSObject
@property (nonatomic, copy) NSString *udid;
- (void)makeACall;
@end
@implementation Phone
- (void)makeACall {
}
@end
@interface iPhone : Phone
@end
@implementation iPhone
- (void)makeACall {
NSLog(@"我用iPhone打电话");
}
@end
@interface huaPhone : Phone
@end
@implementation huaPhone
- (void)makeACall {
NSLog(@"我用huaPhone打电话");
}
@end
-----------PhoneCharger-----------
@interface PhoneCharger : NSObject
- (void)chargeForPhone;
@end
@implementation PhoneCharger
- (void)chargeForPhone {
}
@end
@interface iPhoneCharger : PhoneCharger
@end
@implementation iPhoneCharger
- (void)chargeForPhone {
NSLog(@"iPhone充电器为iPhone充电");
}
@end
@interface huaPhoneCharger : PhoneCharger
@end
@implementation huaPhoneCharger
- (void)chargeForPhone {
NSLog(@"huaPhone充电器为huaPhone充电");
}
@end
现在假设ViewController就是客户,那么他如果想要造苹果系列产品就是下面这样:
- (void)viewDidLoad {
[super viewDidLoad];
ProductFactory *factory = [[AppleFactory alloc] init];// 选定要造哪个系列的产品
Phone *phone = [factory createPhone];
PhoneCharger *phoneCharger = [factory createPhoneCharger];
[phone makeACall];
[phoneCharger chargeForPhone];
}
如果要创建华为系列的产品,那么只需要将AppleFactory
换成HuaWeiFactory
就可以了,其它代码不用动,苹果系列的一切产品就都切换成华为系列的了,很爽吧。
2、使用编译指令+反射机制+静态不可变字符串优化抽象工厂模式
好了,抽象工厂模式我们也已经实现了,但是现在再考虑一个问题,如果苹果工厂和华为工厂现在新增了一个创建手机壳的功能,那我们除了要在抽象工厂类里添加一下这个方法的声明,麻烦的是要为苹果工厂和华为工厂分别添加并实现这个工厂方法,现在是只有两个工厂,想想万一有十个工厂,那每多添一个产品,我们就要添加十个方法的实现,这很难受。想想有什么办法呢?既然是为子工厂添加方法,子工厂太多,那不如把子工厂全都删掉吧,由抽象工厂来统一控制所有的创建操作,不过需要配合编译指令+反射机制+静态不可变字符串。如下:
-----------ProductFactory-----------
#define Apple 0
#if Apple
static NSString * const kPhoneClass = @"iPhone";
static NSString * const kPhoneChargerClass = @"iPhoneCharger";
#else
static NSString * const kPhoneClass = @"huaPhone";
static NSString * const kPhoneChargerClass = @"huaPhoneCharger";
#endif
@interface ProductFactory : NSObject
+ (Phone *)createPhone;
+ (PhoneCharger *)createPhoneCharger;
@end
@implementation ProductFactory
+ (Phone *)createPhone {
return [NSClassFromString(kPhoneClass) new];
}
+ (PhoneCharger *)createPhoneCharger {
return [NSClassFromString(kPhoneChargerClass) new];
}
@end
-----------ViewController-----------
- (void)viewDidLoad {
[super viewDidLoad];
Phone *phone = [ProductFactory createPhone];// 直接通过抽象工厂来创建
PhoneCharger *phoneCharger = [ProductFactory createPhoneCharger];
[phone makeACall];
[phoneCharger chargeForPhone];
}
这样我们只需要切换一下预编译的值,就可以切换系列了,而且无论新增多少创建功能,也只需要在抽象工厂类里添加一遍就好了,非常nice。
3、抽象工厂模式总结
到此,我们就可以对抽象工厂模式做一个总结了:
- 1、什么是工厂设计模式
抽象工厂模式其实也是由三部分组成的,工厂类、抽象父类和子类,主要用来创建系列类,只不过它的工厂类和简单工厂的工厂类有点区别,它的工厂类其实是由众多小工厂抽象出来的一个抽象工厂,内部的实现也不同于简单工厂的工厂类(详看下)。
工厂类:用来创建对象;只不过它会提供多个工厂方法来分别创建不同的对象(因为抽象工厂主要的用途就是创建系列类,所以这种场景下抽象父类会有多个),这些工厂方法的返回值还是抽象父类,但是不再提供type参数让外界选择需要创建什么(不是不能提供,而是在这种场景下提供已经没有意义了,因为多个工厂方法创建出来的子类是隶属于一个系列的,它们之间会有某些关联,所以提供type参数反而不利于创建出同一系列的东西,故不提供);工厂类会利用编译指令、反射机制和静态不可变字符串来实现外界一键切换要创建的系列类。
抽象父类:同简单工厂。
子类:同简单工厂。
- 2、抽象工厂模式的好处
一句话:使用抽象工厂模式,再配合编译指令、反射机制和静态不可变字符串,外界可以实现一键切换要创建的系列类。
- 3、怎么使用抽象工厂模式
首先我们要确定实际的业务中,是否出现了系列类这种情况,如果出现来才使用抽象工厂,否则不使用。使用抽象工厂的时候,我们只需要根据实际的业务,先确定好各个子类,然后由各个子类抽象出抽象父类,再然后创建一个工厂类,确定好这三大组成部分,并按着上面例子的套路来就可以了。
- 4、抽象工厂模式的应用场景
如果我们在开发中发现有一系列类的结构和行为同另一系列类的结构和行为分别相同,只是行为的具体实现不同,那我们就可以考虑采用抽象工厂来创建它们。(比如说苹果手机和苹果充电器都属于苹果系列,华为手机类和华为充电器都属于华为系列,而苹果手机和华为手机有相同的属性和方法,苹果充电器和华为充电器有相同的属性和方法,那我们就可以用抽象工厂来创建它们。)
此外,简单工厂和抽象工厂最大的区别就是它们它们的应用场景不同,虽然它们核心的设计思想是一样的,但由于是为了解决不同的实际场景而设计的,所以就演化出了区别,我们不能认为抽象工厂是简单工厂的进阶版,它俩不存在谁好谁坏,不同的应用场景,我们应该使用不同的工厂设计模式。iOS中的UIButton、NSNumber、NSSring、NSArray、NSDictionary等都是工厂设计模式的应用。