从简单工厂到工厂模式

context

上次的文章,用简单工厂,解决了生产布局类的问题. 但是,上次的方式针对一个模块是可以的,当模块多了,每个类负责的属性就太多了.这是一个需要重构的点.

准备工作

首先,将类重命名, 比如 原来的:

GUILayoutFactory -> GUILoginLayoutFactory

原来的布局产品名称也是应该修改的,但是就不贴出来了,一个原则就是:不要让类名把你自己弄迷糊了即可.

重构

下面是我工厂模式的结构图,目前工厂只有 Login这一个,项目中有更多,但是没必要都在此列举.

文件说明图

当把图画出来, 很容易看出来, 需要进行重构了. 因为在 GUILoginLayoutFactory 存在着逻辑判断代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)


typedef NS_ENUM(NSUInteger, GUIDeviceType) {

GUIDeviceType4S = 1,
GUIDeviceType5,
GUIDeviceType6,
GUIDeviceType6P,
GUIDeviceTypeNotFound

};

上面的代码是当时在应用简单工厂的时候,将逻辑下放到工厂中得.现在它存在于我们的 GUILoginLayoutFactory中,这个工厂还会有同级的兄弟工厂.它们也需要这种判断来生产具体的 layou产品,比如GUIDiaryLayoutFactory,那么,现在应该采用将这些代码提升到父类的重构手法. GUILoginLayoutFactory 的父类是我们的 “ 总工厂 “ 也就是 GUILayoutFactory

现在 GUILayoutFactory的头文件代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import <Foundation/Foundation.h>
@class GUILayout;

#define kisiPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO)

#define kisiPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242,2208), [[UIScreen mainScreen] currentMode].size) : NO)


typedef NS_ENUM(NSUInteger, GUIDeviceType) {

GUIDeviceType4S = 1,
GUIDeviceType5,
GUIDeviceType6,
GUIDeviceType6P,
GUIDeviceTypeNotFound

};

@interface GUILayoutFactory : NSObject

-(GUILayout *)layout;
+(GUILayout *)layout;

@end

养成好习惯,每次小重构后都进行编译.

客户端调用代码:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];

GUILayoutFactory * layoutFactory = [GUILoginLayoutFactory new];
GUILayout * layout = [layoutFactory layout];

NSLog(@"\n %f \n %f \n %f \n %f",layout.textFieldWidth,layout.textFieldHeight,layout.loginButtonWidth,layout.loginButtonHeight);
}

是的, 调用端需要知晓具体的工厂.比如这里的 GUILoginFactory才能拿到具体的产品.

对比简单工厂

原来使用简单工厂实现的时候,如果要增加新产品.那么需要:

  1. 添加具体的产品(Layout类)
  2. 在工厂类中增加一个分支语句

客户端是无需修改和知道工厂内部的细节的.

现在使用工厂模式,如果要增加新产品:

  1. 添加具体的产品(Layout类)
  2. 添加具体产品的工厂类(LayoutFactory类)
  3. 客户端进行判断和需要知晓新的工厂类

加重了客户端的负担.

总结来说:

  • 简单工厂最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择(或者是其他条件)动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖 .

  • 但是如果添加新产品,要修改工厂类,违背了”开放-封闭原则”

  • 工厂模式让子类工厂决定实例化哪个类.让产品类的实例化延迟到了子工厂.

  • 新增加产品的时候, 整个工厂的体系是没有修改的,只是扩展出一个子类.

  • 工厂模式,客户端需要知晓具体的工厂子类

两者没有好坏, 我们需要根据自己的实际情景来选择模式进行应用.