一、外观模式

1、外观模式简介

        外观模式(Facade)在开发过程中的运用频率非常高,尤其是在现阶段各种第三方SDK充斥在我们的周边,而这些SDK很大概率会使用外观模式。通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节。当然,在我们的开发过程中,外观模式也是我们封装API的常用手段,例如网络模块、ImageLoader模块等。

2、概念

       为子系统中的一组接口提供一个统一的接口。外观模式定义了一个更高层次的接口,这个接口使得这一子系统更加容易使用。

3、使用场景

在以下情况下可以考虑使用外观模式:

(1)设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式。
(2)开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口。
(3)维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。

4、角色

(1)子系统类:每个子系统定义了相关功能和模块的接口。
(2)Facade(外观类):整合子系统中的接口,客户端可以调用这个类的方法。
(3)Clients(客户端):通过外观类提供的接口和各个子系统的接口进行交互。

5、Demo

需求一:有一位乘客需要乘坐出租车,出租车司机为驾驶出租车的一组复杂接口提供了一个简化了的接口。如图:

ios 工程架构 ios的架构和设计模式_#import

        通过此图可以看出整个出租车服务作为一个封闭系统,包括一名出租车司机(外观角色)、一辆车(子系统角色)、一个计价器(子系统角色)。同系统交互的唯一途径是通过CabDriver中定义的接口driveToLocation:x。一旦乘客(客户client角色)向出租车司机发出driveToLocation:x消息。CabDriver就会收到消息。司机需要操作两个子系统---Taximeter(计价器)和Car。CabDriver先启动(start)Taximeter,让他开始计价,然后司机对汽车会松刹车(releaseBrakes)、换挡(changeGears)、踩油门(pressAccelerator),把车开走。直到到达了地点x,CabDriver会松油门(releaseAccelerator)、踩刹车(pressBrakes)、停止(stop)Taximeter,结束行程。

      一切都发生于发给CabDriver的一个简单的driveToLocation:x命令之中。无论两个子系统有多么复杂,它们隐藏于乘客的实现之外。因为CabDriver是在为出租车子系统中的其他复杂接口提供了一个简化的接口。CabDriver像“外观”一样,处于乘客与出租车子系统之间。

子系统类:

Car.h

#import <Foundation/Foundation.h>

@interface Car : NSObject

- (void)releaseBrakes; /**< 松刹车 */

- (void)pressBrakes; /**< 踩刹车 */

- (void)pressAccelerator; /**< 踩油门 */

- (void)releaseAccelerator; /**< 松油门 */

@end

Car.m

#import "Car.h"

@implementation Car

- (void)releaseBrakes {
    NSLog(@"松刹车");
}

- (void)pressBrakes {
    NSLog(@"踩刹车");
}

- (void)pressAccelerator {
    NSLog(@"踩油门");
}

- (void)releaseAccelerator {
    NSLog(@"松油门");
}
@end

Taximeter.h

#import <Foundation/Foundation.h>

@interface Taximeter : NSObject

- (void)start; /**< 开 */

- (void)stop; /**< 停止 */

@end

Taximeter.m

#import "Taximeter.h"

@implementation Taximeter
- (void)start {
    NSLog(@"开启");
}

- (void)stop {
    NSLog(@"停止 ");
}
@end

外观类:

CabDriver.h

#import <Foundation/Foundation.h>

@interface CabDriver : NSObject

// 到达指定的位置
+ (void)driveToLocation:(NSString *)location;


@end

CabDriver.m

#import "CabDriver.h"
#import "Car.h"
#import "Taximeter.h"

@implementation CabDriver

+ (void)driveToLocation:(NSString *)location {
    // 启动计价器
    Taximeter *taximeter = [[Taximeter alloc] init];
    [taximeter start];
    
    // 驾驶汽车
    Car *car = [[Car alloc] init];
    [car releaseBrakes];  // 松刹车
    [car pressAccelerator]; // 踩油门
    
    // 到达位置的操作
    [car releaseAccelerator];
    [car pressBrakes];
    [taximeter stop];
    
    NSLog(@"%@已经到达",location);
}

@end

客户端:

ViewController.m

#import "ViewController.h"
#import "CabDriver.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [CabDriver driveToLocation:@"帝都"];
}


@end

需求二:添加一个教练, 教学生学车. 同时它也具备Car类的使用,如图:

ios 工程架构 ios的架构和设计模式_#import_02

这种需求就类似维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。

原有子系统类Car和Taximeter类维持不变,外观类CarDriver去除+ (void)driveToLocation:(NSString *)location方法,并继承新的抽象类Drive。

Drive.h

@interface Drive : NSObject
// 到达指定的位置
+ (void)driveToLocation:(NSString *)location;

@end

Drive.m

#import "Drive.h"

@implementation Drive
+ (void)driveToLocation:(NSString *)location {

}
@end

加多一个子系统Teaching类,增加教开车的功能

Teaching.h

#import <Foundation/Foundation.h>

@interface Teaching : NSObject

- (void)start; /**< 开 */

- (void)stop; /**< 停止 */

- (void)reversing; /** 倒车入库 */

@end

Teaching.m

#import "Teaching.h"

@implementation Teaching
- (void)start {
     NSLog(@"开启");
}

- (void)stop {
    NSLog(@"停止");
}

- (void)reversing {
    NSLog(@"倒车入库");
}
@end

用增加的子系统类Teaching和原有的子系统类Car提供一组接口,增加新的外观类CoachDriver统一这一组接口,形成新的功能。

CoachDriver.h

#import "Drive.h"

@interface CoachDriver : Drive

//+ (void)teachDriving; /**< 教开车 */

@end

CoachDriver.m

#import "CoachDriver.h"
#import "Car.h"
#import "Teaching.h"

@implementation CoachDriver

+ (void)driveToLocation:(NSString *)location {
    [self teachDriving];
    NSLog(@"%@到达了", location);
}

+ (void)teachDriving {
    // 驾驶汽车
    Car *car = [[Car alloc] init];
    [car releaseBrakes];  // 松刹车
    [car pressAccelerator]; // 踩油门
    
    // 到达位置的操作
    [car releaseAccelerator];
    [car pressBrakes];

    // 教倒车
    Teaching *teach = [[Teaching alloc] init];
    [teach reversing];

}

@end

总结:

             外观模式是一个高频率使用的设计模式,它的精髓就在于封装二字。通过一个高层次结构为用户提供统一的API入口,使得用户通过一个类型就基本能够操作整个系统,这样减少了用户的使用成本,也能够提升系统的灵活性。

  优点:

     1.对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。

       2.实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。

      3.降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

       4.只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。

 

  缺点:

         1.外观类接口膨胀。由于子系统的接口都有外观类统一对外暴露,使得外观类的API接口较多,在一定程度上增加了用户使用成本。

         2.不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。

         3.在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

 

二、备忘录模式

1、概念

在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

2、何时使用备忘录模式

  • 当角色的状态改变时,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
  • 备忘录模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Orignator可以根据保存的Memento信息还原到前一状态。
  • 如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储撤销操作的状态。有的时候一些对象的内部信息必须要保存在对象以外的地方,但是必须要由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。
  • 用于获取状态的接口会暴露实现接口,需要将其屏蔽起来。
  • 它一般应用于游戏、文字处理程序的设计中,这种程序需要保存当前上下文的复杂状态的快照并在以后恢复处理。

3、UML图

ios 工程架构 ios的架构和设计模式_外观模式_03

涉及角色:

  • Originator(发起人):负责创建一个备忘录,用以记录当前时刻它的内部状态,并且可使用恢复备忘录内部状态。Originator可根据需要决定Memento存储Originator的哪些内部状态。
  • Memento(备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录。备忘录有两个接口,CareTaker只能看到备忘录的窄接口,它只能将备忘录传给其他对象。originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
  • Caretaker(管理者):负责保存好备忘录,不能对备忘录的内容进行操作或检查。

4、Demo

备忘录类:

Memo.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Memo : NSObject
{
    NSString *tempname;
    NSString *tempaddress;
}

- (Memo *)initWithName:(NSString *)name andWithAddress:(NSString *)address;
- (NSString *)getName;
- (NSString *)getAddress;

@end

NS_ASSUME_NONNULL_END

Memo.m

#import "Memo.h"

@implementation Memo

- (Memo *)initWithName:(NSString *)name andWithAddress:(NSString *)address
{
    tempname = name;
    tempaddress = address;
    return self;
}

- (NSString *)getAddress
{
    return tempaddress;
}

- (NSString *)getName
{
    return tempname;
}

@end

管理者类:

Caretaker.h

#import <UIKit/UIKit.h>
@class Memo;

NS_ASSUME_NONNULL_BEGIN

@interface Caretaker : UIViewController

@property (nonatomic, strong) Memo *memo;

@end

NS_ASSUME_NONNULL_END

Caretaker.m

#import "Caretaker.h"

@interface Caretaker ()

@end

@implementation Caretaker

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

@end

发起人类:

Student.h

#import <Foundation/Foundation.h>
@class Memo;

NS_ASSUME_NONNULL_BEGIN

@interface Student : NSObject

@property (nonatomic, strong) NSString *myName;
@property (nonatomic, strong) NSString *address;

- (Memo *)createMemo;
- (void)setMemo:(Memo *)memo;
- (void)display;

@end

Student.m

#import "Student.h"
#import "Memo.h"

@implementation Student

- (Memo *)createMemo{
    return [[Memo alloc] initWithName:_myName andWithAddress:_address];
}
- (void)setMemo:(Memo *)memo{
    _myName = [memo getName];
    _address = [memo getAddress];
}
- (void)display{
    NSLog(@"State is name:%@;address:%@",_myName,_address);
}

@end

客户端:

ViewController.m

#import "ViewController.h"
#import "Memo.h"
#import "Caretaker.h"
#import "Student.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    Student *stu1 = [[Student alloc] init];
    [stu1 setMyName:@"zhangsan"];
    [stu1 setAddress:@"he nan xin xiang"];
    [stu1 display];
    
    
    //use caretaker to save old name and address
    Caretaker *careTaker = [[Caretaker alloc] init];
    [careTaker setMemo:[stu1 createMemo]];
    
    //updated name and address
    [stu1 setMyName:@"zhangxiaoqiang"];
    [stu1 setAddress:@"bei jing san huan"];
    [stu1 display];
    
    
    //back to old name and address
    [stu1 setMemo:[careTaker memo]];
    [stu1 display];
}


@end

运行结果:

2019-02-22 16:26:48.056109+0800 beiwanglu[1871:152787] State is name:zhangsan;address:he nan xin xiang

2019-02-22 16:26:48.056367+0800 beiwanglu[1871:152787] State is name:zhangxiaoqiang;address:bei jing san huan

2019-02-22 16:26:48.056488+0800 beiwanglu[1871:152787] State is name:zhangsan;address:he nan xin xiang

可以看出把zhangsan原来的姓名和地址先保存到备忘录中,可是过了一段时间zhangsan不行用自己的新名字和地址了,所有就通过备忘录来还原自己的姓名和地址。

5、Cocoa Touch框架中的备忘录模式

        Cocoa Touch框架在归档、属性列表序列化和核心数据采用了备忘录模式。Cocoa的归档是对对象以及其属性还有同其他对象间的关系进行编码,形成一个文档,该文档既可保存与文件系统中,也可在进程或网络间传送。对象与其他对象的关系被看做对象图的网络。

        归档过程把对象保存为一种与架构无关的字节流,保持对象的标识以及对象之间的关系。对象的类型也同数据一起保存。从字节流解码出来的对象通常用于对象编码时相同的类进行实例化。使用NSCoder的具体类NSKeyedArchiver和NSKeyedUnarchiver,使用基于键的归档技术,被编码与解码的对象必须遵守NSCoding协议并实现以下方法:

-(id)initWithCoder:(NSCoder *)coder;
-(void)encodeWithCoder:(NSCoder *)coder;

6、DEMO2

ios 工程架构 ios的架构和设计模式_#import_04

另外介绍:FastCoding(第三方序列化对象工具)

  1.定义: 本地序列化工具 
  2.优点: 普通对象直接转换成NSData,直接存储,效率高于NSCoding,编码也比NSCoding好用
  3.缺点:项目ARC,Fastcoding(MRC)
  4.使用条件:MRC(需要用-fno-objc-arc)

写这个demo之前,必须引入第三库FastCoding,接下来先写备忘录中心

MementoCenter.h

#import <Foundation/Foundation.h>
#import "MementoProtocol.h"

@interface MementoCenter : NSObject
// 存对象的状态
+ (void)saveMementoObject:(id <MementoProtocol>)object withKey:(NSString *)key;

// 取出对象
+ (id)mementoObjectWithKey:(NSString *)key;

@end

MementoCenter.m

#import "MementoCenter.h"
#import "FastCoder.h"

@implementation MementoCenter

// 存对象的状态
+ (void)saveMementoObject:(id <MementoProtocol>)object withKey:(NSString *)key {
    
    id data = [object currentState];
    // 转化data为NSData
    NSData *tmpData = [FastCoder dataWithRootObject:data];
    
    if (tmpData) {
        [[NSUserDefaults standardUserDefaults] setObject:tmpData forKey:key];
    }
}

// 取出对象
+ (id)mementoObjectWithKey:(NSString *)key {
    id data = nil;
    
    NSData *tmpData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
    
    if (tmpData) {
        // 解码
       data = [FastCoder objectWithData:tmpData];
    }
    
    return data;
}

@end

不是什么东西都能存进备忘录的,备忘录中心协议,存储的对象必须满足这个协议,才能存储到备忘录中心

MementoProtocol.h

#import <Foundation/Foundation.h>

@protocol MementoProtocol <NSObject>

- (id)currentState; /**<  获取状态 */

- (void)recoverFromState:(id)state; /**<  恢复状态 */

@end

存进一个Apple类对象,存储的这个对象必须遵循协议MementoProtocol

Apple.h

#import <Foundation/Foundation.h>
#import "MementoProtocol.h"

@interface Apple : NSObject <MementoProtocol>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;

@end

Apple.m

#import "Apple.h"

@implementation Apple

- (id)currentState {
    
    return @{ @"name":self.name,
              @"age" : self.age
              };
}

- (void)recoverFromState:(id)state {
    NSDictionary *data = state;
    self.name = data[@"name"];
    self.age = data[@"age"];
}

@end

客户端:

ViewController.m

#import "ViewController.h"
#import "MementoCenter.h"
#import "Apple.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    Apple *apple = [[Apple alloc] init];
//    apple.name = @"富士苹果";
//    apple.age = @(10);
//    
//    // 存
//    [MementoCenter saveMementoObject:apple withKey:@"Apple"];
    
    // 取
    [apple recoverFromState:[MementoCenter mementoObjectWithKey:@"Apple"]];
    
    NSLog(@"name: %@  age:%@", apple.name, apple.age);
}


@end

7、对Demo2进行优化

生成一个NSObject分类,然后用协议MementoProtocol限制这个类

NSObject+MementoCenter.h

#import <Foundation/Foundation.h>

@interface NSObject (MementoCenter)

// 存储状态
- (void)saveStateWithKey:(NSString *)key;

// 恢复状态
- (void)recoverFromStateWithKey:(NSString *)key;

@end

NSObject+MementoCenter.m

#import "NSObject+MementoCenter.h"
#import "MementoCenter.h"

@implementation NSObject (MementoCenter)

- (void)saveStateWithKey:(NSString *)key {
    id <MementoProtocol> obj = (id <MementoProtocol>)self;
    
    if ([obj respondsToSelector:@selector(currentState)]) {
        [MementoCenter saveMementoObject:obj withKey:key];
    }
}

- (void)recoverFromStateWithKey:(NSString *)key {
    id state = [MementoCenter mementoObjectWithKey:key];
    
    id <MementoProtocol> obj = (id <MementoProtocol>)self;
    
    if ([obj respondsToSelector:@selector(recoverFromState:)]) {
        [obj recoverFromState:state];
    }
}

@end

客户端:

ViewController.m

#import "ViewController.h"
#import "MementoCenter.h"
#import "Apple.h"
#import "NSObject+MementoCenter.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    Apple *apple = [[Apple alloc] init];
    apple.name = @"富士苹果";
//    apple.age = @(10);
//
//    // 存
//    [MementoCenter saveMementoObject:apple withKey:@"Apple"];
//    
//    // 取
//    [apple recoverFromState:[MementoCenter mementoObjectWithKey:@"Apple"]];
    
    [apple saveStateWithKey:@"Apples"];
    [apple recoverFromStateWithKey:@"Apples"];
    
    NSLog(@"name: %@  age:%@", apple.name, apple.age);
}


@end