继上一篇的面向对象设计的设计原则,本篇是面向对象设计系列的第二个部分:面向对象设计的设计模式的第一篇文章。html
最开始说一下什么是设计模式。关于设计模式的概念,有不少不一样的版本,在这里说一下我我的比较赞同的一个说法:前端
设计模式用于在特定的条件下为一些重复出现的软件设计问题提供合理的、有效的解决方案。java
去掉一些定语的修饰,这句话精简为:git
设计模式为问题提供方案。github
简单来看,设计模式其实就是针对某些问题的一些方案。在软件开发中,即便不少人在用不一样的语言去开发不一样的业务,可是不少时候这些人遇到的问题抽象出来都是类似的。一些卓越的开发者将一些常出现的问题和对应的解决方案汇总起来,总结出了这些设计模式。算法
所以掌握了这些设计模式,可让咱们更好地去解决开发过程当中遇到的一些常见问题。并且对这些问题的解决方案的掌握程度越好,咱们就越可以打破语言自己的限制去解决问题,也就是加强“软件开发的内功”。sql
介绍设计模式最著名的一本书莫属《设计模式 可复用面向对象软件的基础》这本书,书中共介绍了23个设计模式。而这些设计模式分为三大类:数据库
而本篇做为该系列的第一篇,讲解的是设计模式中的6个建立型设计模式:编程
注意:简单工厂模式不是 GoF总结出来的23种设计模式之一,不存在于《设计模式 可复用面向对象软件的基础》这本书中。设计模式
在面向对象设计中,类与对象几乎是构成全部系统的基本元素,所以我认为学好了建立型模式才是学会设计系统的第一步:由于你应该知道如何去建立一些特定性质的对象,这才是设计好的系统的开始。
在讲解这6个设计模式以前先说一下该系列文章的讲解方式:
从更多维度来理解一件事物有助于更深入地理解它,所以每一个设计模式我都会从如下这几点来说解:
最后一项:“iOS SDK 和 JDK中的应用”讲解的是该设计模式在Objective-C和java语言(JDK)中的应用。
首先咱们看一下简单工厂模式:
简单工厂模式(Simple Factory Pattern):专门定义一个类(工厂类)来负责建立其余类的实例。能够根据建立方法的参数来返回不一样类的实例,被建立的实例一般都具备共同的父类。
简单工厂模式又称为静态工厂方法(Static Factory Method)模式,它属于类建立型模式。
若是咱们但愿将一些为数很少的相似的对象的建立和他们的建立细节分离开,也不须要知道对象的具体类型,可使用简单工厂模式。
举个形象点的例子:在前端开发中,经常会使用外观各式各样的按钮:好比有的按钮有圆角,有的按钮有阴影,有的按钮有边框,有的按钮无边框等等。可是由于同一种样式的按钮能够出如今项目的不少地方,因此若是在每一个地方都把建立按钮的逻辑写一遍的话显然是会形成代码的重复(并且因为业务的缘由有的按钮的建立逻辑能比较复杂,代码量大)。
那么为了不重复代码的产生,咱们能够将这些建立按钮的逻辑都放在一个“工厂”里面,让这个工厂来根据你的需求(传入的参数)来建立对应的按钮并返回给你。这样一来,一样类型的按钮在多个地方使用的时候,就能够只给这个工厂传入其对应的参数并拿到返回的按钮便可。
下面来看一下简单工厂模式的成员和类图。
简单工厂模式的结构比较简单,一共只有三个成员:
下面经过类图来看一下各个成员之间的关系:
从类图中能够看出,工厂类提供一个静态方法:经过传入的字符串来制造其所对应的产品。
举一个店铺售卖不一样品牌手机的例子:店铺,即客户端类向手机工厂购进手机售卖。
该场景可使用简单工厂的角色来设计:
Phone
,是全部具体产品类的父类,提供一个公共接口packaging
表示手机的装箱并送到店铺。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];
复制代码
上面代码的解读:
i
。在这里咱们须要注意的是:商店从工厂拿到手机不须要了解手机制做的过程,只须要知道它要工厂作的是手机(只知道Phone
类便可),和须要给工厂类传入它所需手机所对应的参数便可(这里的iPhone手机对应的参数就是i
)。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
NSNumber
的工厂方法传入不一样类型的数据,则会返回不一样数据所对应的NSNumber
的子类。Calendar
类中的私有的createCalendar(TimeZone zone, Locale aLocale)
方法经过不一样的入参来返回不一样类型的Calendar子类的实例。工厂方法模式(Factory Method Pattern)又称为工厂模式,工厂父类负责定义建立产品对象的公共接口,而工厂子类则负责生成具体的产品对象,即经过不一样的工厂子类来建立不一样的产品对象。
工厂方法模式的适用场景与简单工厂相似,都是建立数据和行为比较相似的对象。可是和简单工厂不一样的是:在工厂方法模式中,由于建立对象的责任移交给了抽象工厂的子类,所以客户端须要知道其所需产品所对应的工厂子类,而不是简单工厂中的参数。
下面咱们看一下工厂方法模式的成员和类图。
工厂方法模式包含四个成员:
下面经过类图来看一下各个成员之间的关系:
从类图中咱们能够看到:抽象工厂负责定义具体工厂必须实现的接口,而建立产品对象的任务则交给具体工厂,由特定的子工厂来建立其对应的产品。
这使得工厂方法模式能够容许系统在不修改原有工厂的状况下引进新产品:只须要建立新产品类和其所对应的工厂类便可。
一样也是模拟上面的简单工厂例子中的场景(手机商店卖手机),可是因为此次是由工厂方法模式来实现的,所以在代码设计上会有变化。
与简单工厂模式不一样的是:简单工厂模式里面只有一个工厂,而工厂方法模式里面有一个抽象工厂和继承于它的具体工厂。
所以一样的三个品牌的手机,咱们能够经过三个不一样的具体工厂:苹果手机工厂(IPhoneFactory
),小米手机工厂 (MIPhoneFactory
),华为手机工厂(HWPhoneFactory
)来生产。而这些具体工厂类都会继承于抽象手机工厂类:PhoneFactory
,它来声明生产手机的接口。
下面咱们用代码来具体来看一下工厂类(抽象工厂和具体工厂)的设计:
首先咱们声明一个抽象工厂类 PhoneFactory
:
//================== PhoneFactory.h ==================
#import "Phone.h"
@interface PhoneFactory : NSObject
+ (Phone *)createPhone;
@end
//================== PhoneFactory.m ==================
@implementation PhoneFactory
+ (Phone *)createPhone{
//implemented by subclass
return nil;
}
@end
复制代码
抽象工厂类给具体工厂提供了生产手机的接口,所以不一样的具体工厂能够按照本身的方式来生产手机。下面看一下具体工厂:
苹果手机工厂 IPhoneFactory
//================== IPhoneFactory.h ==================
@interface IPhoneFactory : PhoneFactory
@end
//================== IPhoneFactory.m ==================
#import "IPhone.h"
@implementation IPhoneFactory
+ (Phone *)createPhone{
IPhone *iphone = [[IPhone alloc] init];
NSLog(@"iPhone has been created");
return iphone;
}
@end
复制代码
小米手机工厂 MIPhoneFactory
:
//================== MIPhoneFactory.h ==================
@interface MPhoneFactory : PhoneFactory
@end
//================== MIPhoneFactory.m ==================
#import "MiPhone.h"
@implementation MPhoneFactory
+ (Phone *)createPhone{
MiPhone *miPhone = [[MiPhone alloc] init];
NSLog(@"MIPhone has been created");
return miPhone;
}
@end
复制代码
华为手机工厂 HWPhoneFactory
:
//================== HWPhoneFactory.h ==================
@interface HWPhoneFactory : PhoneFactory
@end
//================== HWPhoneFactory.m ==================
#import "HWPhone.h"
@implementation HWPhoneFactory
+ (Phone *)createPhone{
HWPhone *hwPhone = [[HWPhone alloc] init];
NSLog(@"HWPhone has been created");
return hwPhone;
}
@end
复制代码
以上就是声明的抽象工厂类和具体工厂类。由于生产手机的责任分配给了各个具体工厂类,所以客户端只须要委托所需手机所对应的工厂就能够得到其生产的手机了。
由于抽象产品类
Phone
和三个具体产品类(IPhone
,MIPhone
,HWPhone
)和简单工厂模式中介绍的例子中的同样,所以这里就再也不重复介绍了。
下面咱们用代码模拟一下该场景:
//================== Using by client ==================
//A phone store
Store *phoneStore = [[Store alloc] init];
//phoneStore wants to sell iphone
Phone *iphone = [IPhoneFactory createPhone];
[iphone packaging];
[phoneStore sellPhone:iphone];
//phoneStore wants to sell MIPhone
Phone *miPhone = [MPhoneFactory createPhone];
[miPhone packaging];
[phoneStore sellPhone:miPhone];
//phoneStore wants to sell HWPhone
Phone *hwPhone = [HWPhoneFactory createPhone];
[hwPhone packaging];
[phoneStore sellPhone:hwPhone];
复制代码
由上面的代码能够看出:客户端phoneStore
只需委托iPhone,MIPhone,HWPhone对应的工厂便可得到对应的手机了。
并且之后若是增长其余牌子的手机,例如魅族手机,就能够声明一个魅族手机类和魅族手机的工厂类并实现createPhone
这个方法便可,而不须要改动原有已经声明好的各个手机类和具体工厂类。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
Collection
接口声明了iterator()
方法,该方法返回结果的抽象类是Iterator
。ArrayList
就实现了这个接口;,而ArrayList对应的具体产品是Itr
。抽象工厂模式(Abstract Factory Pattern):提供一个建立一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
有时候咱们须要一个工厂能够提供多个产品对象,而不是单一的产品对象。好比系统中有多于一个的产品族,而每次只使用其中某一产品族,属于同一个产品族的产品将在一块儿使用。
在这里说一下产品族和产品等级结构的概念:
用一张图来帮助理解:
在上图中:
下面再举一个例子帮助你们理解:
咱们将小米,华为,苹果公司比做抽象工厂方法里的工厂:这三个工厂都有本身生产的手机,平板和电脑。 那么小米手机,小米平板,小米电脑就属于小米这个工厂的产品族;一样适用于华为工厂和苹果工厂。 而小米手机,华为手机,苹果手机则属于同一产品等级结构:手机的产品等级结构;平板和电脑也是如此。
结合这个例子对上面的图作一个修改能够更形象地理解抽象工厂方法的设计:
上面的关于产品族和产品等级结构的说法参考了慕课网实战课程:java设计模式精讲 Debug 方式+内存分析的6-1节。
抽象工厂模式的成员和工厂方法模式的成员是同样的,只不过抽象工厂方法里的工厂是面向产品族的。
下面经过类图来看一下各个成员之间的关系:
因为抽象工厂方法里的工厂是面向产品族的,因此为了贴合抽象工厂方法的特色,咱们将上面的场景作一下调整:在上面两个例子中,商店只卖手机。在这个例子中咱们让商店也卖电脑:分别是苹果电脑,小米电脑,华为电脑。
若是咱们仍是套用上面介绍过的工厂方法模式来实现该场景的话,则须要建立三个电脑产品对应的工厂:苹果电脑工厂,小米电脑工厂,华为电脑工厂。这就致使类的个数直线上升,之后若是还增长其余的产品,还须要添加其对应的工厂类,这显然是不够优雅的。
仔细看一下这六个产品的特色,咱们能够把这它们划分在三个产品族里面:
而抽象方法偏偏是面向产品族设计的,所以该场景适合使用的是抽象工厂方法。下面结合代码来看一下该如何设计。
首先引入电脑的基类和各个品牌的电脑类:
电脑基类:
//================== Computer.h ==================
@interface Computer : NSObject
//package to store
- (void)packaging;
@end
//================== Computer.m ==================
@implementation Computer
- (void)packaging{
//implemented by subclass
}
@end
复制代码
苹果电脑类 MacBookComputer
:
//================== MacBookComputer.h ==================
@interface MacBookComputer : Computer
@end
//================== MacBookComputer.m ==================
@implementation MacBookComputer
- (void)packaging{
NSLog(@"MacBookComputer has been packaged");
}
@end
复制代码
小米电脑类 MIComputer
:
//================== MIComputer.h ==================
@interface MIComputer : Computer
@end
//================== MIComputer.m ==================
@implementation MIComputer
- (void)packaging{
NSLog(@"MIComputer has been packaged");
}
@end
复制代码
华为电脑类 MateBookComputer
:
//================== MateBookComputer.h ==================
@interface MateBookComputer : Computer
@end
//================== MateBookComputer.m ==================
@implementation MateBookComputer
- (void)packaging{
NSLog(@"MateBookComputer has been packaged");
}
@end
复制代码
引入电脑相关产品类之后,咱们须要从新设计工厂类。由于抽象工厂方法模式的工厂是面向产品族的,因此抽象工厂方法模式里的工厂所建立的是同一产品族的产品。下面咱们看一下抽象工厂方法模式的工厂该如何设计:
首先建立全部工厂都须要集成的抽象工厂,它声明了生产同一产品族的全部产品的接口:
//================== Factory.h ==================
#import "Phone.h"
#import "Computer.h"
@interface Factory : NSObject
+ (Phone *)createPhone;
+ (Computer *)createComputer;
@end
//================== Factory.m ==================
@implementation Factory
+ (Phone *)createPhone{
//implemented by subclass
return nil;
}
+ (Computer *)createComputer{
//implemented by subclass
return nil;
}
@end
复制代码
接着,根据不一样的产品族,咱们建立不一样的具体工厂:
首先是苹果产品族工厂 AppleFactory
:
//================== AppleFactory.h ==================
@interface AppleFactory : Factory
@end
//================== AppleFactory.m ==================
#import "IPhone.h"
#import "MacBookComputer.h"
@implementation AppleFactory
+ (Phone *)createPhone{
IPhone *iPhone = [[IPhone alloc] init];
NSLog(@"iPhone has been created");
return iPhone;
}
+ (Computer *)createComputer{
MacBookComputer *macbook = [[MacBookComputer alloc] init];
NSLog(@"Macbook has been created");
return macbook;
}
@end
复制代码
接着是小米产品族工厂 MIFactory
:
//================== MIFactory.h ==================
@interface MIFactory : Factory
@end
//================== MIFactory.m ==================
#import "MIPhone.h"
#import "MIComputer.h"
@implementation MIFactory
+ (Phone *)createPhone{
MIPhone *miPhone = [[MIPhone alloc] init];
NSLog(@"MIPhone has been created");
return miPhone;
}
+ (Computer *)createComputer{
MIComputer *miComputer = [[MIComputer alloc] init];
NSLog(@"MIComputer has been created");
return miComputer;
}
@end
复制代码
最后是华为产品族工厂 HWFactory
:
//================== HWFactory.h ==================
@interface HWFactory : Factory
@end
//================== HWFactory.m ==================
#import "HWPhone.h"
#import "MateBookComputer.h"
@implementation HWFactory
+ (Phone *)createPhone{
HWPhone *hwPhone = [[HWPhone alloc] init];
NSLog(@"HWPhone has been created");
return hwPhone;
}
+ (Computer *)createComputer{
MateBookComputer *hwComputer = [[MateBookComputer alloc] init];
NSLog(@"HWComputer has been created");
return hwComputer;
}
@end
复制代码
以上就是工厂类的设计。这样设计好以后,客户端若是须要哪一产品族的某个产品的话,只须要找到对应产品族工厂后,调用生产该产品的接口便可。假如须要苹果电脑,只须要委托苹果工厂来制造苹果电脑便可;若是须要小米手机,只须要委托小米工厂制造小米手机便可。
下面用代码来模拟一下这个场景:
//================== Using by client ==================
Store *store = [[Store alloc] init];
//Store wants to sell MacBook
Computer *macBook = [AppleFactory createComputer];
[macBook packaging];
[store sellComputer:macBook];
//Store wants to sell MIPhone
Phone *miPhone = [MIFactory createPhone];
[miPhone packaging];
[store sellPhone:miPhone];
//Store wants to sell MateBook
Computer *mateBook = [HWFactory createComputer];
[mateBook packaging];
[store sellComputer:mateBook];
复制代码
上面的代码就是模拟了商店售卖苹果电脑,小米手机,华为电脑的场景。而从此若是该商店引入了新品牌的产品,好比联想手机,联想电脑,那么咱们只须要新增联想手机类,联想电脑类,联想工厂类便可。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
因为三个工厂的产品总数过多,所以在这里只体现了苹果工厂和小米工厂的产品。
Connection
。在这个接口里面有createStatement()
和prepareStatement(String sql)
。这两个接口都是获取的统一产品族的对象,好比MySql和PostgreSQL产品族,具体返回的是哪一个产品族对象,取决于所链接的数据库类型。OK,到如今三个工厂模式已经讲完了。在继续讲解下面三个设计模式以前,先简单回顾一下上面讲解的三个工厂模式:
大致上看,简单工厂模式,工厂方法模式和抽象工厂模式的复杂程度是逐渐升高的。
在实际开发过程当中,咱们须要根据业务场景的复杂程度的不一样来采用最适合的工厂模式。
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,并提供一个访问它的全剧访问点。
系统只须要一个实例对象,客户调用类的单个实例只容许使用一个公共访问点,除了该公共访问点,不能经过其余途径访问该实例。比较典型的例子是音乐播放器,日志系统类等等。
单例模式只有一个成员,就是单例类。由于只有一个成员,因此该设计模式的类图比较简单:
通常来讲单例类会给外部提供一个获取单例对象的方法,内部会用静态对象的方式保存这个对象。
在这里咱们建立一个简单的打印日至或上报日至的日至管理单例。
在建立单例时,除了要保证提供惟一实例对象之外,还需注意多线程的问题。下面用代码来看一下。
建立单例类 LogManager
//================== LogManager.h ==================
@interface LogManager : NSObject
+(instancetype)sharedInstance;
- (void)printLog:(NSString *)logMessage;
- (void)uploadLog:(NSString *)logMessage;
@end
//================== LogManager.m ==================
@implementation LogManager
static LogManager* _sharedInstance = nil;
+(instancetype)sharedInstance
{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_sharedInstance = [[super allocWithZone:NULL] init] ;
}) ;
return _sharedInstance ;
}
+(id)allocWithZone:(struct _NSZone *)zone
{
return [LogManager sharedInstance] ;
}
-(id)copyWithZone:(struct _NSZone *)zone
{
return [LogManager sharedInstance];
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return [LogManager sharedInstance];
}
- (void)printLog:(NSString *)logMessage{
//print logMessage
}
- (void)uploadLog:(NSString *)logMessage{
//upload logMessage
}
@end
复制代码
从上面的代码中能够看到:
sharedInstance
方法是向外部提供的获取惟一的实例对象的方法,也是该类中的其余能够建立对象的方法的都调用的方法。在这个方法内部使用了dispatch_once
函数来避免多线程访问致使建立多个实例的状况。alloc init
出初始化方法能够返回同一个实例对象,在allocWithZone:
方法里面仍然调用了sharedInstance
方法。copy
和mutableCopy
方法也能够返回同一个实例对象,在copyWithZone:
与mutableCopyWithZone
也是调用了sharedInstance
方法。下面分别用这些接口来验证一下实例的惟一性:
//================== Using by client ==================
//alloc&init
LogManager *manager0 = [[LogManager alloc] init];
//sharedInstance
LogManager *manager1 = [LogManager sharedInstance];
//copy
LogManager *manager2 = [manager0 copy];
//mutableCopy
LogManager *manager3 = [manager1 mutableCopy];
NSLog(@"\nalloc&init: %p\nsharedInstance: %p\ncopy: %p\nmutableCopy: %p",manager0,manager1,manager2,manager3);
复制代码
咱们看一下打印出来的四个指针所指向对象的地址:
alloc&init: 0x60000000f7e0
sharedInstance: 0x60000000f7e0
copy: 0x60000000f7e0
mutableCopy: 0x60000000f7e0
复制代码
能够看出打印出来的地址都相同,说明都是同一对象,证实了实现方法的正确性。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
NSUserDefaults
(key-value持久化)和UIApplication
类(表明应用程序,能够处理一些点击事件等)。Runtime
类(表明应用程序的运行环境,使应用程序可以与其运行的环境相链接);Desktop
类(容许 Java 应用程序启动已在本机桌面上注册的关联应用程序)生成器模式(Builder Pattern):也叫建立者模式,它将一个复杂对象的构建与它的表示分离,使得一样的构建过程能够建立不一样的表示。
具体点说就是:有些对象的建立流程是同样的,可是由于自身特性的不一样,因此在建立他们的时候须要将建立过程和特性的定制分离开来。
下面咱们看一下该设计模式的适用场景。
当建立复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时比较适合使用生成器模式。
一些复杂的对象,它们拥有多个组成部分(如汽车,它包括车轮、方向盘、发送机等各类部件)。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车。并且这些部分的建立顺序是固定的,或者是须要指定的。
在这种状况下能够经过建造者模式对其进行设计与描述,生成器模式能够将部件和其组装过程分开,一步一步建立一个复杂的对象。
建造者模式包含4个成员:
下面经过类图来看一下各个成员之间的关系:
须要注意的是:
- Builder类中的product成员变量的关键字为
protected
,目的是为了仅让它和它的子类能够访问该成员变量。- Director类中的
constructProductWithBuilder(Builder builder)
方法是经过传入不一样的builder来构造产品的。并且它的getProduct()
方法同时也封装了Concrete Builder
类的getProduct()
方法,目的是为了让客户端直接从Director
拿到对应的产品(有些资料里面的Director
类没有封装Concrete Builder
类的getProduct()
方法)。
模拟一个制造手机的场景:手机的组装须要几个固定的零件:CPU,RAM,屏幕,摄像头,并且须要CPU -> RAM ->屏幕 -> 摄像头的顺序来制造。
咱们使用建造者设计模式来实现这个场景:首先不一样的手机要匹配不一样的builder;而后在Director
类里面来定义制造顺序。
首先咱们定义手机这个类,它有几个属性:
//================== Phone.h ==================
@interface Phone : NSObject
@property (nonatomic, copy) NSString *cpu;
@property (nonatomic, copy) NSString *capacity;
@property (nonatomic, copy) NSString *display;
@property (nonatomic, copy) NSString *camera;
@end
复制代码
而后咱们建立抽象builder类:
//================== Builder.h ==================
#import "Phone.h"
@interface Builder : NSObject
{
@protected Phone *_phone;
}
- (void)createPhone;
- (void)buildCPU;
- (void)buildCapacity;
- (void)buildDisplay;
- (void)buildCamera;
- (Phone *)obtainPhone;
@end
复制代码
抽象builder类声明了建立手机各个组件的接口,也提供了返回手机实例的对象。
接下来咱们建立对应不一样手机的具体生成者类:
IPhoneXR手机的builder:IPhoneXRBuilder
:
//================== IPhoneXRBuilder.h ==================
@interface IPhoneXRBuilder : Builder
@end
//================== IPhoneXRBuilder.m ==================
@implementation IPhoneXRBuilder
- (void)createPhone{
_phone = [[Phone alloc] init];
}
- (void)buildCPU{
[_phone setCpu:@"A12"];
}
- (void)buildCapacity{
[_phone setCapacity:@"256"];
}
- (void)buildDisplay{
[_phone setDisplay:@"6.1"];
}
- (void)buildCamera{
[_phone setCamera:@"12MP"];
}
- (Phone *)obtainPhone{
return _phone;
}
@end
复制代码
小米8手机的builder:MI8Builder
:
//================== MI8Builder.h ==================
@interface MI8Builder : Builder
@end
//================== MI8Builder.m ==================
@implementation MI8Builder
- (void)createPhone{
_phone = [[Phone alloc] init];
}
- (void)buildCPU{
[_phone setCpu:@"Snapdragon 845"];
}
- (void)buildCapacity{
[_phone setCapacity:@"128"];
}
- (void)buildDisplay{
[_phone setDisplay:@"6.21"];
}
- (void)buildCamera{
[_phone setCamera:@"12MP"];
}
- (Phone *)obtainPhone{
return _phone;
}
@end
复制代码
从上面两个具体builder的代码能够看出,这两个builder都按照其对应的手机配置来建立其对应的手机。
下面来看一下Director的用法:
//================== Director.h ==================
#import "Builder.h"
@interface Director : NSObject
- (void)constructPhoneWithBuilder:(Builder *)builder;
- (Phone *)obtainPhone;
@end
//================== Director.m ==================
implementation Director
{
Builder *_builder;
}
- (void)constructPhoneWithBuilder:(Builder *)builder{
_builder = builder;
[_builder buildCPU];
[_builder buildCapacity];
[_builder buildDisplay];
[_builder buildCamera];
}
- (Phone *)obtainPhone{
return [_builder obtainPhone];
}
@end
复制代码
Director类提供了
construct:
方法,须要传入builder的实例。该方法里面按照既定的顺序来建立手机。
最后咱们看一下客户端是如何使用具体的Builder和Director实例的:
//================== Using by client ==================
//Get iPhoneXR
//1. A director instance
Director *director = [[Director alloc] init];
//2. A builder instance
IPhoneXRBuilder *iphoneXRBuilder = [[IPhoneXRBuilder alloc] init];
//3. Construct phone by director
[director construct:iphoneXRBuilder];
//4. Get phone by builder
Phone *iPhoneXR = [iphoneXRBuilder obtainPhone];
NSLog(@"Get new phone iPhoneXR of data: %@",iPhoneXR);
//Get MI8
MI8Builder *mi8Builder = [[MI8Builder alloc] init];
[director construct:mi8Builder];
Phone *mi8 = [mi8Builder obtainPhone];
NSLog(@"Get new phone MI8 of data: %@",mi8);
复制代码
从上面能够看出客户端获取具体产品的过程:
- 首先须要实例化一个Director的实例。
- 而后根据所须要的产品找出其对应的builder。
- 将builder传入director实例的
construct:
方法。- 从builder的
obtainPhone
获取手机实例。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
建造者模式所建立的产品通常具备较多的共同点,其组成部分类似,若是产品之间的差别性很大,则不适合使用建造者模式,所以其使用范围受到必定的限制。
若是产品的内部变化复杂,可能会致使须要定义不少具体建造者类来实现这种变化,致使系统变得很庞大。
StringBuilder
属于builder,它向外部提供append(String)
方法来拼接字符串(也能够传入int等其余类型);而toString()
方法来返回字符串。原型模式(Prototype Pattern): 使用原型实例指定待建立对象的类型,而且经过复制这个原型来建立新的对象。
对象层级嵌套比较多,从零到一建立对象的过程比较繁琐时,能够直接经过复制的方式建立新的对象
当一个类的实例只能有几个不一样状态组合中的一种时,咱们能够利用已有的对象进行复制来得到
原型模式主要包含以下两个角色:
下面经过类图来看一下各个成员之间的关系:
须要注意的是,这里面的clone()
方法返回的是被复制出来的实例对象。
模拟一份校招的简历,简历里面有人名,性别,年龄以及学历相关的信息。这里面学历相关的信息又包含学校名称,专业,开始和截止年限的信息。
这里的学历相关信息可使用单独一个对象来作,所以总体的简历对象的结构能够是:
简历对象:
并且由于对于同一学校同一届的同一专业的毕业生来讲,学历对象中的信息是相同的,这时候若是须要大量生成这些毕业生的简历的话比较适合使用原型模式。
首先定义学历对象:
//================== UniversityInfo.h ==================
@interface UniversityInfo : NSObject<NSCopying>
@property (nonatomic, copy) NSString *universityName;
@property (nonatomic, copy) NSString *startYear;
@property (nonatomic, copy) NSString *endYear;
@property (nonatomic, copy) NSString *major;
- (id)copyWithZone:(NSZone *)zone;
@end
//================== UniversityInfo.m ==================
@implementation UniversityInfo
- (id)copyWithZone:(NSZone *)zone
{
UniversityInfo *infoCopy = [[[self class] allocWithZone:zone] init];
[infoCopy setUniversityName:[_universityName mutableCopy]];
[infoCopy setStartYear:[_startYear mutableCopy]];
[infoCopy setEndYear:[_endYear mutableCopy]];
[infoCopy setMajor:[_major mutableCopy]];
return infoCopy;
}
@end
复制代码
由于学历对象是支持复制的,所以须要听从
<NSCopying>
协议并实现copyWithZone:
方法。并且支持的是深复制,因此在复制NSString的过程当中须要使用mutableCopy
来实现。
接着咱们看一下简历对象:
//================== Resume.h ==================
#import "UniversityInfo.h"
@interface Resume : NSObject<NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSString *age;
@property (nonatomic, strong) UniversityInfo *universityInfo;
@end
//================== Resume.m ==================
@implementation Resume
- (id)copyWithZone:(NSZone *)zone
{
Resume *resumeCopy = [[[self class] allocWithZone:zone] init];
[resumeCopy setName:[_name mutableCopy]];
[resumeCopy setGender:[_gender mutableCopy]];
[resumeCopy setAge:[_age mutableCopy]];
[resumeCopy setUniversityInfo:[_universityInfo copy]];
return resumeCopy;
}
@end
复制代码
一样地,简历对象也须要听从
<NSCopying>
协议并实现copyWithZone:
方法。
最后咱们看一下复制的效果有没有达到咱们的预期(被复制对象和复制对象的地址和它们全部的属性对象的地址都不相同)
//================== Using by client ==================
//resume for LiLei
Resume *resume = [[Resume alloc] init];
resume.name = @"LiLei";
resume.gender = @"male";
resume.age = @"24";
UniversityInfo *info = [[UniversityInfo alloc] init];
info.universityName = @"X";
info.startYear = @"2014";
info.endYear = @"2018";
info.major = @"CS";
resume.universityInfo = info;
//resume_copy for HanMeiMei
Resume *resume_copy = [resume copy];
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== copy resume ======== %@",resume,resume_copy);
resume_copy.name = @"HanMeiMei";
resume_copy.gender = @"female";
resume_copy.universityInfo.major = @"TeleCommunication";
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== revised copy resume ======== %@",resume,resume_copy);
复制代码
上面的代码模拟了这样一个场景:李雷同窗写了一份本身的简历,而后韩梅梅复制了一份并修改了姓名,性别和专业这三个和李雷不一样的信息。
这里咱们重写了Resume
的description
方法来看一下全部属性的值及其内存地址。最后来看一下resume对象和resume_copy对象打印的结果:
//================== Output log ==================
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== copy resume ========
resume object address:0x604000247da0
name:LiLei | 0xa000069654c694c5
gender:male | 0xa000000656c616d4
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:CS | 0xa000000000053432
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== revised copy resume ========
resume object address:0x604000247da0
name:HanMeiMei | 0x10bc0c1b0
gender:female | 0x10bc0c1d0
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:TeleCommunication | 0x10bc0c1f0
复制代码
- 上面两个是原resume和刚被复制后的 copy resume的信息,能够看出来不管是这两个对象的地址仍是它们的值对应的地址都是不一样的,说明成功地实现了深复制。
- 下面两个是原resume和被修改后的 copy_resume的信息,能够看出来新的copy_resume的值发生了变化,并且值所对应的地址仍是和原resume的不一样。
注:还能够用序列化和反序列化的办法来实现深复制,由于与代码设计上不是很复杂,不少语言直接提供了接口,故这里不作介绍。
下面咱们看一下该例子对应的 UML类图,能够更直观地看一下各个成员之间的关系:
在这里须要注意的是:
copy
方法是NSObject
类提供的复制本对象的接口。NSObject
相似于Java中的Object
类,在Objective-C中几乎全部的对象都继承与它。并且这个copy
方法也相似于Object
类的clone()
方法。copyWithZone(NSZone zone)
方法是接口NSCopying
提供的接口。而由于这个接口存在于实现文件而不是头文件,因此它不是对外公开的;便是说外部没法直接调用copyWithZone(NSZone zone)
方法。copyWithZone(NSZone zone)
方法是在上面所说的copy
方法调用后再调用的,做用是将对象的全部数据都进行复制。所以使用者须要在copyWithZone(NSZone zone)
方法里作工做,而不是copy
方法,这一点和Java的clone
方法不一样。
<NSCopying>
协议,配合- (id)copyWithZone:(NSZone *)zone
方法; 或者<NSMutableCopying>
协议,配合 copyWithZone:/mutableCopyWithZone:
方法Cloneable
接口并实现clone()
方法来复制该类的实例。到这里设计模式中的建立型模式就介绍完了,读者能够结合UML类图和demo的代码来理解每一个设计模式的特色和相互之间的区别,但愿读者能够有所收获。
另外,本篇博客的代码和类图都保存在个人GitHub库中:knightsj:object-oriented-design中的Chapter2。
下一篇是面向对象系列的第三篇,讲解的是面向对象设计模式中的结构型模式。 该系列的第一篇讲解的是设计原则,有兴趣的读者能够移步:面向对象设计的六大设计原则(附 Demo 及 UML 类图)
本篇已同步到我的博客:面向对象设计的设计模式(一):建立型模式(附 Demo 及 UML 类图)
笔者在近期开通了我的公众号,主要分享编程,读书笔记,思考类的文章。
由于公众号天天发布的消息数有限制,因此到目前为止尚未将全部过去的精选文章都发布在公众号上,后续会逐步发布的。
并且由于各大博客平台的各类限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~
扫下方的公众号二维码并点击关注,期待与您的共同成长~