@qidiandasheng
2021-01-11T21:59:21.000000Z
字数 11793
阅读 1124
架构
侧重于对象的创建。
在软件工程中,引自维基百科创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
创建型模式由两个主导思想构成。一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合的方式。
单例模式(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
可以看出打印出来的地址都相同,说明都是同一对象,证明了实现方法的正确性。
NSUserDefaults
(key-value持久化)和UIApplication
类(代表应用程序,可以处理一些点击事件等)。生成器模式(Builder Pattern):也叫建造者模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
具体点说就是:有些对象的创建流程是一样的,但是因为自身特性的不同,所以在创建他们的时候需要将创建过程和特性的定制分离开来。
假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便; 因此你需要重载这个构造函数, 新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。
@interface Phone : NSObject
- (void)initWithCPU:(NSString *)cpu;
- (void)initWithCPU:(NSString *)cpu Capacity:(NSString *)capacity;
- (void)initWithCPU:(NSString *)cpu Capacity:(NSString *)capacity Display:(NSString *)display;
- (void)initWithCPU:(NSString *)cpu Capacity:(NSString *)capacity Display:(NSString *)display Camera:(NSString *)camera;
@end
生成器模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。
解决上面构造函数过长问题我们可以使用属性set的方式。那这里会存在几个问题:
如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用生成器模式(如手机CPU不同)。
基本生成器接口中定义了所有可能的制造步骤,具体生成器将实现这些步骤来制造特定形式的产品。同时主管类将负责管理制造步骤的顺序。
跟工厂模式不同的点:
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
比如:顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
建造者模式包含4个成员:
模拟一个制造手机的场景:手机的组装需要几个固定的零件:CPU,RAM,屏幕,摄像头,而且需要CPU -> RAM -> 屏幕 -> 摄像头的顺序来制造。
Builder.h:
@interface Builder : NSObject
@property (readonly, nonatomic, copy) NSString * cpu;
@property (readonly, nonatomic, copy) NSString * capacity;
@property (readonly, nonatomic, copy) NSString * display;
@property (readonly, nonatomic, copy) NSString * camera;
//采用链式编程的方式来创建
@property (readonly, nonatomic, copy) Builder * (^buildCpu)(NSString *cpu);
@property (readonly, nonatomic, copy) Builder * (^buildCapacity)(NSString *capacity);
@property (readonly, nonatomic, copy) Builder * (^buildDisplay)(NSString *display);
@property (readonly, nonatomic, copy) Builder * (^buildCamera)(NSString *camera);
@property (readonly, nonatomic, copy) Phone * (^build)(void);
@end
Builder.m:
@interface Builder()
@property (nonatomic, copy) NSString * cpu;
@property (nonatomic, copy) NSString * capacity;
@property (nonatomic, copy) NSString * display;
@property (nonatomic, copy) NSString * camera;
@end
@implementation Builder
- (Builder * (^)(NSString * _Nonnull))buildCpu{
return ^(NSString *cpu){
self.cpu = cpu;
return self;
};
}
- (Builder * (^)(NSString * _Nonnull))buildCapacity{
return ^(NSString *capacity){
self.capacity = capacity;
return self;
};
}
- (Builder * (^)(NSString * _Nonnull))buildDisplay{
return ^(NSString *display){
self.display = display;
return self;
};
}
- (Builder * (^)(NSString * _Nonnull))buildCamera{
return ^(NSString *camera){
self.camera = camera;
return self;
};
}
- (Phone * (^)(void))build{
return ^(void){
Phone *phone = [[Phone alloc] initWithBuild:self];
return phone;
};
}
@end
Phone.h:
@interface Phone : NSObject
- (instancetype)initWithBuild:(Builder *)builder;
@end
Phone.m:
@interface Phone()
@property (nonatomic, copy) NSString * cpu;
@property (nonatomic, copy) NSString * capacity;
@property (nonatomic, copy) NSString * display;
@property (nonatomic, copy) NSString * camera;
@end
@implementation Phone
- (instancetype)initWithBuild:(Builder *)builder{
self = [super init];
if (self) {
_cpu = builder.cpu;
_capacity = builder.capacity;
_display = builder.display;
_camera = builder.camera;
}
return self;
}
使用:
//这里使用方可以是指挥者(Director),也可以直接是业务方
Phone *iphoneXR = [Builder new]
.buildCpu(@"A12")
.buildCapacity(@"256")
.buildDisplay(@"6.1")
.buildCamera(@"12MP")
.build();
Phone *miPhone = [Builder new]
.buildCpu(@"Snapdragon 845")
.buildCapacity(@"128")
.buildDisplay(@"6.21")
.buildCamera(@"12MP")
.build();
继承Builder生成具体的Builder:
@interface IphoneXRBuilder : Builder
@end
@implementation IphoneXRBuilder
- (instancetype)init{
self = [super init];
if (self) {
self.buildCpu(@"A12")
.buildCapacity(@"256")
.buildDisplay(@"6.1")
.buildCamera(@"12MP");
}
return self;
}
@end
//使用
Phone *iphoneXR = [[IphoneXRBuilder alloc] init].build();
原型模式(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
因为学历对象是支持复制的,因此需要遵从协议并实现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
同样地,简历对象也需要遵从协议并实现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的不同。
注:还可以用序列化和反序列化的办法来实现深复制
在这里需要注意的是:
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:
方法