面向对象设计的设计模式(二):结构型模式(附 Demo & UML类图)

本篇是面向对象设计系列文章的第三篇,讲解的是设计模式中的结构型模式:html

  • 外观模式
  • 适配器模式
  • 桥接模式
  • 代理模式
  • 装饰者模式
  • 享元模式

该系列前面的两篇文章:java

一. 外观模式

定义

外观模式(Facade Pattern):外观模式定义了一个高层接口,为子系统中的一组接口提供一个统一的接口。外观模式又称为门面模式,它是一种结构型设计模式模式。git

定义解读:经过这个高层接口,能够将客户端与子系统解耦:客户端能够不直接访问子系统,而是经过外观类间接地访问;同时也能够提升子系统的独立性和可移植性。github

适用场景

  • 子系统随着业务复杂度的提高而变得愈来愈复杂,客户端须要某些子系统共同协做来完成某个任务。
  • 在多层结构的系统中,使用外观对象能够做为每层的入口来简化层间的调用。

成员与类图

成员

外观模式包括客户端共有三个成员:编程

  • 客户端类(Client):客户端是意图操做子系统的类,它与外观类直接接触;与外观类间接接触设计模式

  • 外观类(Facade):外观类知晓各个子系统的职责和接口,封装子系统的接口并提供给客户端数组

  • 子系统类(SubSystem):子系统类实现子系统的功能,对外观类一无所知缓存

下面经过类图来看一下各个成员之间的关系:bash

模式类图

外观模式类图

上图中的method1&2()方法就是调用SubSystem1SubSystem2method1()method2()方法。一样适用于method2&3()网络

代码示例

场景概述

模拟一个智能家居系统。这个智能家居系统能够用一个中央遥控器操做其所接入的一些家具:台灯,音箱,空调等等。

在这里咱们简单操纵几个设备:

  • 空调
  • CD Player
  • DVD Player
  • 音箱
  • 投影仪

场景分析

有的时候,咱们须要某个设备能够一次执行两个不一样的操做;也可能会须要多个设备共同协做来执行一些任务。好比:

假设咱们能够用遥控器直接开启热风,那么实际上就是两个步骤:

  1. 开启空调
  2. 空调切换为热风模式

咱们把这两个步骤用一个操做包含起来,一步到位。像这样简化操做步骤的场景比较适合用外观模式。

一样的,咱们想听歌的话,须要四个步骤:

  1. 开启CD Player
  2. 开启音箱
  3. 链接CD Player和音箱
  4. 播放CD Player

这些步骤咱们也能够装在单独的一个接口里面。

相似的,若是咱们想看DVD的话,步骤会更多,由于DVD须要同时输出声音和影像:

  1. 开启DVD player
  2. 开启音箱
  3. 音响与DVD Player链接
  4. 开启投影仪
  5. 投影仪与DVD Player链接
  6. 播放DVD Player

这些接口也能够装在一个单独的接口里。

最后,若是咱们要出门,须要关掉全部家用电器,也不须要一个一个将他们关掉,也只须要一个关掉的总接口就行了,由于这个关掉的总接口里面能够包含全部家用电器的关闭接口。

所以,这些设备能够看作是该智能家居系统的子系统;而这个遥控器则扮演的是外观类的角色。

下面咱们用代码来看一下如何实现这些设计。

代码实现

由于全部家用电器都有开启和关闭的操做,因此咱们先建立一个家用电器的基类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
复制代码

上面的接口分为了四大类,分别是:

  • 关于空调的接口
  • 关于CD Player的接口
  • 关于DVD Player的接口
  • 关于总开关的接口

为了便于读者理解,这四类的接口所封装的子系统接口的数量是逐渐增多的。

在看这些接口时如何实现的以前,咱们先看一下外观类是如何保留这些子系统类的实例的。在该代码示例中,这些子系统类的实例在外观类的构造方法里被建立,并且做为外观类的成员变量被保存了下来。

//================== 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来保存全部可操做的设备。因此若是咱们须要开启或关闭全部的设备就能够遍历这个数组并向每一个元素调用onoff方法。由于这些元素都继承于HomeDevice,也就是都有onoff方法。

这样作的好处是,咱们不须要单独列出全部设备来分别调用它们的接口;并且后面若是添加或者删除某些设备的话也不须要修改这两个接口的实现了。

下面咱们看一下该demo多对应的类图。

代码对应的类图

外观模式代码示例类图

从上面的UML类图中能够看出,该示例的子系统之间的耦合仍是比较多的;而外观类HomeDeviceManager的接口大大简化了User对这些子系统的使用成本。

优势

  • 实现了客户端与子系统间的解耦:客户端无需知道子系统的接口,简化了客户端调用子系统的调用过程,使得子系统使用起来更加容易。同时便于子系统的扩展和维护。
  • 符合迪米特法则(最少知道原则):子系统只须要将须要外部调用的接口暴露给外观类便可,并且他的接口则能够隐藏起来。

缺点

  • 违背了开闭原则:在不引入抽象外观类的状况下,增长新的子系统可能须要修改外观类或客户端的代码。

Objective-C & Java的实践

  • Objective-C:SDWebImage封装了负责图片下载的类和负责图片缓存的类,而外部仅向客户端暴露了简约的下载图片的接口。
  • Java:Spring-JDBC中的JdbcUtils封装了ConnectionResultSetStatement的方法提供给客户端

二. 适配器模式

定义

适配器模式(Adapter Pattern) :将一个接口转换成客户但愿的另外一个接口,使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。适配器模式的别名是包装器模式(Wrapper),是一种结构型设计模式。

定义解读:适配器模式又分为对象适配器和类适配器两种。

  • 对象适配器:利用组合的方式将请求转发给被适配者。
  • 类适配器:经过适配器类多重继承目标接口和被适配者,将目标方法的调用转接到调用被适配者的方法。

适用场景

  • 想使用一个已经存在的类,可是这个类的接口不符合咱们的要求,缘由多是和系统内的其余须要合做的类不兼容。
  • 想建立一个功能上能够复用的类,这个类可能须要和将来某些未知接口的类一块儿工做。

成员与类图

成员

适配器模式有三个成员:

  • 目标(Target):客户端但愿直接接触的类,给客户端提供了调用的接口
  • 被适配者(Adaptee):被适配者是已经存在的类,即须要被适配的类
  • 适配器(Adapter):适配器对Adaptee的接口和Target的接口进行适配

模式类图

如上文所说,适配器模式分为类适配器模式和对象适配器模式,所以这里同时提供这两种细分模式的 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
复制代码

为了读者区分方便,将新旧两个缓存组件取名为NewCacheOldCache。实现代码也比较简单,由于不是本文介绍的重点,只需区分接口名称便可。

如今咱们让客户端来使用这个旧缓存组件:

//================== 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
复制代码

能够看到,NewCacheProtocolOldCacheProtocol接口大体是类似的,可是名称仍是不一样,这里使用了不一样的方法前缀作了区分。

接着看一下新缓存组件是如何实现这个接口的:

//================== 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;,即客户端依赖了旧缓存组件的类型,那么咱们的适配器类就没法这么容易地放在这里了。所以为了咱们的程序在未来能够更好地修改和扩展,依赖接口是一个前提。

下面咱们看一下该代码示例对应的类图:

代码对应的类图

适配器模式代码示例类图

优势

  • 符合开闭原则:使用适配器而不须要改变现有类,提升类的复用性。
  • 目标类和适配器类解耦,提升程序扩展性。

缺点

  • 增长了系统的复杂性

Objective-C & Java的实践

  • Objective-C:暂时未发现适配器模式的实践,有知道的同窗能够留言
  • Java:JDK中的XMLAdapter使用了适配器模式。

三. 桥接模式

定义

桥接模式(Simple Factory Pattern):将抽象部分与它的实现部分分离,使它们均可以独立地变化。

定义解读:桥接模式的核心是两个抽象以组合的形式关联到一块儿,从而他们的实现就互不依赖了。

适用场景

若是一个系统存在两个独立变化的维度,并且这两个维度都须要进行扩展的时候比较适合使用桥接模式。

下面来看一下简单工厂模式的成员和类图。

成员与类图

成员

桥接模式一共只有三个成员:

  • 抽象类(Abstraction):抽象类维护一个实现部分的对象的引用,并声明调用实现部分的对象的接口。
  • 扩展抽象类(RefinedAbstraction):扩展抽象类定义跟实际业务相关的方法。
  • 实现类接口(Implementor):实现类接口定义实现部分的接口。
  • 具体实现类(ConcreteImplementor):具体实现类具体实现类是实现实现类接口的对象。

下面经过类图来看一下各个成员之间的关系:

模式类图

桥接模式类图

从类图中能够看出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类图能够看出,该设计是由两个抽象层的类ShapeColor构建的,正由于依赖的双方都是抽象类(而不是具体的实现),并且两者是以组合的方式联系到一块儿的,因此扩展起来很是方便,互不干扰。这对于从此咱们对代码的设计有比较好的借鉴意义。

优势

  • 扩展性好,符合开闭原则:将抽象与实现分离,让两者能够独立变化

缺点

  • 在设计以前,须要识别出两个独立变化的维度。

Objective-C & Java的实践

  • Objective-C:暂时未发现桥接模式的实践,有知道的同窗能够留言
  • Java:Spring-JDBC中的DriveManager经过registerDriver方法注册不一样类型的驱动

四. 代理模式

定义

代理模式(Proxy Pattern) :为某个对象提供一个代理,并由这个代理对象控制对原对象的访问。

定义解读:使用代理模式之后,客户端直接访问代理,代理在客户端和目标对象之间起到中介的做用。

适用场景

在某些状况下,一个客户不想或者不能直接引用一个对象,此时能够经过一个称之为“代理”的第三者来实现间接引用。

由于代理对象能够在客户端和目标对象之间起到中介的做用,所以能够经过代理对象去掉客户不能看到 的内容和服务或者添加客户须要的额外服务。

根据业务的不一样,代理也能够有不一样的类型:

  • 远程代理:为位于不一样地址或网络化中的对象提供本地表明。
  • 虚拟代理:根据要求建立重型的对象。
  • 保护代理:根据不一样访问权限控制对原对象的访问。

下面来看一下代理模式的成员和类图。

成员与类图

成员

代理模式算上客户端一共有四个成员:

  • 客户端(Client):客户端意图访问真是主体接口
  • 抽象主题(Subejct):抽象主题定义客户端须要访问的接口
  • 代理(Proxy):代理继承于抽象主题,目的是为了它持有真实目标的实例的引用,客户端直接访问代理
  • 真实主题(RealSubject):真实主题便是被代理的对象,它也继承于抽象主题,它的实例被代理所持有,它的接口被包装在了代理的接口中,并且客户端没法直接访问真实主题对象。

其实我也不太清楚代理模式里面为何会是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类图中咱们能够看出,在这里没有使用抽象主题对象,而是用一个接口来分别让中介和房东实现。

优势

  • 下降系统的耦合度:代理模式可以协调调用者和被调用者,在必定程度上下降了系 统的耦合度。
  • 不一样类型的代理能够对客户端对目标对象的访问进行不一样的控制:
    • 远程代理,使得客户端能够访问在远程机器上的对象,远程机器 可能具备更好的计算性能与处理速度,能够快速响应并处理客户端请求。
    • 虚拟代理经过使用一个小对象来表明一个大对象,能够减小系统资源的消耗,对系统进行优化并提升运行速度。
    • 保护代理能够控制客户端对真实对象的使用权限。

缺点

  • 因为在客户端和被代理对象之间增长了代理对象,所以可能会让客户端请求的速度变慢。

Objective-C & Java的实践

  • iOS SDK:NSProxy能够为持有的对象进行消息转发
  • JDK:AOP下的JDKDynamicAopProxy是对JDK的动态代理进行了封装

五. 装饰者模式

定义

装饰模式(Decorator Pattern) :不改变原有对象的前提下,动态地给一个对象增长一些额外的功能。

适用场景

  • 动态地给一个对象增长职责(功能),这些职责(功能)也能够动态地被撤销。
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时。

成员与类图

成员

装饰者模式一共有四个成员:

  1. 抽象构件(Component):抽象构件定义一个对象(接口),能够动态地给这些对象添加职责。
  2. 具体构件(Concrete Component):具体构件是抽象构件的实例。
  3. 装饰(Decorator):装饰类也继承于抽象构件,它持有一个具体构件对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰(Concrete Decorator):具体装饰负责给具体构建对象实例添加上附加的责任。

模式类图

装饰者模式类图

代码示例

场景概述

模拟沙拉的制做:沙拉由沙拉底和酱汁两个部分组成,不一样的沙拉底和酱汁搭配能够组成不一样的沙拉。

沙拉底 价格
蔬菜 5
鸡肉 10
牛肉 16
酱汁 价格
醋汁 2
花生酱 4
蓝莓酱 6

注意:同一份沙拉底能够搭配多钟酱汁,并且酱汁的份数也能够不止一份。

场景分析

由于选择一个沙拉底以后,能够随意添加不一样份数和种类的酱汁,也就是在原有的沙拉对象增长新的对象,因此比较适合用装饰者模式来设计:酱汁至关于装饰者,而沙拉底则是被装饰的构件。

下面咱们用代码看一下如何实现该场景。

代码实现

首先咱们定义 抽象构件,也就是沙拉类的基类Salad

//================== Salad.h ==================

@interface Salad : NSObject

- (NSString *)getDescription;

- (double)price;

@end
复制代码

getDescriptionprice方法用来描述当前沙拉的配置以及价格(由于随着装饰者的装饰,这两个数据会一直变化)。

下面咱们再声明装饰者的基类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,到如今全部的类已经定义好了,为了验证是否实现正确,下面用客户端尝试着搭配几种不一样的沙拉吧:

  1. 蔬菜加单份醋汁沙拉(7元)
  2. 牛肉加双份花生酱沙拉(24元)
  3. 鸡肉加单份花生酱再加单份蓝莓酱沙拉(20元)

首先咱们看第一个搭配:

//================== 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类就能够了,现有的代码并不须要更改;并且通过不一样组合能够搭配出更多种类的沙拉。

下面咱们看一下该代码实现对应的类图。

代码对应的类图

装饰者模式代码示例类图

优势

  • 比继承更加灵活:不一样于在编译期起做用的继承;装饰者模式能够在运行时扩展一个对象的功能。另外也能够经过配置文件在运行时选择不一样的装饰器,从而实现不一样的行为。也能够经过不一样的组合,能够实现不一样效果。
  • 符合“开闭原则”:装饰者和被装饰者能够独立变化。用户能够根据须要增长新的装饰类,在使用时再对其进行组合,原有代码无须改变。

缺点

  • 装饰者模式须要建立一些具体装饰类,会增长系统的复杂度。

Objective-C & Java的实践

  • Objective-C中暂时未发现装饰者模式的实践,有知道的小伙伴能够留言
  • JDK中:BufferReader继承了Reader,在BufferReader的构造器中传入了Reader,实现了装饰

六. 享元模式

定义

享元模式(Flyweight Pattern):运用共享技术复用大量细粒度的对象,下降程序内存的占用,提升程序的性能。

定义解读:

  • 享元模式的目的就是使用共享技术来实现大量细粒度对象的复用,提升性能。
  • 享元对象能作到共享的关键是区份内部状态(Internal State)和外部状态(External State)。
    • 内部状态是存储在享元对象内部而且不会随环境改变而改变的状态,所以内部状态能够共享。
    • 外部状态是随环境改变而改变的、不能够共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被建立以后,在须要使用的时候再传入到享元对象内部。一个外部状态与另外一个外部状态之间是相互独立的。

适用场景

  • 系统有大量的类似对象,这些对象有一些外在状态。
  • 应当在屡次重复使用享元对象时才值得使用享元模式。使用享元模式须要维护一个存储享元对象的享元池,而这须要耗费资源,所以,

成员与类图

成员

享元模式一共有三个成员:

  • 享元工厂(FlyweightFactory): 享元工厂提供一个用于存储享元对象的享元池,用户须要对象时,首先从享元池中获取,若是享元池中不存在,则建立一个新的享元对象返回给用户,并在享元池中保存该新增对象
  • 抽象享元(Flyweight):抽象享元定义了具体享元对象须要实现的接口。
  • 具体享元(ConcreteFlyweight): 具体享元实现了抽象享元类定义的接口。

模式类图

享元模式类图

代码示例

场景概述

这里咱们使用《Objective-C 编程之道:iOS设计模式解析》里的第21章使用的例子:在一个页面展现数百个大小,位置不一样的花的图片,然而这些花的样式只有6种。

看一下截图:

百花图

场景分析

因为这里咱们须要建立不少对象,而这些对象有能够共享的内部状态(6种图片内容)以及不一样的外部状态(随机的,数百个位置坐标和图片大小),所以比较适合使用享元模式来作。

根据上面提到的享元模式的成员:

  • 咱们须要建立一个工厂类来根据花的类型来返回花对象(这个对象包括内部能够共享的图片以及外部状态位置和大小):每次当新生成一种花的类型的对象的时候就把它保存起来,由于下次若是还须要这个类型的花内部图片对象的时候就能够直接用了。
  • 抽象享元类就是Objective-C的原生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里面,UIImageFlowerImageView所继承的UIImageView的一个属性,因此在这里FlowerImageView就直接包含了UIImage

下面咱们来看一下客户端如何使用FlowerFactoryFlowerImageView这两个类:

//================== 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所持有的。(可是由于UIImageFlowerImageView的一个外部能够引用的属性,因此在这里客户端仍是能够访问到UIImage,这是Objective-C原生的实现。后面咱们在用享元模式的时候能够不将内部属性暴露出来)

优势

  • 使用享元模能够减小内存中对象的数量,使得相同对象或类似对象在内存中只保存一份,下降系统的使用内存,也能够提性能。
  • 享元模式的外部状态相对独立,并且不会影响其内部状态,从而使得享元对象能够在不一样的环境中被共享。

缺点

  • 使用享元模式须要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

  • 对象在缓冲池中的复用须要考虑线程问题。

Objective-C & Java的实践

  • iOS SDK中的UITableViewCell的复用池就是使用享元模式的一个例子。
  • Java:JDK中的Integer类的valueOf方法,若是传入的值的区间在[IntegerCache.low,IntegerCache.high]中的话,则直接从缓存里获取;不然就建立一个新的Integer

到这里设计模式中的结构型模式就介绍完了,读者能够结合UML类图和demo的代码来理解每一个设计模式的特色和相互之间的区别,但愿读者能够有所收获。

另外,本篇博客的代码和类图都保存在个人GitHub库中:object-oriented-design chapter 2.2

该系列前面的两篇文章:

本篇的下一篇是面向对象系列的第四篇,讲解的是面向对象设计模式中的行为型模式。

本篇已同步到我的博客:面向对象设计的设计模式(二):结构型模式(附 Demo 及 UML 类图)

参考书籍和教程


笔者在近期开通了我的公众号,主要分享编程,读书笔记,思考类的文章。

  • 编程类文章:包括笔者之前发布的精选技术文章,以及后续发布的技术文章(以原创为主),而且逐渐脱离 iOS 的内容,将侧重点会转移到提升编程能力的方向上。
  • 读书笔记类文章:分享编程类思考类心理类职场类书籍的读书笔记。
  • 思考类文章:分享笔者平时在技术上生活上的思考。

由于公众号天天发布的消息数有限制,因此到目前为止尚未将全部过去的精选文章都发布在公众号上,后续会逐步发布的。

并且由于各大博客平台的各类限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~

扫下方的公众号二维码并点击关注,期待与您的共同成长~

相关文章
相关标签/搜索