iOS中的设计模式——工厂模式(Factory)
06 Sep 2016
工厂模式分为简单工厂模式和抽象工厂模式。
工厂方法是抽象工厂模式的组成部分。
开始之前,我们先看下简单工厂模式,作为warming up
。
简单工厂模式
通过工厂类根据参数负责创建具体的产品,即工厂类在工厂方法中通过对参数进行条件判断(if
、switch
)来创建不同的实例)。
简单工厂模式的主要参与者:
-
工厂(
Factory
)角色:接受客户端的请求,通过请求负责创建相应的产品对象。 -
抽象产品(
AbstractProduct
)角色: 是工厂模式所创建对象的父类或是共同拥有的接口。可是抽象类或接口。 -
具体产品(
ConcreteProduct
)对象:工厂模式所创建的对象都是这个角色的实例。
//简单工厂模式的主要实现
+ (Product *)createProductWithType:(ProductType)type {
switch (type) {
case ProductTypeOne:
return [[ProductOne alloc] init];
break;
case ProductTypeTwo:
return [[ProductTwo alloc] init];
break;
case ProductTypeThree:
return [[ProductThree alloc] init];
break;
default:
break;
}
return nil;
}
理解起来比较简单,但是有个问题——由于简单工厂模式不仅对扩展开放,而且对修改也开放,所以违反了“开放-关闭原则”。说人话:当需要扩展一个产品时,不仅需要新建一个产品类,同时还要修改工厂方法。另外,所有产品对象创建的逻辑都被集中到了工厂类中,并不优雅。OC中可以使用runtime
来进行优化。
//使用字符串参数
+ (Product *)createProductWithType:(NSString *)product {
if (!product || [product isEqualToString:@""]) {
return nil;
}
Class class = NSClassFromString(product);
Product *p = [[class alloc] init];
if (p) {
return p;
}
return nil;
}
//使用类型参数
+ (Product *)createProductWithClass:(Class)productClass {
if (!productClass) {
return nil;
}
Product *p = [[productClass alloc] init];
if (p) {
return p;
}
return nil;
}
工厂方法
工厂方法(Factory
):定义创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。
说人话:工厂方法模式将之前负责生成具体抽象子类的工厂类,抽象为工厂抽象类和工厂子类组成的一系列类。每创建一个抽象子类,就需要创建一个工厂子类,并且一一对应,由工厂子类去生成对应的抽象子类,由外界使用方来决定生成哪个工厂子类。这样在增加新的需求时,就不需要对工厂抽象类进行修改,而是对应新增的抽象子类创建对应的工厂子类即可。
主要参与者
-
抽象工厂角色:与应用程序无关,任何在模式中创建对象的工厂必须实现这个接口。
-
具体工厂角色:实现了抽象工厂接口的具体类,含有与引用密切相关的逻辑,并且受到应用程序的调用以创建产品对象。
-
抽象产品角色:工厂方法所创建产品对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
-
具体产品角色:这个角色实现了抽象产品角色所声名的接口。工厂方法所创建的每个具体产品对象都是某个具体产品角色的实例。
类图
抽象的Product
(产品)定义了工厂方法创建的对象的接口。ConcreteProduct
实现了Product
接口。Creator
定义了返回Product
对象的工厂方法。它也可以为工厂方法定义一个默认实现,返回默认ConcreteProduct
对象。Creator
的其他操作可以调用此工厂方法创建Product
对象。ConcreteProduct
是Creator
的子类。它重载了工厂方法,用来返回ConcreteProduct
的实例。
使用场景
-
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
-
这个系统有多于一个的产品族,而系统只消费其中某一产品族。
-
同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
-
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
实现
//抽象产品
@interface Product : NSObject
@end
@implementation Product
@end
//具体产品1
@interface ProductOne : Product
@end
@implementation ProductOne
@end
//具体产品2
@interface ProductTwo : Product
@end
@implementation ProductTwo
@end
//抽象工厂
@interface Factory : NSObject
+ (Product *)createProduct;
@end
@implementation Factory
+ (Product *)createProduct {
return [[Product alloc] init];//如果Product是抽象的,那么这里可以返回nil
}
@end
//具体产品1的具体工厂
@interface ProductOneFactory : Factory
+ (Product *)createProduct;
@end
@implementation ProductOneFactory
+ (Product *)createProduct {
return [[ProductOne alloc] init];
}
@end
//具体产品2的具体工厂
@interface ProductTwoFactory : Factory
+ (Product *)createProduct;
@end
@implementation ProductTwoFactory
+ (Product *)createProduct {
return [[ProductTwo alloc] init];
}
@end
//客户端调用
Product *p = [ProductOneFactory createProduct];
NSLog(@"%@", p);
Product *p2 = [ProductTwoFactory createProduct];
NSLog(@"%@", p2);
2016-09-07 16:05:29.837 OCDemo[16036:284508] <ProductOne: 0x7ff6106b34d0>
2016-09-07 16:05:29.838 OCDemo[16036:284508] <ProductTwo: 0x7ff6106a8d20>
当需求发生改变时,外界只需要将工厂子类调用类方法的类名换一下即可,其他地方都不用发生变化。这样做就像一个“开关”一样,在外界由这个工厂子类的类型控制着抽象子类的实例化类型,而我们并不知道抽象子类实例化的过程。
优点
工厂方法模式的的优点在于更大的灵活性,增加或删除某个产品都不会对其他地方造成影响,更佳符合开放封闭原则。
而且对抽象的使用更佳深入,将工厂类也抽象为了抽象工厂类和工厂子类,外界调用更加灵活,这也是对多态的一种体现。
缺点
工厂方法模式的缺点也是非常显而易见的,工厂方法模式中新增一个抽象子类,意味着工厂子类要跟着成对增加(OC中要x4😭),这样会造成生成过多的类,工厂方法模式的复杂度也会随之增加。
抽象工厂模式
抽象工厂模式:提供了创建一系列相关抽象子类的接口,而无需指定它们具体的类型。也叫做Kit模式。
抽象工厂模式和工厂方法模式很相似,但是抽象工厂模式将抽象发挥的更加极致,是三种工厂模式中最抽象的一种设计模式。
抽象工厂模式中定义了抽象工厂类,抽象工厂类中定义了每个系列的抽象子类创建所需的方法,这些方法对应着不同类型的抽象子类实例化过程。每个工厂子类都对应着一个系列,工厂子类通过重写这些方法来实例化当前系列的抽象子类。
抽象工厂模式与工厂方法模式
两者都用于相同的目的:创建对象而不让客户端知道返回了什么确切的具体对象。
在工厂方法模式中,工厂子类负责抽象子类的实例化,每个工厂子类对应着一个抽象子类,且具有唯一性。而在抽象工厂模式中,一个工厂子类代表一个系列,工厂子类根据当前系列对不同类型的抽象子类进行创建。工厂方法模式中工厂子类对应的是一个类型的抽象子类,抽象工厂模式对应的是一个系列的抽象子类。
工厂方法模式一个工厂子类对应一个抽象子类的设计,会有很大的浪费,产生了过多的类。而抽象工厂模式更好的利用了工厂子类,使每个工厂子类对应着一个系列的抽象子类,这种设计非常适用于两个具有相同结构关系,但是分属于不同系列的系列之间的切换。
说人话:工厂方法模式是针对单个类型的抽象类,而抽象工厂模式是针对具有相同结构的一系列类型的抽象类。
抽象工厂 | 工厂方法 |
---|---|
通过对象组合创建抽象产品 | 通过类继承创建抽象产品 |
创建多系列产品 | 创建一种产品 |
必须修改父类的接口才能支持新的产品 | 子类化创建者并重载工厂方法以创建新产品 |
主要参与者
-
抽象工厂(
AbstractFactory
)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统商业逻辑无关的。 -
具体工厂(
ConcreteFactory
)角色:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。 -
抽象产品(
AbstractProduct
)角色:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。 -
具体产品(
ConcreteProduct
)角色:抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。这是客户端最终需要的东西,其内部一定充满了应用系统的商业逻辑。
类图
客户端只知道抽象工厂(AbstractFactory)和抽象产品(AbstractProduct)。每个工厂类中,结构与实际操作的细节按黑箱对待。设置产品也不知道谁将负责创建它们。只有具体工厂知道为客户端创建什么、如果创建(大多数时候,它都用工厂方法模式来实现)。工厂方法把实际的创建过程推迟到重载它的子类中。在类图中,createProductA
和createProductB
是工厂方法。最初的抽象方法什么也不创建。抽象工厂模式常与原型模式、单例模式和享元模式等其他设计模式一起使用。
使用场景
-
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
-
这个系统有多于一个的产品族,而系统只消费其中某一产品族。
-
同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
-
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
实现
//抽象工厂
@interface BrandingFactory : NSObject
+ (BrandingFactory *)factory;
- (Hatchback *)createHatchback;
- (SUV *)createSUV;
@end
@implementation BrandingFactory
+ (BrandingFactory *)factory {
if ([[self class] isSubclassOfClass:[ToyotaBrandingFactory class]]) {
return [[ToyotaBrandingFactory alloc] init];
} else if ([[self class] isSubclassOfClass:[FordBrandingFactory class]]) {
return [[FordBrandingFactory alloc] init];
} else {
return nil;
}
}
- (Hatchback *)createHatchback {
return nil;
}
- (SUV *)createSUV {
return nil;
}
@end
// 具体工厂
@interface FordBrandingFactory : BrandingFactory
@end
@implementation FordBrandingFactory
- (Hatchback *)createHatchback {
FordHatchback *hatchback = [[FordHatchback alloc] init];
return hatchback;
}
- (SUV *)createSUV {
FordSUV *suv = [[FordSUV alloc] init];
return suv;
}
@end
@interface ToyotaBrandingFactory : BrandingFactory
@end
@implementation ToyotaBrandingFactory
- (Hatchback *)createHatchback {
ToyotaHatchback *hatchback = [[ToyotaHatchback alloc] init];
return hatchback;
}
- (SUV *)createSUV {
ToyotaSUV *suv = [[ToyotaSUV alloc] init];
return suv;
}
@end
//抽象产品
@interface Hatchback : NSObject
@end
@implementation Hatchback
@end
@interface SUV : NSObject
@end
@implementation SUV
@end
//具体产品
@interface FordHatchback : Hatchback
@end
@implementation FordHatchback
@end
@interface ToyotaHatchback : Hatchback
@end
@implementation ToyotaHatchback
@end
@interface FordSUV : SUV
@end
@implementation FordSUV
@end
@interface ToyotaSUV : SUV
@end
@implementation ToyotaSUV
@end
OC中类太多了简直有点吃不消啊。JAVA实现起来肯定更优雅些。😝
Cocoa
中的抽象工厂模式
创建NSNumber
实例的方式完全符合抽象工厂模式。
创建Cocoa
对象有两种方式:使用先alloc
后init
的方法,或者使用类中的+ className...
方法。在Cocoa
的基础框架中,NSNumber
类有很多类方法用于创建各种类型的NSNumber
对象。
NSNumber *boolNumber = [NSNumber numberWithBool:YES];
NSNumber *charNumber = [NSNumber numberWithChar:'a'];
NSNumber *intNumber = [NSNumber numberWithInteger:2];
每个返回的对象属于代表最初输入值的不同私有子类。打印如下:
2016-09-08 10:30:28.415 OCDemo[1164:29635] __NSCFBoolean
2016-09-08 10:30:28.416 OCDemo[1164:29635] __NSCFNumber
2016-09-08 10:30:28.416 OCDemo[1164:29635] __NSCFNumber
接受不同类型的参数并返回NSNumber
实例的类方法是类工厂方法。NSNumber
是抽象工厂实现的一个例子。基础框架中抽象工厂的此种特点被称为“类簇”(Class Cluster
)。
类簇是基础框架中一种常见的设计模式,基于抽象工厂模式的思想。它将若干相关的私有具体工厂子类集合到一个工友的抽象超类指下。例如,“数”包含了各种数值类型的完整集合,如字符、整数、浮点数和双精度输。这些数值类型是”数“的字集。所以NSNumber
自然成为这些数子类型的超类型(super-type
)。NSNumber
有一系列共有API,定义了各种类型的数所共有的行为。客户端在使用时无需知道NSNumber
实例的具体类型。
类簇是抽象工厂的一种形式。比如,NSNumber
本身是一个高度抽象的工厂,而NSCFBoolean
和NSCFNumber
是具体的工厂子类。子类是具体工厂,因为他们重载了NSNumber
中声明的公有工厂方法以生产产品。例如,intValue
和boolValue
根据实际NSNumber
对象的内部值返回一个值,虽说值的数据类型可能不同。从这些工厂方法返回的实际值就是抽象工厂模式的最初定义中所说的“产品”。
创建抽象产品的工厂方法与创建抽象工厂的工厂方法之间有个不同点。显然,像intValue
和boolValue
这样的工厂方法,应在具体工厂(NSCFBoolean
和NSCFNumber
)中重载以返回实际值(产品)。其他像numberWithBool:
和numberWithInteger:
这样的工厂方法并不是为了返回产品,而是为了返回能返回产品的工厂,因此它们不应该在具体工厂子类中重载。
其他时限为类簇的基础类有NSData
、NSArray
、NSDictionary
、NSString
。
工厂模式总结
在这三种设计模式中都有一个共同的特点,就是继承自抽象类的抽象子类或工厂子类,都必须对抽象类定义的方法给出对应的实现(可以相同,也可以不同),这种模式才叫做工厂模式。工厂模式的核心就是抽象和多态,抽象子类继承自抽象类,对抽象类中定义的方法和属性给出不同的实现方式,通过多态的方式进行方法实现和调用,构成了工厂模式的核心。
在工厂类中对“开放-封闭原则”有着完美的体现,对扩展的开放以及对修改的封闭。例如最抽象的抽象工厂模式,抽象工厂模式中增加新的系列,直接扩展一个工厂子类及对应的抽象子类,对整个模式框架不会带来其他影响。如果增加一个新的类型,创建新的类型对应的类,并对整个抽象工厂类及其子类进行方法扩展。
在外界使用抽象子类的功能时,不需要知道任何关于抽象子类的特征,抽象子类也不会出现在外界,外界只需要和抽象类打交道就可以。工厂模式将抽象子类的创建和实现分离,具体的创建操作由工厂类来进行,抽象子类只需要关注业务即可,外界不需要知道抽象子类实例化的过程。这种方式非常灵活并易于扩展,而且在大型项目中尤为明显,可以很好的避免代码量过多的问题。
参考
代码
文章中的代码都可以从我的GitHub DesignPatterns
找到。