本篇是面向对象设计系列文章的第三篇,讲解的是设计模式中的结构型模式:html
该系列前面的两篇文章:java
外观模式(Facade Pattern):外观模式定义了一个高层接口,为子系统中的一组接口提供一个统一的接口。外观模式又称为门面模式,它是一种结构型设计模式模式。git
定义解读:经过这个高层接口,能够将客户端与子系统解耦:客户端能够不直接访问子系统,而是经过外观类间接地访问;同时也能够提升子系统的独立性和可移植性。github
外观模式包括客户端共有三个成员:编程
客户端类(Client):客户端是意图操做子系统的类,它与外观类直接接触;与外观类间接接触设计模式
外观类(Facade):外观类知晓各个子系统的职责和接口,封装子系统的接口并提供给客户端数组
子系统类(SubSystem):子系统类实现子系统的功能,对外观类一无所知缓存
下面经过类图来看一下各个成员之间的关系:bash
上图中的
method1&2()
方法就是调用SubSystem1
和SubSystem2
的method1()
和method2()
方法。一样适用于method2&3()
。网络
模拟一个智能家居系统。这个智能家居系统能够用一个中央遥控器操做其所接入的一些家具:台灯,音箱,空调等等。
在这里咱们简单操纵几个设备:
有的时候,咱们须要某个设备能够一次执行两个不一样的操做;也可能会须要多个设备共同协做来执行一些任务。好比:
假设咱们能够用遥控器直接开启热风,那么实际上就是两个步骤:
咱们把这两个步骤用一个操做包含起来,一步到位。像这样简化操做步骤的场景比较适合用外观模式。
一样的,咱们想听歌的话,须要四个步骤:
这些步骤咱们也能够装在单独的一个接口里面。
相似的,若是咱们想看DVD的话,步骤会更多,由于DVD须要同时输出声音和影像:
这些接口也能够装在一个单独的接口里。
最后,若是咱们要出门,须要关掉全部家用电器,也不须要一个一个将他们关掉,也只须要一个关掉的总接口就行了,由于这个关掉的总接口里面能够包含全部家用电器的关闭接口。
所以,这些设备能够看作是该智能家居系统的子系统;而这个遥控器则扮演的是外观类的角色。
下面咱们用代码来看一下如何实现这些设计。
由于全部家用电器都有开启和关闭的操做,因此咱们先建立一个家用电器的基类HomeDevice
:
//================== HomeDevice.h ==================
//设备基类
@interface HomeDevice : NSObject
//链接电源
- (void)on;
//关闭电源
- (void)off;
@end
复制代码
而后是继承它的全部家用电器类:
空调类AirConditioner
:
//================== AirConditioner.h ==================
@interface AirConditioner : HomeDevice
//高温模式
- (void)startHighTemperatureMode;
//常温模式
- (void)startMiddleTemperatureMode;
//低温模式
- (void)startLowTemperatureMode;
@end
复制代码
CD Player类:CDPlayer
:
//================== CDPlayer.h ==================
@interface CDPlayer : HomeDevice
- (void)play;
@end
复制代码
DVD Player类:DVDPlayer
:
//================== DVDPlayer.h ==================
@interface DVDPlayer : HomeDevice
- (void)play;
@end
复制代码
音箱类VoiceBox
:
//================== VoiceBox.h ==================
@class CDPlayer;
@class DVDPlayer;
@interface VoiceBox : HomeDevice
//与CDPlayer链接
- (void)connetCDPlayer:(CDPlayer *)cdPlayer;
//与CDPlayer断开链接
- (void)disconnetCDPlayer:(CDPlayer *)cdPlayer;
//与DVD Player链接
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;
//与DVD Player断开链接
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;
@end
复制代码
投影仪类Projecter
:
//================== Projecter.h ==================
@interface Projecter : HomeDevice
//与DVD Player链接
- (void)connetDVDPlayer:(DVDPlayer *)dvdPlayer;
//与DVD Player断开链接
- (void)disconnetDVDPlayer:(DVDPlayer *)dvdPlayer;
@end
复制代码
注意,音箱是能够链接CD Player和DVD Player的;而投影仪只能链接DVD Player
如今咱们把全部的家用电器类和他们的接口都定义好了,下面咱们看一下该实例的外观类HomeDeviceManager
如何设计。
首先咱们看一下客户端指望外观类实现的接口:
//================== HomeDeviceManager.h ==================
@interface HomeDeviceManager : NSObject
//===== 关于空调的接口 =====
//空调吹冷风
- (void)coolWind;
//空调吹热风
- (void)warmWind;
//===== 关于CD Player的接口 =====
//播放CD
- (void)playMusic;
//关掉音乐
- (void)offMusic;
//===== 关于DVD Player的接口 =====
//播放DVD
- (void)playMovie;
//关闭DVD
- (void)offMoive;
//===== 关于总开关的接口 =====
//打开所有家用电器
- (void)allDeviceOn;
//关闭全部家用电器
- (void)allDeviceOff;
@end
复制代码
上面的接口分为了四大类,分别是:
为了便于读者理解,这四类的接口所封装的子系统接口的数量是逐渐增多的。
在看这些接口时如何实现的以前,咱们先看一下外观类是如何保留这些子系统类的实例的。在该代码示例中,这些子系统类的实例在外观类的构造方法里被建立,并且做为外观类的成员变量被保存了下来。
//================== HomeDeviceManager.m ==================
@implementation HomeDeviceManager
{
NSMutableArray *_registeredDevices;//全部注册(被管理的)的家用电器
AirConditioner *_airconditioner;
CDPlayer *_cdPlayer;
DVDPlayer *_dvdPlayer;
VoiceBox *_voiceBox;
Projecter *_projecter;
}
- (instancetype)init{
self = [super init];
if (self) {
_airconditioner = [[AirConditioner alloc] init];
_cdPlayer = [[CDPlayer alloc] init];
_dvdPlayer = [[DVDPlayer alloc] init];
_voiceBox = [[VoiceBox alloc] init];
_projecter = [[Projecter alloc] init];
_registeredDevices = [NSMutableArray arrayWithArray:@[_airconditioner,
_cdPlayer,
_dvdPlayer,
_voiceBox,
_projecter]];
}
return self;
}
复制代码
其中 _registeredDevices
这个成员变量是一个数组,它包含了全部和这个外观类实例关联的子系统实例。
子系统与外观类的关联实现方式不止一种,不做为本文研究重点,如今只需知道外观类保留了这些子系统的实例便可。按照顺序,咱们首先看一下关于空调的接口的实现:
//================== HomeDeviceManager.m ==================
//空调吹冷风
- (void)coolWind{
[_airconditioner on];
[_airconditioner startLowTemperatureMode];
}
//空调吹热风
- (void)warmWind{
[_airconditioner on];
[_airconditioner startHighTemperatureMode];
}
复制代码
吹冷风和吹热风的接口都包含了空调实例的两个接口,第一个都是开启空调,第二个则是对应的冷风和热风的接口。
咱们接着看关于CD Player的接口的实现:
//================== HomeDeviceManager.m ==================
- (void)playMusic{
//1. 开启CDPlayer开关
[_cdPlayer on];
//2. 开启音箱
[_voiceBox on];
//3. 音响与CDPlayer链接
[_voiceBox connetCDPlayer:_cdPlayer];
//4. 播放CDPlayer
[_cdPlayer play];
}
//关掉音乐
- (void)offMusic{
//1. 切掉与音箱的链接
[_voiceBox disconnetCDPlayer:_cdPlayer];
//2. 关掉音箱
[_voiceBox off];
//3. 关掉CDPlayer
[_cdPlayer off];
}
复制代码
在上面的场景分析中提到过,听音乐这个指令要分四个步骤:CD Player和音箱的开启,两者的链接,以及播放CD Player,这也比较符合实际生活中的场景。关掉音乐也是先断开链接再切断电源(虽然直接切断电源也能够)。
接下来咱们看一下关于DVD Player的接口的实现:
//================== HomeDeviceManager.m ==================
- (void)playMovie{
//1. 开启DVD player
[_dvdPlayer on];
//2. 开启音箱
[_voiceBox on];
//3. 音响与DVDPlayer链接
[_voiceBox connetDVDPlayer:_dvdPlayer];
//4. 开启投影仪
[_projecter on];
//5.投影仪与DVDPlayer链接
[_projecter connetDVDPlayer:_dvdPlayer];
//6. 播放DVDPlayer
[_dvdPlayer play];
}
- (void)offMoive{
//1. 切掉音箱与DVDPlayer链接
[_voiceBox disconnetDVDPlayer:_dvdPlayer];
//2. 关掉音箱
[_voiceBox off];
//3. 切掉投影仪与DVDPlayer链接
[_projecter disconnetDVDPlayer:_dvdPlayer];
//4. 关掉投影仪
[_projecter off];
//5. 关掉DVDPlayer
[_dvdPlayer off];
}
复制代码
由于DVD Player要同时链接音箱和投影仪,因此这两个接口封装的子系统接口相对于CD Player的更多一些。
最后咱们看一下关于总开关的接口的实现:
//================== HomeDeviceManager.m ==================
//打开所有家用电器
- (void)allDeviceOn{
[_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
[device on];
}];
}
//关闭全部家用电器
- (void)allDeviceOff{
[_registeredDevices enumerateObjectsUsingBlock:^(HomeDevice *device, NSUInteger idx, BOOL * _Nonnull stop) {
[device off];
}];
}
复制代码
这两个接口是为了方便客户端开启和关闭全部设备的,有这两个接口的话,用户就不用一一开启或关闭多个设备了。
关于这两个接口的实现:
上文说过,该外观类经过一个数组成员变量_registeredDevices
来保存全部可操做的设备。因此若是咱们须要开启或关闭全部的设备就能够遍历这个数组并向每一个元素调用on
或off
方法。由于这些元素都继承于HomeDevice
,也就是都有on
或off
方法。
这样作的好处是,咱们不须要单独列出全部设备来分别调用它们的接口;并且后面若是添加或者删除某些设备的话也不须要修改这两个接口的实现了。
下面咱们看一下该demo多对应的类图。
从上面的UML类图中能够看出,该示例的子系统之间的耦合仍是比较多的;而外观类
HomeDeviceManager
的接口大大简化了User
对这些子系统的使用成本。
SDWebImage
封装了负责图片下载的类和负责图片缓存的类,而外部仅向客户端暴露了简约的下载图片的接口。Spring-JDBC
中的JdbcUtils
封装了Connection
,ResultSet
,Statement
的方法提供给客户端适配器模式(Adapter Pattern) :将一个接口转换成客户但愿的另外一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。适配器模式的别名是包装器模式(Wrapper),是一种结构型设计模式。
定义解读:适配器模式又分为对象适配器和类适配器两种。
适配器模式有三个成员:
如上文所说,适配器模式分为类适配器模式和对象适配器模式,所以这里同时提供这两种细分模式的 UML类图。
对象适配器模式:
对象适配器中,被适配者的对象被适配器所持有。当适配器的
request
方法被调用时,在这个方法内部再调用被适配者对应的方法。
类适配器模式:
类适配器中采用了多继承的方式:适配器同时继承了目标类和被适配者类,也就都持有了者两者的方法。
多继承在Objective-C中能够经过遵循多个协议来实现,在本模式的代码示例中只使用对象适配器来实现。
模拟一个替换缓存组件的场景:目前客户端已经依赖于旧的缓存组件的接口,然后来发现有一个新的缓组件的性能更好一些,须要将旧的缓存组件替换成新的缓存组件,可是新的缓存组件的接口与旧的缓存接口不一致,因此目前来看客户端是没法直接与新缓存组件一块儿工做的。
因为客户端在不少地方依赖了旧缓存组件的接口,将这些地方的接口都换成新缓存组件的接口会比较麻烦,并且万一后面还要换回旧缓存组件或者再换成另一个新的缓存组件的话就还要作重复的事情,这显然是不够优雅的。
所以该场景比较适合使用适配器模式:建立一个适配器,让本来与旧缓存接口的客户端能够与新缓存组件一块儿工做。
在这里,新的缓存组件就是Adaptee
,旧的缓存组件(接口)就是Target
,由于它是直接和客户端接触的。而咱们须要建立一个适配器类Adaptor
来让客户端与新缓存组件一块儿工做。下面用代码看一下上面的问题如何解决:
首先咱们建立旧缓存组件,并让客户端正常使用它。 先建立旧缓存组件的接口OldCacheProtocol
:
对应Java的接口,Objective-C中叫作协议,也就是protocol。
//================== OldCacheProtocol.h ==================
@protocol OldCacheProtocol <NSObject>
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key;
- (id)old_getCacheObjectForKey:(NSString *)key;
@end
复制代码
能够看到该接口包含了两个操做缓存的方法,方法前缀为
old
。
再简单建立一个缓存组件类OldCache
,它实现了OldCacheProtocol
接口:
//================== OldCache.h ==================
@interface OldCache : NSObject <OldCacheProtocol>
@end
//================== OldCache.m ==================
@implementation OldCache
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
NSLog(@"saved cache by old cache object");
}
- (id)old_getCacheObjectForKey:(NSString *)key{
NSString *obj = @"get cache by old cache object";
NSLog(@"%@",obj);
return obj;
}
@end
复制代码
为了读者区分方便,将新旧两个缓存组件取名为
NewCache
和OldCache
。实现代码也比较简单,由于不是本文介绍的重点,只需区分接口名称便可。
如今咱们让客户端来使用这个旧缓存组件:
//================== client.m ==================
@interface ViewController ()
@property (nonatomic, strong) id<OldCacheProtocol>cache;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//使用旧缓存
[self useOldCache];
//使用缓存组件操做
[self saveObject:@"cache" forKey:@"key"];
}
//实例化旧缓存并保存在``cache``属性里
- (void)useOldCache{
self.cache = [[OldCache alloc] init];
}
//使用cache对象
- (void)saveObject:(id)object forKey:(NSString *)key{
[self.cache old_saveCacheObject:object forKey:key];
}
复制代码
- 在这里的客户端就是
ViewController
,它持有一个听从OldCacheProtocol
协议的实例,也就是说它目前依赖于OldCacheProtocol
的接口。useOldCache
方法用来实例化旧缓存并保存在cache
属性里。saveObject:forKey:
方法是真正使用cache对象来保存缓存。
运行并打印一下结果输出是:saved cache by old cache object
。如今看来客户端使用旧缓存是没有问题的。
而如今咱们要加入新的缓存组件了: 首先定义新缓存组件的接口NewCacheProtocol
:
//================== NewCacheProtocol.h ==================
@protocol NewCacheProtocol <NSObject>
- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key;
- (id)new_getCacheObjectForKey:(NSString *)key;
@end
复制代码
能够看到,NewCacheProtocol
与OldCacheProtocol
接口大体是类似的,可是名称仍是不一样,这里使用了不一样的方法前缀作了区分。
接着看一下新缓存组件是如何实现这个接口的:
//================== NewCache.h ==================
@interface NewCache : NSObject <NewCacheProtocol>
@end
//================== NewCache.m ==================
@implementation NewCache
- (void)new_saveCacheObject:(id)obj forKey:(NSString *)key{
NSLog(@"saved cache by new cache object");
}
- (id)new_getCacheObjectForKey:(NSString *)key{
NSString *obj = @"saved cache by new cache object";
NSLog(@"%@",obj);
return obj;
}
@end
复制代码
如今咱们拿到了新的缓存组件,可是客户端类目前依赖的是旧的接口,所以适配器类应该上场了:
//================== Adaptor.h ==================
@interface Adaptor : NSObject <OldCacheProtocol>
- (instancetype)initWithNewCache:(NewCache *)newCache;
@end
//================== Adaptor.m ==================
@implementation Adaptor
{
NewCache *_newCache;
}
- (instancetype)initWithNewCache:(NewCache *)newCache{
self = [super init];
if (self) {
_newCache = newCache;
}
return self;
}
- (void)old_saveCacheObject:(id)obj forKey:(NSString *)key{
//transfer responsibility to new cache object
[_newCache new_saveCacheObject:obj forKey:key];
}
- (id)old_getCacheObjectForKey:(NSString *)key{
//transfer responsibility to new cache object
return [_newCache new_getCacheObjectForKey:key];
}
@end
复制代码
- 首先,适配器类也实现了旧缓存组件的接口;目的是让它也能够接收到客户端操做旧缓存组件的方法。
- 而后,适配器的构造方法里面须要传入新组件类的实例;目的是在收到客户端操做旧缓存组件的命令后,将该命令转发给新缓存组件类,并调用其对应的方法。
- 最后咱们看一下适配器类是如何实现两个旧缓存组件的接口的:在
old_saveCacheObject:forKey:
方法中,让新缓存组件对象调用对应的new_saveCacheObject:forKey:
方法;一样地,在old_getCacheObjectForKey
方法中,让新缓存组件对象调用对应的new_getCacheObjectForKey:
方法。
这样一来,适配器类就定义好了。 那么最后咱们看一下在客户端里面是如何使用适配器的:
//================== client ==================
- (void)viewDidLoad {
[super viewDidLoad];
//使用新缓存组件
[self useNewCache];
[self saveObject:@"cache" forKey:@"key"];
}
- (void)useOldCache{
self.cache = [[OldCache alloc] init];
}
//使用新缓存组件
- (void)useNewCache{
self.cache = [[Adaptor alloc] initWithNewCache:[[NewCache alloc] init]];
}
//使用cache对象
- (void)saveObject:(id)object forKey:(NSString *)key{
[self.cache old_saveCacheObject:object forKey:key];
}
复制代码
咱们能够看到,在客户端里面,只须要改一处就能够了:将咱们定义好的适配器类保存在原来的
cache
属性中就能够了(useNewCache
方法的实现)。而真正操做缓存的方法saveObject:forKey
不须要有任何改动。
咱们能够看到,使用适配器模式,客户端调用旧缓存组件接口的方法都不须要改变;只需稍做处理,就能够在新旧缓存组件中来回切换,也不须要原来客户端对缓存的操做。
而之因此能够作到这么灵活,其实也是由于在一开始客户端只是依赖了旧缓存组件类所实现的接口,而不是旧缓存组件类的类型。有心的读者可能注意到了,上面viewController
的属性是@property (nonatomic, strong) id<OldCacheProtocol>cache;
。正由于如此,咱们新建的适配器实例才能直接用在这里,由于适配器类也是实现了<OldCacheProtocol>
接口。相反,若是咱们的cache
属性是这么写的:@property (nonatomic, strong) OldCache *cache;
,即客户端依赖了旧缓存组件的类型,那么咱们的适配器类就没法这么容易地放在这里了。所以为了咱们的程序在未来能够更好地修改和扩展,依赖接口是一个前提。
下面咱们看一下该代码示例对应的类图:
XMLAdapter
使用了适配器模式。桥接模式(Simple Factory Pattern):将抽象部分与它的实现部分分离,使它们均可以独立地变化。
定义解读:桥接模式的核心是两个抽象以组合的形式关联到一块儿,从而他们的实现就互不依赖了。
若是一个系统存在两个独立变化的维度,并且这两个维度都须要进行扩展的时候比较适合使用桥接模式。
下面来看一下简单工厂模式的成员和类图。
桥接模式一共只有三个成员:
下面经过类图来看一下各个成员之间的关系:
从类图中能够看出
Abstraction
持有Implementor
,可是两者的实现类互不依赖。这就是桥接模式的核心。
建立一些不一样的形状,这些形状带有不一样的颜色。
三种形状:
三种颜色:
根据上述需求,可能有的朋友会这么设计:
这样的设计确实能够实现上面的需求。可是设想一下,若是后来增长了一种颜色或者形状的话,是否是要多出来不少类?若是形状的种类数是m
,颜色的种类数是n
,以这种方式建立的总类数就是 m*n
,当m或n很是大的时候,它们相乘的结果就会变得很大。
咱们观察一下这个场景:形状和颜色这两者的是没有关联性的,两者能够独立扩展和变化,这样的组合比较适合用桥接模式来作。
根据上面提到的桥接模式的成员:
下面咱们用代码看一下该如何设计。
首先咱们建立形状的基类Shape
:
//================== Shape.h ==================
@interface Shape : NSObject
{
@protected Color *_color;
}
- (void)renderColor:(Color *)color;
- (void)show;
@end
//================== Shape.m ==================
@implementation Shape
- (void)renderColor:(Color *)color{
_color = color;
}
- (void)show{
NSLog(@"Show %@ with %@",[self class],[_color class]);
}
@end
复制代码
由上面的代码能够看出:
- 形状类
Shape
持有Color
类的实例,两者是以组合的形式结合到一块儿的。并且Shape
类定义了供外部传入Color
实例的方法renderColor:
:在这个方法里面接收从外部传入的Color
实例并保存起来。- 另一个公共接口
show
实际上就是打印这个图形的名称及其所搭配的颜色,便于咱们后续验证。
接着咱们建立三种不一样的图形类,它们都继承于Shape
类:
正方形类:
//================== Square.h ==================
@interface Square : Shape
@end
//================== Square.m ==================
@implementation Square
- (void)show{
[super show];
}
@end
复制代码
长方形类:
//================== Rectangle.h ==================
@interface Rectangle : Shape
@end
//================== Rectangle.m ==================
@implementation Rectangle
- (void)show{
[super show];
}
@end
复制代码
圆形类:
//================== Circle.h ==================
@interface Circle : Shape
@end
//================== Circle.m ==================
@implementation Circle
- (void)show{
[super show];
}
@end
复制代码
还记得上面的Shape
类持有的Color
类么?它就是全部颜色类的父类:
//================== Color.h ==================
@interface Color : NSObject
@end
//================== Color.m ==================
@implementation Color
@end
复制代码
接着咱们建立继承这个Color
类的三个颜色类:
红色类:
//================== RedColor.h ==================
@interface RedColor : Color
@end
//================== RedColor.m ==================
@implementation RedColor
@end
复制代码
绿色类:
//================== GreenColor.h ==================
@interface GreenColor : Color
@end
//================== GreenColor.m ==================
@implementation GreenColor
@end
复制代码
蓝色类:
//================== BlueColor.h ==================
@interface BlueColor : Color
@end
//================== BlueColor.m ==================
@implementation BlueColor
@end
复制代码
OK,到如今全部的形状类和颜色类的相关类已经建立好了,咱们看一下客户端是如何使用它们来组合成不一样的带有颜色的形状的:
//================== client ==================
//create 3 shape instances
Rectangle *rect = [[Rectangle alloc] init];
Circle *circle = [[Circle alloc] init];
Square *square = [[Square alloc] init];
//create 3 color instances
RedColor *red = [[RedColor alloc] init];
GreenColor *green = [[GreenColor alloc] init];
BlueColor *blue = [[BlueColor alloc] init];
//rect & red color
[rect renderColor:red];
[rect show];
//rect & green color
[rect renderColor:green];
[rect show];
//circle & blue color
[circle renderColor:blue];
[circle show];
//circle & green color
[circle renderColor:green];
[circle show];
//square & blue color
[square renderColor:blue];
[square show];
//square & red color
[square renderColor:red];
[square show];
复制代码
上面的代码里,咱们先声明了全部的形状类和颜色类的实例,而后自由搭配,造成不一样的形状+颜色的组合。
下面咱们经过打印的结果来看一下组合的效果:
Show Rectangle with RedColor
Show Rectangle with GreenColor
Show Circle with BlueColor
Show Circle with GreenColor
Show Square with BlueColor
Show Square with RedColor
复制代码
从打印的接口能够看出组合的结果是没问题的。
跟上面没有使用桥接模式的设计相比,使用桥接模式须要的类的总和是 m + n
:当m或n的值很大的时候是远小于 m * n
(没有使用桥接,而是使用继承的方式)的。
并且若是后面还要增长形状和颜色的话,使用桥接模式就能够很方便地将原有的形状和颜色和新的形状和颜色进行搭配了,新的类和旧的类互不干扰。
下面咱们看一下上面代码所对应的类图:
从UML类图能够看出,该设计是由两个抽象层的类
Shape
和Color
构建的,正由于依赖的双方都是抽象类(而不是具体的实现),并且两者是以组合的方式联系到一块儿的,因此扩展起来很是方便,互不干扰。这对于从此咱们对代码的设计有比较好的借鉴意义。
Spring-JDBC
中的DriveManager
经过registerDriver
方法注册不一样类型的驱动代理模式(Proxy Pattern) :为某个对象提供一个代理,并由这个代理对象控制对原对象的访问。
定义解读:使用代理模式之后,客户端直接访问代理,代理在客户端和目标对象之间起到中介的做用。
在某些状况下,一个客户不想或者不能直接引用一个对象,此时能够经过一个称之为“代理”的第三者来实现间接引用。
由于代理对象能够在客户端和目标对象之间起到中介的做用,所以能够经过代理对象去掉客户不能看到 的内容和服务或者添加客户须要的额外服务。
根据业务的不一样,代理也能够有不一样的类型:
下面来看一下代理模式的成员和类图。
代理模式算上客户端一共有四个成员:
其实我也不太清楚代理模式里面为何会是Subject和RealSubject这个叫法。
下面经过类图来看一下各个成员之间的关系:
从类图中能够看出,工厂类提供一个静态方法:经过传入的字符串来制造其所对应的产品。
在这里举一个买房者经过买房中介买房的例子。
如今通常咱们买房子不直接接触房东,而是先接触中介,买房的相关合同和一些事宜能够先和中介进行沟通。
在本例中,咱们在这里让买房者直接支付费用给中介,而后中介收取一部分的中介费, 再将剩余的钱交给房东。
中介做为房东的代理,与买房者直接接触。并且中介还须要在真正交易前作其余的事情(收取中介费,帮买房者check房源的真实性等等),所以该场景比较适合使用代理模式。
根据上面的代理模式的成员:
客户端就是买房者
代理就是中介
真实主题就是房东
中介和房东都会实现收钱的方法,咱们能够定义一个抽象主题类,它有一个公共接口是收钱的方法。
首先咱们定义一下房东和代理须要实现的接口PaymentInterface
(在类图里面是继承某个共同对象,我我的比较习惯用接口来作)。
//================== PaymentInterface.h ==================
@protocol PaymentInterface <NSObject>
- (void)getPayment:(double)money;
@end
复制代码
这个接口声明了中介和房东都须要实现的方法getPayment:
接着咱们声明代理类HouseProxy
:
//================== HouseProxy.h ==================
@interface HouseProxy : NSObject<PaymentInterface>
@end
//================== HouseProxy.m ==================
const float agentFeeRatio = 0.35;
@interface HouseProxy()
@property (nonatomic, copy) HouseOwner *houseOwner;
@end
@implementation HouseProxy
- (void)getPayment:(double)money{
double agentFee = agentFeeRatio * money;
NSLog(@"Proxy get payment : %.2lf",agentFee);
[self.houseOwner getPayment:(money - agentFee)];
}
- (HouseOwner *)houseOwner{
if (!_houseOwner) {
_houseOwner = [[HouseOwner alloc] init];
}
return _houseOwner;
}
@end
复制代码
在HouseProxy
里面,持有了房东,也就是被代理者的实例。而后在的getPayment:
方法里,调用了房东实例的getPayment:
方法。并且咱们能够看到,在调用房东实例的getPayment:
方法,代理先拿到了中介费(中介费比率agentFeeRatio
定义为0.35,即中介费的比例占35%)。
这里面除了房东实例的getPayment:
方法以外的一些操做就是代理存在的意义:它能够在真正被代理对象作事情以前,以后作一些其余额外的事情。好比相似AOP编程同样,定义相似的before***Method
或是after**Method
方法等等。
最后咱们看一下房东是如何实现getPayment:
方法的:
//================== HouseOwner.h ==================
@interface HouseOwner : NSObject<PaymentInterface>
@end
//================== HouseOwner.m ==================
@implementation HouseOwner
- (void)getPayment:(double)money{
NSLog(@"House owner get payment : %.2lf",money);
}
@end
复制代码
房东类HouseOwner
按照本身的方式实现了getPayment:
方法。
不少时候被代理者(委托者)能够彻底按照本身的方式去作事情,而把一些额外的事情交给代理来作,这样能够保持原有类的功能的纯粹性,符合开闭原则。
下面咱们看一下客户端的使用以及打印出来的结果:
客户端代码:
//================== client.m ==================
HouseProxy *proxy = [[HouseProxy alloc] init];
[proxy getPayment:100];
复制代码
上面的客户端支付给了中介100元。
下面咱们看一下打印结果:
Proxy get payment : 35.00
House owner get payment : 65.00
复制代码
和预想的同样,中介费收取了35%的中介费,剩下的交给了房东。
从UML类图中咱们能够看出,在这里没有使用抽象主题对象,而是用一个接口来分别让中介和房东实现。
JDKDynamicAopProxy
是对JDK的动态代理进行了封装装饰模式(Decorator Pattern) :不改变原有对象的前提下,动态地给一个对象增长一些额外的功能。
装饰者模式一共有四个成员:
模拟沙拉的制做:沙拉由沙拉底和酱汁两个部分组成,不一样的沙拉底和酱汁搭配能够组成不一样的沙拉。
沙拉底 | 价格 |
---|---|
蔬菜 | 5 |
鸡肉 | 10 |
牛肉 | 16 |
酱汁 | 价格 |
---|---|
醋汁 | 2 |
花生酱 | 4 |
蓝莓酱 | 6 |
注意:同一份沙拉底能够搭配多钟酱汁,并且酱汁的份数也能够不止一份。
由于选择一个沙拉底以后,能够随意添加不一样份数和种类的酱汁,也就是在原有的沙拉对象增长新的对象,因此比较适合用装饰者模式来设计:酱汁至关于装饰者,而沙拉底则是被装饰的构件。
下面咱们用代码看一下如何实现该场景。
首先咱们定义 抽象构件,也就是沙拉类的基类Salad
:
//================== Salad.h ==================
@interface Salad : NSObject
- (NSString *)getDescription;
- (double)price;
@end
复制代码
getDescription
和price
方法用来描述当前沙拉的配置以及价格(由于随着装饰者的装饰,这两个数据会一直变化)。
下面咱们再声明装饰者的基类SauceDecorator
。按照装饰者设计模式类图,该类是继承于沙拉类的:
//================== SauceDecorator.h ==================
@interface SauceDecorator : Salad
@property (nonatomic, strong) Salad *salad;
- (instancetype)initWithSalad:(Salad *)salad;
@end
//================== SauceDecorator.m ==================
@implementation SauceDecorator
- (instancetype)initWithSalad:(Salad *)salad{
self = [super init];
if (self) {
self.salad = salad;
}
return self;
}
@end
复制代码
在装饰者的构造方法里面传入
Salad
类的实例,并将它保存下来,目的是为了在装饰它的时候用到。
如今抽象构件和装饰者的基类都建立好了,下面咱们建立具体构件和具体装饰者。首先咱们建立具体构件:
蔬菜沙拉VegetableSalad
:
//================== VegetableSalad.h ==================
@interface VegetableSalad : Salad
@end
//================== VegetableSalad.m ==================
@implementation VegetableSalad
- (NSString *)getDescription{
return @"[Vegetable Salad]";
}
- (double)price{
return 5.0;
}
@end
复制代码
首先
getDescription
方法返回的是蔬菜沙拉底的描述;而后price
方法返回的是它所对应的价格。
相似的,咱们继续按照价格表来建立鸡肉沙拉底和牛肉沙拉底:
鸡肉沙拉底:
//================== ChickenSalad.h ==================
@interface ChickenSalad : Salad
@end
//================== ChickenSalad.m ==================
@implementation ChickenSalad
- (NSString *)getDescription{
return @"[Chicken Salad]";
}
- (double)price{
return 10.0;
}
@end
复制代码
牛肉沙拉底:
//================== BeefSalad.h ==================
@interface BeefSalad : Salad
@end
//================== BeefSalad.m ==================
@implementation BeefSalad
- (NSString *)getDescription{
return @"[Beef Salad]";
}
- (double)price{
return 16.0;
}
@end
复制代码
如今全部的被装饰者建立好了,下面咱们按照酱汁的价格表来建立酱汁类(也就是具体装饰者):
首先看一下醋汁VinegarSauceDecorator
:
//================== VinegarSauceDecorator.h ==================
@interface VinegarSauceDecorator : SauceDecorator
@end
//================== VinegarSauceDecorator.m ==================
@implementation VinegarSauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + vinegar sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 2.0;
}
@end
复制代码
重写了
getDescription
方法,并添加了本身的装饰,即在原来的描述上增长了+ vinegar sauce
字符串。之因此能够获取到原有的描述,是由于在构造方法里已经获取了被装饰者的对象(在装饰者基类中定义的方法)。一样地,价格也在原来的基础上增长了本身的价格。
如今咱们知道了具体装饰者的设计,以此类推,咱们看一下花生酱和蓝莓酱类如何定义:
花生酱PeanutButterSauceDecorator
类:
//================== PeanutButterSauceDecorator.h ==================
@interface PeanutButterSauceDecorator : SauceDecorator
@end
//================== PeanutButterSauceDecorator.m ==================
@implementation PeanutButterSauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + peanut butter sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 4.0;
}
@end
复制代码
蓝莓酱类BlueBerrySauceDecorator
:
//================== BlueBerrySauceDecorator.h ==================
@interface BlueBerrySauceDecorator : SauceDecorator
@end
//================== BlueBerrySauceDecorator.m ==================
@implementation BlueBerrySauceDecorator
- (NSString *)getDescription{
return [NSString stringWithFormat:@"%@ + blueberry sauce",[self.salad getDescription]];
}
- (double)price{
return [self.salad price] + 6.0;
}
@end
复制代码
OK,到如今全部的类已经定义好了,为了验证是否实现正确,下面用客户端尝试着搭配几种不一样的沙拉吧:
首先咱们看第一个搭配:
//================== client ==================
//vegetable salad add vinegar sauce
Salad *vegetableSalad = [[VegetableSalad alloc] init];
NSLog(@"%@",vegetableSalad);
vegetableSalad = [[VinegarSauceDecorator alloc] initWithSalad:vegetableSalad];
NSLog(@"%@",vegetableSalad);
复制代码
第一次打印输出:This salad is: [Vegetable Salad] and the price is: 5.00
第二次打印输出:This salad is: [Vegetable Salad] + vinegar sauce and the price is: 7.00
上面代码中,咱们首先建立了蔬菜底,而后再让醋汁装饰它(将蔬菜底的实例传入醋汁装饰者的构造方法中)。最后咱们打印这个蔬菜底对象,描述和价格和装饰以前的确实发生了变化,说明咱们的代码没有问题。
接着咱们看第二个搭配:
//================== client ==================
//beef salad add two peanut butter sauce:
Salad *beefSalad = [[BeefSalad alloc] init];
NSLog(@"%@",beefSalad);
beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);
beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);
复制代码
第一次打印输出:[Beef Salad] and the price is: 16.00
第二次打印输出:[Beef Salad] + peanut butter sauce and the price is: 20.00
第三次打印输出:[Beef Salad] + peanut butter sauce + peanut butter sauce and the price is: 24.00
和上面的代码实现相似,都是先建立沙拉底(此次是牛肉底),而后再添加调料。因为是分两次装饰,因此要再写一次花生酱的装饰代码。对比每次打印的结果和上面的价格表能够看出输出是正确的。
这个例子是加了两次相同的酱汁,最后咱们看第三个搭配,加入的是不一样的两个酱汁:
//================== client ==================
//chiken salad add peanut butter sauce and blueberry sauce
Salad *chikenSalad = [[ChickenSalad alloc] init];
NSLog(@"%@",chikenSalad);
chikenSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);
chikenSalad = [[BlueBerrySauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);
复制代码
第一次打印输出:[Chicken Salad] and the price is: 10.00
第二次打印输出:[Chicken Salad] + peanut butter sauce and the price is: 14.00
第三次打印输出:[Chicken Salad] + peanut butter sauce + blueberry sauce and the price is: 20.00
对比每次打印的结果和上面的价格表能够看出输出是正确的。
到这里,该场景就模拟结束了。能够试想一下,若是从此加了其余的沙拉底和酱汁的话,只须要分别继承Salad
类和SauceDecorator
类就能够了,现有的代码并不须要更改;并且通过不一样组合能够搭配出更多种类的沙拉。
下面咱们看一下该代码实现对应的类图。
BufferReader
继承了Reader
,在BufferReader
的构造器中传入了Reader
,实现了装饰享元模式(Flyweight Pattern):运用共享技术复用大量细粒度的对象,下降程序内存的占用,提升程序的性能。
定义解读:
享元模式一共有三个成员:
这里咱们使用《Objective-C 编程之道:iOS设计模式解析》里的第21章使用的例子:在一个页面展现数百个大小,位置不一样的花的图片,然而这些花的样式只有6种。
看一下截图:
因为这里咱们须要建立不少对象,而这些对象有能够共享的内部状态(6种图片内容)以及不一样的外部状态(随机的,数百个位置坐标和图片大小),所以比较适合使用享元模式来作。
根据上面提到的享元模式的成员:
UIImageView
,它能够显示图片UIImageView
,由于后续咱们能够直接添加更多其余的属性。下面咱们看一下用代码如何实现:
首先咱们建立一个工厂,这个工厂能够根据所传入花的类型来返回花内部图片对象,在这里能够直接使用原生的UIImage
对象,也就是图片对象。并且这个工厂持有一个保存图片对象的池子:
下面咱们看一下代码是如何实现的:
//================== FlowerFactory.h ==================
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
@interface FlowerFactory : NSObject
- (FlowerImageView *) flowerImageWithType:(FlowerType)type
@end
//================== FlowerFactory.m ==================
@implementation FlowerFactory
{
NSMutableDictionary *_flowersPool;
}
- (FlowerImageView *) flowerImageWithType:(FlowerType)type
{
if (_flowersPool == nil){
_flowersPool = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
}
//尝试获取传入类型对应的花内部图片对象
UIImage *flowerImage = [_flowersPool objectForKey:[NSNumber numberWithInt:type]];
//若是没有对应类型的图片,则生成一个
if (flowerImage == nil){
NSLog(@"create new flower image with type:%u",type);
switch (type){
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
flowerImage = nil;
break;
}
[_flowersPool setObject:flowerImage forKey:[NSNumber numberWithInt:type]];
}else{
//若是有对应类型的图片,则直接使用
NSLog(@"reuse flower image with type:%u",type);
}
//建立花对象,将上面拿到的花内部图片对象赋值并返回
FlowerImageView *flowerImageView = [[FlowerImageView alloc] initWithImage:flowerImage];
return flowerImageView;
}
复制代码
- 在这个工厂类里面定义了六中图片的类型
- 该工厂类持有
_flowersPool
私有成员变量,保存新建立过的图片。flowerImageWithType:
的实现:结合了_flowersPool
:当_flowersPool
没有对应的图片时,新建立图片并返回;不然直接从_flowersPool
获取对应的图片并返回。
接着咱们定义这些花对象FlowerImageView
:
//================== FlowerImageView.h ==================
@interface FlowerImageView : UIImageView
@end
//================== FlowerImageView.m ==================
@implementation FlowerImageView
@end
复制代码
在这里面其实也能够直接使用
UIImageView
,之因此建立一个子类是为了后面能够更好地扩展这些花独有的一些属性。注意一下花对象和花内部图片对象的区别:花对象
FlowerImageView
是包含花内部图片对象的UIImage
。由于在Objective-C里面,UIImage
是FlowerImageView
所继承的UIImageView
的一个属性,因此在这里FlowerImageView
就直接包含了UIImage
。
下面咱们来看一下客户端如何使用FlowerFactory
和FlowerImageView
这两个类:
//================== client ==================
//首先建造一个生产花内部图片对象的工厂
FlowerFactory *factory = [[FlowerFactory alloc] init];
for (int i = 0; i < 500; ++i)
{
//随机传入一个花的类型,让工厂返回该类型对应花类型的花对象
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
FlowerImageView *flowerImageView = [factory flowerImageWithType:flowerType];
// 建立花对象的外部属性值(随机的位置和大小)
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
//将位置和大小赋予花对象
flowerImageView.frame = CGRectMake(x, y, size, size);
//展现这个花对象
[self.view addSubview:flowerImageView];
}
复制代码
上面代码里面是生成了500朵位置和大小都是随机的花内部图片对象。这500朵花最主要的区别仍是它们的位置和大小;而它们使用的花的图片对象只有6个,所以能够用专门的Factory
来生成和管理这些少数的花内部图片对象,从工厂的打印咱们能够看出来:
create new flower image with type:1
create new flower image with type:3
create new flower image with type:4
reuse flower image with type:3
create new flower image with type:5
create new flower image with type:2
create new flower image with type:0
reuse flower image with type:5
reuse flower image with type:5
reuse flower image with type:4
reuse flower image with type:1
reuse flower image with type:3
reuse flower image with type:4
reuse flower image with type:0
复制代码
从上面的打印结果能够看出,在六种图片都建立好之后,再获取时就直接拿生成过的图片了,在必定程度上减小了内存的开销。
下面咱们来看一下该代码示例对应的UML类图。
这里须要注意的是
- 工厂和花对象是组合关系:
FlowerFactroy
生成了多个FlowerImageView
对象,也就是花的内部图片对象,两者的关系属于强关系,由于在该例子中两者若是分离而独立存在都将会失去意义,因此在UML类图中用了组合的关系(实心菱形)。- 抽象享元类是
UIImageView
,它的一个内部对象是UIImage
(这两个都是Objective-C原生的关于图片的类)。- 客户端依赖的对象是工厂对象和花对象,而对花的内部图片对象
UIImage
能够一无所知,由于它是被FlowerFactroy
建立并被FlowerImageView
所持有的。(可是由于UIImage
是FlowerImageView
的一个外部能够引用的属性,因此在这里客户端仍是能够访问到UIImage
,这是Objective-C原生的实现。后面咱们在用享元模式的时候能够不将内部属性暴露出来)
使用享元模式须要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
对象在缓冲池中的复用须要考虑线程问题。
UITableViewCell
的复用池就是使用享元模式的一个例子。Integer
类的valueOf
方法,若是传入的值的区间在[IntegerCache.low,IntegerCache.high]
中的话,则直接从缓存里获取;不然就建立一个新的Integer
。到这里设计模式中的结构型模式就介绍完了,读者能够结合UML类图和demo的代码来理解每一个设计模式的特色和相互之间的区别,但愿读者能够有所收获。
另外,本篇博客的代码和类图都保存在个人GitHub库中:object-oriented-design chapter 2.2
该系列前面的两篇文章:
本篇的下一篇是面向对象系列的第四篇,讲解的是面向对象设计模式中的行为型模式。
本篇已同步到我的博客:面向对象设计的设计模式(二):结构型模式(附 Demo 及 UML 类图)
笔者在近期开通了我的公众号,主要分享编程,读书笔记,思考类的文章。
由于公众号天天发布的消息数有限制,因此到目前为止尚未将全部过去的精选文章都发布在公众号上,后续会逐步发布的。
并且由于各大博客平台的各类限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~
扫下方的公众号二维码并点击关注,期待与您的共同成长~