简单工厂设计模式

什么是简单工厂模式?

简单工厂模式主要包含三部分:

  • 工厂类:根据外界的需求,决定创建并返回哪个具体的抽象子类。
  • 抽象类:定义抽象子类所需的属性和方法,子类通过继承自抽象类获得这些方法。
  • 抽象子类:继承自抽象类,是具体操作的实现者。

简单工厂模式中定义一个抽象类,抽象类中声明公共的特征及属性,抽象子类继承自抽象类,去实现具体的操作。工厂类根据外界需求,在工厂类中创建对应的抽象子类实例并传给外界,而对象的创建是由外界决定的。外界只需要知道抽象子类对应的参数即可,而不需要知道抽象子类的创建过程,在外界使用时甚至不用引入抽象子类。

简单工厂模式将操作对象的创建,和关于操作对象相关的业务逻辑分离开,降低操作对象的耦合度。由于工厂类只是为外界创建对象,所以并不需要实例化工厂类对象,只需要为外界提供类方法即可。外界需要什么类型的抽象子类,只需要传递对应的参数即可。

业务场景

简单工厂模式主要适用于抽象子类的业务逻辑相同,但具体实现不同的情况。不同的操作子类执行同样的方法,最后的结果却是不同的,这也是多态的一种表现方式。

这里用一个简单的加减乘除的基础运算例子来说明一下,下面的UML类图和代码都会依据这个场景来实现。假设现在需要实现一个简单的加减乘除运算,这些运算具体操作都是类似的,都有两个被操作的值,只是运算符不同,这种情况就适合用简单工厂模式。

UML类图
例子.jpg
普通方式代码实现

首先定义抽象类,抽象类中将会包含参与运算的抽象子类的属性和行为(方法)。

//.h
@interface Operation : NSObject
@property (nonatomic, assign) CGFloat numberOne;
@property (nonatomic, assign) CGFloat numberTwo;
- (CGFloat)getResult;
@end
//.m
@implementation Operation
- (CGFloat)getResult {
return 0;
}
@end

定义抽象类之后,需要创建负责具体运算的抽象子类,也就是操作类。

//.h
@interface OperationAdd : Operation
@end
 //.m 
@implementation OperationAdd
- (CGFloat)getResult {
return self.numberOne + self.numberTwo;
}
@end
//.h
@interface OperationSub : Operation
@end  
//.m
@implementation OperationSub
- (CGFloat)getResult {
return self.numberOne - self.numberTwo;
}
@end 
//.h
@interface OperationAdd : Operation
@end
//.m 
@implementation OperationAdd
- (CGFloat)getResult {
return self.numberOne + self.numberTwo;
}
@end
//.h
@interface OperationSub : Operation
@end  
//.m
@implementation OperationSub
- (CGFloat)getResult {
return self.numberOne - self.numberTwo;
}
@end 
//.h
@interface OperationMul : Operation
@end
//.m 
@implementation OperationMul
- (CGFloat)getResult {
return self.numberOne * self.numberTwo;
}
@end
//.h
@interface OperationDiv : Operation
@end
//.m
@implementation OperationDiv
- (CGFloat)getResult {
if (self.numberTwo == 0) {
NSLog(@"除数不能为零");
return 0;
} else {
return self.numberOne / self.numberTwo;
}
}
@end

现在具体参与运算的类都已经定义完成,就需要定义工厂类了。工厂类的职责就是根据外界需要,创建对应的抽象子类实例并返回给外界。

//下面先定义了四个静态变量,这四个静态变量声明了创建对象的类型,在后面反射部分代码中也会用到这四个静态变量。
static NSString kOperationAdd = @"OperationAdd";
static NSString kOperationSub = @"OperationSub";
static NSString kOperationMul = @"OperationMul";
static NSString kOperationDiv = @"OperationDiv";

//.h
@interface OperationFactory : NSObject
+ (Operation )CreateOperationWithType:(NSString )type;
@end
//.m
@implementation OperationFactory
+ (Operation )CreateOperationWithType:(NSString )type {
if ([kOperationAdd isEqualToString:type]) {
return [OperationAdd new];
} else if ([kOperationSub isEqualToString:type]) {
return [OperationSub new];
} else if ([kOperationMul isEqualToString:type]) {
return [OperationMul new];
} else if ([kOperationDiv isEqualToString:type]) {
return [OperationDiv new];
}
return nil;
}
@end

到目前为止简单工厂模式的代码就写完了,可以看到外界想进行什么类型的运算,只需要将传入的运算类型参数改一下即可,工厂类就会实例化其他的抽象子类进行运算。但是这种工厂类的设计,有一个很大的问题,就在于每次增加或删除某个算法时,都需要对工厂类进行修改,这是不符合开放封闭原则的。对于这个问题,我们将会通过反射机制来进行处理。

工厂模式也是对面向对象编程三大特性之一的多态的一个很好的表述,下面将会简单的介绍一下多态的特性。

简单介绍一下多态

面向对象编程三大特性之一就有多态,多态是指在程序运行时,相同的消息可能会发给继承自同一个父类的不同类型的对象,虽然是同一个方法,但是运行时系统会根据当前对象所属的具体类型作出不同的响应。

面向对象三大特性中,继承和封装都是为了代码重用,继承可以继承自父类的特征和属性,封装可以将实现细节封装,外界调用实现某些功能。而多态则是为了接口重用。

多个子类继承同一个父类,就会具有和父类相同的行为和特征,子类可以对父类的方法进行重写,所以可能同一个方法每个子类的实现都不同。通过父类指针指向任意子类对象并调用相同方法,可能会得到不同的结果。

简单的说就是系统允许将当前类的指针,指向任何继承自当前类的子类,并且不会报错。由于子类继承自父类,所以和父类有相同的特征(方法)。当前类的指针向指向的子类对象发送消息,系统会根据具体的子类对父类方法的实现,作出不同的响应。 例如下面这行代码:

Operation *obj = [OperationFactory CreateOperationWithType:kOperationAdd];

在OC语言中大多数类都是继承自NSObject类的(也有继承自NSProxy等类),所以都具有NSObject的方法,如果子类对NSObject的方法进行了重写,NSObject指向子类对象并发送消息的结果就不一样了。

在上面这个例子中,OperationFactory工厂类将会返回Operation的子类实例,Operation的子类分别继承自同一父类,并且对其getResult方法进行了重写。Operation实例化的obj指针可能指向任何Operation的子类,并对其发送getResult消息。最终的结果会根据obj指针指向的子类有不同的结果,这就是多态。

配合反射机制优化代码

在上面的代码中,我们会发现工厂类创建抽象子类的代码都是相同的,只是创建的具体对象不同,而且如果抽象子类很多的话,会有过多的条件语句。编程中这种重复代码我们都要将其简化,不然写出的代码就像垃圾代码一样,这也是新手和老手的区别之一。

在这里我们可以利用反射机制来简化代码,根据外面需要的操作子类的类型,反射出具体的类。在上面我们已经定义了一些NSString类型的静态变量,这些静态变量的值就是反射需要的字符串,外界只需要使用这些静态变量即可,不用自己手打字符串,也防止了错误的发生。修改之后外界不需要发生任何变化,只需要知道这些静态变量即可,只需要对工厂类进行修改。

只需将OperationFactory的创建方法改一下实现,其他地方不受影响。

- (Operation )CreateOperationWithType:(NSString )type 
{ 
      return [NSClassFromString(type) new]; 
}

改完之后的代码非常符合面向对象编程的开放封闭原则,即当外界需求发生变化时,只对现有代码进行扩展,不对其原有代码进行修改的原则。

现在假设再增加一个其他运算功能,只需要再创建一个继承自抽象类的抽象子类,在抽象子类中重写getResult方法来实现运算,并且在上面定义的静态变量中加入一个对应的变量。其他地方都不会受到影响,这就是一个比较好的面向对象的设计。

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

推荐阅读更多精彩内容