继上一篇的面向对象设计的设计原则,本篇是面向对象设计系列的第二个部分:面向对象设计的设计模式的第一篇文章。
另外,本篇博客的代码和类图都保存在我的GitHub库中:中的Chapter2。
最开始说一下什么是设计模式。关于设计模式的概念,有很多不同的版本,在这里说一下我个人比较赞同的一个说法:
设计模式用于在特定的条件下为一些重复出现的软件设计问题提供合理的、有效的解决方案。
去掉一些定语的修饰,这句话精简为:
设计模式为问题提供方案。
简单来看,设计模式其实就是针对某些问题的一些方案。在软件开发中,即使很多人在用不同的语言去开发不同的业务,但是很多时候这些人遇到的问题抽象出来都是相似的。一些卓越的开发者将一些常出现的问题和对应的解决方案汇总起来,总结出了这些设计模式。
因此掌握了这些设计模式,可以让我们更好地去解决开发过程中遇到的一些常见问题。而且对这些问题的解决方案的掌握程度越好,我们就越能够打破语言本身的限制去解决问题,也就是增强“软件开发的内功”。
介绍设计模式最著名的一本书莫属《设计模式 可复用面向对象软件的基础》这本书,书中共介绍了23个设计模式。而这些设计模式分为三大类:
- 创建型设计模式:侧重于对象的创建。
- 结构型设计模式:侧重于接口的设计和系统的结构。
- 行为型设计模式:侧重于类或对象的行为。
而本篇作为该系列的第一篇,讲解的是设计模式中的6个创建型设计模式:
- 简单工厂模式(Simple Factory Pattern)
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 生成器模式(Builder Pattern)
- 原型模式(Prototype Pattern)
注意:简单工厂模式不是 GoF总结出来的23种设计模式之一,不存在于《设计模式 可复用面向对象软件的基础》这本书中。
在面向对象设计中,类与对象几乎是构成所有系统的基本元素,因此我认为学好了创建型模式才是学会设计系统的第一步:因为你应该知道如何去创建一些特定性质的对象,这才是设计好的系统的开始。
在讲解这6个设计模式之前先说一下该系列文章的讲解方式:
从更多维度来理解一件事物有助于更深刻地理解它,因此每个设计模式我都会从以下这几点来讲解:
- 定义
- 使用场景
- 成员与类图
- 代码示例
- 优点
- 缺点
- iOS SDK 和 JDK 中的应用
最后一项:“iOS SDK 和 JDK中的应用”讲解的是该设计模式在Objective-C和java语言(JDK)中的应用。
首先我们看一下简单工厂模式:
一. 简单工厂模式
定义
简单工厂模式(Simple Factory Pattern):专门定义一个类(工厂类)来负责创建其他类的实例。可以根据创建方法的参数来返回不同类的实例,被创建的实例通常都具有共同的父类。
简单工厂模式又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。
适用场景
如果我们希望将一些为数不多的类似的对象的创建和他们的创建细节分离开,也不需要知道对象的具体类型,可以使用简单工厂模式。
举个形象点的例子:在前端开发中,常常会使用外观各式各样的按钮:比如有的按钮有圆角,有的按钮有阴影,有的按钮有边框,有的按钮无边框等等。但是因为同一种样式的按钮可以出现在项目的很多地方,所以如果在每个地方都把创建按钮的逻辑写一遍的话显然是会造成代码的重复(而且由于业务的原因有的按钮的创建逻辑能比较复杂,代码量大)。
那么为了避免重复代码的产生,我们可以将这些创建按钮的逻辑都放在一个“工厂”里面,让这个工厂来根据你的需求(传入的参数)来创建对应的按钮并返回给你。这样一来,同样类型的按钮在多个地方使用的时候,就可以只给这个工厂传入其对应的参数并拿到返回的按钮即可。
下面来看一下简单工厂模式的成员和类图。
成员与类图
成员
简单工厂模式的结构比较简单,一共只有三个成员:
- 工厂(Factory):工厂负责实现创建所有产品实例的逻辑
- 抽象产品(Product):抽象产品是工厂所创建的所有产品对象的父类,负责声明所有产品实例所共有的公共接口。
- 具体产品(Concrete Product):具体产品是工厂所创建的所有产品对象类,它以自己的方式来实现其共同父类声明的接口。
下面通过类图来看一下各个成员之间的关系:
模式类图
从类图中可以看出,工厂类提供一个静态方法:通过传入的字符串来制造其所对应的产品。
代码示例
场景概述
举一个店铺售卖不同品牌手机的例子:店铺,即客户端类向手机工厂购进手机售卖。
场景分析
该场景可以使用简单工厂的角色来设计:
- 抽象产品:
Phone
,是所有具体产品类的父类,提供一个公共接口packaging
表示手机的装箱并送到店铺。 - 具体产品:不同品牌的手机,iPhone手机类(
IPhone
),小米手机类(MIPhone
),华为手机类(HWPhone
)。 - 工厂:
PhoneFactory
根据不同的参数来创建不同的手机。 - 客户端类:店铺类
Store
负责售卖手机。
代码实现
抽象产品类Phone
:
//================== Phone.h ==================
@interface Phone : NSObject
//package to store
- (void)packaging;
@end
具体产品类 IPhone
:
//================== IPhone.h ==================
@interface IPhone : Phone
@end
//================== IPhone.m ==================
@implementation IPhone
- (void)packaging{
NSLog(@"IPhone has been packaged");
}
@end
具体产品类 MIPhone
:
//================== MIPhone.h ==================
@interface MIPhone : Phone
@end
//================== MIPhone.m ==================
@implementation MIPhone
- (void)packaging{
NSLog(@"MIPhone has been packaged");
}
@end
具体产品类:HWPhone
:
//================== HWPhone.h ==================
@interface HWPhone : Phone
@end
//================== HWPhone.m ==================
@implementation HWPhone
- (void)packaging{
NSLog(@"HUAWEI Phone has been packaged");
}
@end
以上是抽象产品类以及它的三个子类:苹果手机类,小米手机类和华为手机类。
下面看一下工厂类 PhoneFactory
:
//================== PhoneFactory.h ==================
@interface PhoneFactory : NSObject
+ (Phone *)createPhoneWithTag:(NSString *)tag;
@end
//================== PhoneFactory.m ==================
#import "IPhone.h"
#import "MIPhone.h"
#import "HWPhone.h"
@implementation PhoneFactory
+ (Phone *)createPhoneWithTag:(NSString *)tag{
if ([tag isEqualToString:@"i"]) {
IPhone *iphone = [[IPhone alloc] init];
return iphone;
}else if ([tag isEqualToString:@"MI"]){
MIPhone *miPhone = [[MIPhone alloc] init];
return miPhone;
}else if ([tag isEqualToString:@"HW"]){
HWPhone *hwPhone = [[HWPhone alloc] init];
return hwPhone;
}else{
return nil;
}
}
@end
工厂类向外部(客户端)提供了一个创造手机的接口
createPhoneWithTag:
,根据传入参数的不同可以返回不同的具体产品类。因此客户端只需要知道它所需要的产品所对应的参数即可获得对应的产品了。
在本例中,我们声明了店铺类 Store
为客户端类:
//================== Store.h ==================
#import "Phone.h"
@interface Store : NSObject
- (void)sellPhone:(Phone *)phone;
@end
//================== Store.m ==================
@implementation Store
- (void)sellPhone:(Phone *)phone{
NSLog(@"Store begins to sell phone:%@",[phone class]);
}
@end
客户端类声明了一个售卖手机的接口
sellPhone:
。表示它可以售卖作为参数所传入的手机。
最后我们用代码模拟一下这个实际场景:
//================== Using by client ==================
//1\. A phone store wants to sell iPhone
Store *phoneStore = [[Store alloc] init];
//2\. create phone
Phone *iPhone = [PhoneFactory createPhoneWithTag:@"i"];
//3\. package phone to store
[iphone packaging];
//4\. store sells phone after receving it
[phoneStore sellPhone:iphone];
上面代码的解读:
- 最开始实例化一个商店,商店打算卖苹果手机
- 商店委托工厂给他制作一台iPhone手机,传入对应的字段
i
。 - 手机生产好以后打包送到商店
- 商店售卖手机
在这里我们需要注意的是:商店从工厂拿到手机不需要了解手机制作的过程,只需要知道它要工厂做的是手机(只知道Phone
类即可),和需要给工厂类传入它所需手机所对应的参数即可(这里的iPhone手机对应的参数就是i
)。
下面我们看一下该例子对应的 UML类图,可以更直观地看一下各个成员之间的关系:
代码对应的类图
优点
- 客户端只需要给工厂类传入一个正确的(约定好的)参数,就可以获取你所需要的对象,而不需要知道其创建细节,一定程度上减少系统的耦合。
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,减少开发者的记忆成本。
缺点
- 如果业务上添加新产品的话,就需要修改工厂类原有的判断逻辑,这其实是违背了开闭原则的。
- 在产品类型较多时,有可能造成工厂逻辑过于复杂。所以简单工厂模式比较适合产品种类比较少而且增多的概率很低的情况。
iOS SDK 和 JDK 中的应用
- Objective-C中的类簇就是简单工厂设计模式的一个应用。如果给
NSNumber
的工厂方法传入不同类型的数据,则会返回不同数据所对应的NSNumber
的子类。 - JDK中的
Calendar
类中的私有的createCalendar(TimeZone zone, Locale aLocale)
方法通过不同的入参来返回