一、外观模式
1、外观模式简介
外观模式(Facade)在开发过程中的运用频率非常高,尤其是在现阶段各种第三方SDK充斥在我们的周边,而这些SDK很大概率会使用外观模式。通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节。当然,在我们的开发过程中,外观模式也是我们封装API的常用手段,例如网络模块、ImageLoader模块等。
2、概念
为子系统中的一组接口提供一个统一的接口。外观模式定义了一个更高层次的接口,这个接口使得这一子系统更加容易使用。
3、使用场景
在以下情况下可以考虑使用外观模式:
(1)设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式。
(2)开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口。
(3)维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。
4、角色
(1)子系统类:每个子系统定义了相关功能和模块的接口。
(2)Facade(外观类):整合子系统中的接口,客户端可以调用这个类的方法。
(3)Clients(客户端):通过外观类提供的接口和各个子系统的接口进行交互。
5、Demo
需求一:有一位乘客需要乘坐出租车,出租车司机为驾驶出租车的一组复杂接口提供了一个简化了的接口。如图:
通过此图可以看出整个出租车服务作为一个封闭系统,包括一名出租车司机(外观角色)、一辆车(子系统角色)、一个计价器(子系统角色)。同系统交互的唯一途径是通过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类的使用,如图:
这种需求就类似维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。
原有子系统类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图
涉及角色:
- 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
另外介绍: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