基本原则
- 开闭原则(Open Close Principle)
对扩展打开,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
- 里氏替换原则(Liskov Substitution Principle)
父子可以相互调用,父类必须提供足够多的抽象方法供子类重载使用。 任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 依赖倒转原则(Dependence Inversion Principle)
抽象不依赖细节,细节依赖抽象。这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
- 接口隔离原则(Interface Segregation Principle)
尽量做必要的事。使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
- 最少知道原则(Demeter Principle),迪米特法则
个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 合成/聚合复用
尽量使用合成/聚合的方式,而不是使用继承。
适配器模式
是什么?
适配器模式作为两个不兼容的接口之间的桥梁,它将一个类的接口转换成客户端希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式有时也称作为包装器 (Wapper)。适配器实现客户端所要的某种接口的行为。同时,它又连接到另一个具有(完全)不同接口与行为的对象。一边是客户端懂得如何使用的目标接口,另一边是客户端一无所知的被适配者,适配器处于两者之间。适配器的主要作用是把被适配者的行为传递给管道另一端的客户端。
怎么用?
- 当你想使用一个已经存在的类,而它的接口不符合你的需求;
- 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作;
- 你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器可以适配它的父亲接口。
优缺点?
- 优点:降低数据层和视图层(对象)的耦合度,使之使用更加广泛,适应复杂多变的变化。
- 缺点:降低了可读性,代码量增加,对于不理解这种模式的人来说比较难看懂。
类适配器
通过继承来适配两个接口,这称为类适配器。在 Objective-C
可以通过实现接口或协议,同时继承父类来实现类适配。
Target
指目标接口。Adaptee
指被适配者。request
意为请求行为。
Adapter
是一个 遵守了 <Target>
协议,同时也是一个 Adaptee
类型。Adapter
实现了 Target
的 request
方法。但是 Adapter
没有重载 Adaptee
的 specficRequest
方法,而是在 Adapter
的 request
方法中的实现中,调用父类 specficRequest
方法。request
方法在运行时向父类发送 [super specficRequest]
消息。super
就是 Adaptee
,它的 Adapter
的 request
方法的作用域内,按自己的方式执行 specficRequest
方法。
只有当
Target
是协议而不是类时,类适配器才能够用 Objective-C 来实现。协议的设计与适配器模式的描述并不完全匹配。但它实现了该模式的目标:允许具有其他不兼容接口的类一起工作。
代码实现:
假设现在有一个TypeC接口的设备
1
2
3
4
5
6
7
8
9@interface TypeCDevice: NSObject
- (void)connectWithTypeC;
@end
@implementation TypeCDevice
- (void)connectWithTypeC {
NSLog(@"TypeC connect");
}
@end现在需要将MicroUSB数据线通过
MicroUSBToTypeCAdapter
连接TypeC接口的设备1
2
3
4
5
6
7
8
9
10
11
12
13
14@protocol MicroUSBProtocol<NSObject>;
@required
- (void)connectWithMicroUSB;//声明适配方法
@end
@interface MicroUSBToTypeCAdapter : TypeCDevice<MicroUSBProtocol>;
@end
@implementation MicroUSBToTypeCAdapter
- (void)connectWithMicroUSB {
NSLog(@"MicroUSB convert To TypeC");
[super connectWithTypeC];
}
@end
对象适配器
对象适配器不继承被适配者,而是组合了一个对它的引用。实现为对象适配器时,它们之间的关系为:
Target
和Adapter
之间的关系与类适配器相同,而Adapter
与Adaptee
之间的关系从“属于”变成了“包含”。这种关系下,Adapter
需要保持一个对Adaptee
的引用。在request
方法中,Adapter
发送[adaptee specficRequest]
消息给引用adaptee
,以间接访问它的行为,然后实现客户端请求的其余部分。由于Adapter
与Adaptee
之间是一种“包含”关系,用Adapter
去适配Adaptee
的子类也没什么问题。
代码实现:
- 假设当前有一个
<MediaPlayer>
协议和一个遵守<MediaPlayer>
的类AudioPlayer
。默认情况下,AudioPlayer
可以播放mp3格式的音频文件。
1 | @protocol MediaPlayer <NSObject>; |
还有一个
<AdvancedMediaPlayer>
协议,和实现了<AdvancedMediaPlayer>
接口的实体类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23@protocol AdvancedMediaPlayer <NSObject>
@optional
- (void)playVlc:(NSString *)fileName;
- (void)playMp4:(NSString *)fileName;
@end
@interface VlcPlayer : NSObject<AdvancedMediaPlayer>
@end
@implementation VlcPlayer
- (void)playVlc:(NSString *)fileName{
NSLog(@"Playing vlc file. Name:%@",fileName);
}
@end
@interface Mp4Player : NSObject<AdvancedMediaPlayer>
@end
@implementation Mp4Player
- (void)playMp4:(NSString *)fileName {
NSLog(@"Playing mpf4 file. Name:%@",fileName);
}
@end为了让
AudioPlayer
支持播放其他格式的音频文件。需要创建一个实现了MediaPlayer
接口的适配器类MediaAdapter
,并使用 遵守了AdvancedMediaPlayer
协议的实例来播放所需的格式。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34@interface MediaAdapter : NSObject <MediaPlayer>
@property (nonatomic, week) id <AdvancedMediaPlayer> advancedMusicPlayer;
- (instancetype)initWithAudioType:(NSString *)audioType;
- (instancetype)init NS_UNAVAILABLE;
@end
@implementation MediaAdapter
- (instancetype)initWithAudioType:(NSString *)audioType {
self = [super init];
if (self) {
if ([audioType isEqualToString:@"vlc"]) {
self.advancedMusicPlayer = [VlcPlayer new];
} else if ([audioType isEqualToString:@"mp4"]){
self.advancedMusicPlayer = [Mp4Player new];
}
}
return self;
}
- (void)play:(NSString *)audioType FileName:(NSString *)fileName {
if ([audioType isEqualToString:@"vlc"]) {
if ([self.advancedMusicPlayer respondsToSelector:@selector(playVlc:)]) {
[self.advancedMusicPlayer playVlc:fileName];
}
} else if ([audioType isEqualToString:@"mp4"]) {
if ([self.advancedMusicPlayer respondsToSelector:@selector(playMp4:)]) {
[self.advancedMusicPlayer playMp4:fileName];
}
}
}
@end
策略模式
是什么?
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的对象而独立变化。
怎么用?
在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
比如:小孩和大人,小孩吃蔬菜,大人吃肉,小孩每天跑步一小时,大人每天跑步2小时。使用继承会导致代码复用性交叉。
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
优缺点?
- 优点:简化操作,提高代码维护性。算法可以自由切换,避免使用多重条件判断,扩展性良好。
- 缺点:在使用之前就要确定使用某种策略,而不是动态的选择策略。策略类会增多,所有策略类都需要对外暴露。
代码实现:
定义一个通用算法协议
1
2
3
4
5
6@protocol PlayerProtocol <NSObject>
@optional
- (NSString *)play;
- (NSString *)pause;
- (NSString *)stop;
@endAPlayer的算法封装
1 | @interface APlayer : NSObject<PlayerProtocol> |
BPlayer的算法封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28@interface BPlayer : NSObject<PlayerProtocol>
@property (nonatomic, week) id <PlayerProtocol> player;
@end
@implementation BPlayer
- (instancetype)init {
self = [super init];
if (self) {
player = [[BPlayer alloc] init];// 初始化AVPlayer播放器对象
}
return self;
}
// 播放
- (NSString *)play{
return [player b_play];
}
// 暂停
- (NSString *)pause{
return [player b_pause];
}
// 停止
- (NSString *)stop{
return [player b_stop];
}
@end通用播放器类CPlayer的定义。根据不同的策略选择不同的算法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55// 播放器的类型
typedef enum : NSUInteger {
PlayerType_APlayer,
PlayerType_BPlayer
} PlayerType;
@interface CPlayer : NSObject
- (instancetype)initWithType:(layerType)type;
@property (nonatomic, week) id <PlayerProtocol> player;
- (NSString *)play;
- (NSString *)pause;
- (NSString *)stop;
@end
@implementation Clayer
- (instancetype)initWithType:(PlayerType)type {
self = [super init];
if (self) {
[self initPlayerWithType:type];
}
return self;
}
// 初始化播放器
- (void)initPlayerWithType:(PlayerType)type {
switch (type) {
case PlayerType_APlayer: {
player = [[AVPlayer alloc] init];
break;
}
case PlayerType_BPlayer: {
player = [[BPlayer alloc] init];
break;
}
}
}
//开启视频
- (NSString *)play {
return [player play];
}
//暂停视频
- (NSString *)pause {
return [player pause];
}
//停止播放
- (NSString *)stop {
return [player stop];
}
@end
观察者模式
是什么?
本质上是一种发布-订阅模型,当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。如
NSNotificationCenter
、KVO
。怎么用?
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
- 优缺点?
- 优点:观察者和被观察者是抽象耦合的。
- 缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
原型模式
是什么?
用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
怎么用?
- 需要创建的对象不依赖于具体的类型以及创建方式
- 具体实例化的对象类型是在运行期决定的
- 不同类型之间的差异仅仅是状态的组合
- 类型创建复杂,例如类型有复杂的嵌套
- 很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
- 优缺点
- 优点:性能提高
- 缺点:配备克隆方法需要对类的功能进行通盘考虑。逃避构造函数的约束。
代码实现:
1 | // Person 需要遵循并实现NSCopying |
外观模式
是什么?
外观模式为子系统中一组不同的接口提供统一的接口。
外观定义了上层接口,通过降低复杂度和隐藏子系统间的通信及依存关系,让子系统更易于使用。
外观模式是面向对象编程里使用得很频繁的一个模式。最经常见到的例子就是各种库里的Manager类。比如SDWebImage
。
怎么用?
- 外部不需要知道系统内部的复杂联系,整个系统只需提供一个接口即可。
- 每个子系统层级有一个外观作为入口,让它们通过其外观进行通信,可以简化它们之间的依赖关系。
- 外部不需要知道系统内部的复杂联系,整个系统只需提供一个接口即可。
优缺点
- 优点:减少系统相互依赖、提高灵活性、提高了安全性
- 缺点:不符合开闭原则,由于没有很多的接口暴露,如果要改东西很麻烦,继承重写都不合适。
装饰模式
是什么?
是指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。是继承的替代方案,属于结构型模式,它是作为现有的类的一个包装,比继承子类方式更灵活。
怎么用?
- 抽象构件(
Component
):定义一个抽象接口以规范准备接收附加责任的对象。 - 具体构件(
ConcreteComponent
):继承抽象构件,通过装饰角色为其添加一些职责。 - 抽象装饰(
Decorator
):持有抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。 - 具体装饰(
ConcreteDecorator
):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
- 抽象构件(
优缺点
- 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
- 缺点:多层继承装饰比较复杂。
代码实现:
先定义抽象构件类Hero,通过方法blessBuff展示英雄此时所加持的buff
1
2
3
4
5
6
7
8
9@interface Hero : NSObject
- (void)blessBuff;
@end
@implementation Hero
- (void)blessBuff {
NSAssert(false, @"must implement in subClass");
}
@end定义具体构件英雄盖伦Galen和Timo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// Galen.h
@interface Galen : Hero
@end
@implementation Galen
- (void)blessBuff {
NSLog(@"盖伦被动技能:脱离战斗后回血加快");
}
@end
@interface Timo : Hero
@end
@implementation Timo
- (void)blessBuff {
NSLog(@"提莫被动技能:脱离战斗后,静止不动一段时间进入隐身");
}
@end定义抽象装饰器BuffDecorator
1 | // 集成抽象构件 |
定义具体装饰器红buff
1
2
3
4
5
6
7
8
9
10// 集成抽象装饰器
@interface RedBuffDecorator : BuffDecorator
@end
@implementation RedBuffDecorator
- (void)blessBuff {
[super blessBuff];
NSLog(@"红buff: 攻击附加真实伤害,并造成灼烧效果");
}
@end定义具体装饰器蓝buff
1
2
3
4
5
6
7
8
9
10// 集成抽象装饰器
@interface BlueBuffDecorator : BuffDecorator
@end
@implementation BlueBuffDecorator
- (void)blessBuff {
[super blessBuff];
NSLog(@"蓝buff: 蓝量回复速度加快,并且缩减技能CD");
}
@end使用
1
2
3
4
5
6
7
8
9
10
11Hero *galen = [Galen new];
galen = [[RedBuffDecorator alloc] initWithHero:galen];
[galen blessBuff];
galen = [[BlueBuffDecorator alloc] initWithHero:galen];
[galen blessBuff];
NSLog(@"----------------Timo----------------------");
Hero *timo = [Timo new];
timo = [[RedBuffDecorator alloc] initWithHero:timo];
[timo blessBuff];
timo = [[BlueBuffDecorator alloc] initWithHero:timo];
[timo blessBuff];
工厂模式
是什么?
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。怎么用?
- 明确地计划不同条件下创建不同实例时。
- 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
优缺点
- 优点:扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。屏蔽对象的具体实现,调用者只关心产品的接口。
- 缺点:在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
简单工厂模式
- 当系统中只有唯一的产品时,可以省略抽象产品,工厂角色与具体产品可以合并。
- 工厂类只有一个,它集中了所有产品创建的逻辑,它将是整个系统的瓶颈,同时造成系统难以拓展。
- 简单工厂模式通常使用静态工厂方法,这使得工厂类无法由子类继承,这使得工厂角色无法形成基于继承的等级结构。
代码实现:
定义抽象产品类
1
2
3
4
5
6
7
8@interface Food : NSObject
@property (nonatomic, copy)NSString *product;
- (void)productBreakfast;
@end
@implementation Food
- (void)productBreakfast{
}定义具体产品类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@interface Milk : Food
@end
@implementation Milk
- (void)productBreakfast{
_product = @"牛奶";
}
@interface Bread : Food
@end
@implementation Bread
- (void)productBreakfast{
_product = @"面包";
}定义工厂类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27typedef NS_ENUM(NSInteger, FactoryProductType){
FactoryProductTypeMilk,
FactoryProductTypeBread,
};
@interface Factory : NSObject
//工厂(Factory)角色:接受客户端的请求,通过请求负责创建相应的产品对象。
+ (Food *)operationBreakfast:(FactoryProductType )breakfastType;
@end
@implementation SFFactory
+ (Food *)operationBreakfast:(FactoryProductType)breakfastType {
//通过枚举返回不同的产品
Food *food;
switch (breakfastType) {
case FactoryProductTypeMilk:
food = [[Milk alloc] init];
break;
case FactoryProductTypeBread:
food = [[Bread alloc] init];
break;
default:
return nil;
break;
}
return food;
}
@end
标准工厂模式
在简单工厂模式的基础上,增加抽象工厂。
共有如下四个角色:
- 抽象工厂角色:与应用程序无关,任何在模式中创建对象的工厂必须实现这个接口。
- 具体工厂角色:实现了抽象工厂接口的具体类,含有与引用密切相关的逻辑,并且受到应用程序的调用以创建产品对象。
- 抽象产品角色:工厂方法所创建产品对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
- 具体产品角色:这个角色实现了抽象产品角色所声名的接口。工厂方法所创建的每个具体产品对象都是某个具体产品角色的实例。
抽象工厂模式
- 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
参与角色与标准工厂模式一样,不过抽象工厂不仅仅作为接口存在,而可以产生其他工厂。
代码实现:
定义抽象产品、具体产品和简单工厂一致
定义抽象工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27@interface Factory : NSObject
typedef NS_ENUM(NSInteger, FactoryProductType) {
FactoryProductTypeMilk,
FactoryProductTypeBread,
};
+ (instancetype)factoryWithType:(FactoryProductType)type;
- (Food *)createProduct;
@implementation AFFactory
+ (instancetype)factoryWithType:(FactoryProductType)type {
Factory *factory;
switch (type) {
case FactoryProductTypeMilk:
factory = [[FactoryMilk alloc] init];
break;
case FactoryProductTypeBread:
factory = [[FactoryBread alloc] init];
break;
default:
break;
}
return factory;
}
- (Food *)createProduct {
return nil;
}定义具体工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@interface FactoryMilk : Factory
@end
@implementation FactoryMilk
- (Food *)createProduct {
return [[Milk alloc] init];
}
@end
@interface FactoryBread : Factory
@end
@implementation FactoryBread
- (Food *)createProduct {
return [[Bread alloc] init];
}
@end使用
1
2
3Factory *factory = [Factory factoryWithType:FactoryProductTypeMilk];
Food *milk = [factory createProduct];
[milk productBreakfast];
总结
从简单工厂模式到工厂模式,再到抽象工厂模式。可以看到整个模式的一步步演进。
简单工厂模式在产品多样之后,整个工厂将会变得臃肿而难以维护。于是将简单工厂模式中的工程做了抽象处理,这样每种产品对应一个工厂。这样无疑会增加代码量。但是好处是显而易见的,单独让一个工厂处理一种产品会让逻辑变得好维护。
但是这样还不够,因为增加新的品类,就会产生新的类,对于调用者来说,处理太多具有相同接口的类显然是不合算的。于是,我们使用抽象工厂模式来解决这个问题。我们让抽象工厂内部做一个封装,用以隐藏真正的具体工厂。这样,对于调用者来说,即时内部增加了新的产品,外部也是不知道的。
桥接模式
是什么?
从针对不同的实现中,分离出其可被复用的抽象称为桥接模式。这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。比如遥控器和电视机。
Abstraction
是定义了供客户端使用的上层抽象接口的父接口。它有一个对Implementor
实例的引用,Implementor
定义了实现类的接口。这个接口不必跟Abstraction
的接口一致。Implementor
的接口提供基本操作,而Abstraction
的上层操作基于这些基本操作。当客户端向Abstraction
的实例发送operation
消息时,这个方法向imp
发送operationImp
消息。实际的ConcreteImplementator
将作出响应并接收任务。怎么用?
- 不想在抽象与其实现之间形成固定的绑定关系
- 抽象及其实现都应可以通过子类独立化进行扩展
- 对抽象的实现进行修改不应影响客户端代码
- 如果每个实现需要额外的子类以细化抽象,则说明有必要把它们分成两个部分
- 想在带有不同抽象接口的多个对象之前共享一个实现。
优缺点?
- 优点:抽象和实现的分离、优秀的扩展能力、实现细节对客户透明。
- 缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
代码实现:
- 定义实现类
1 | typedef NS_ENUM(NSUInteger, EcommandType) { |
定义抽象类,并建立桥接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30@interface AbstractSystem : NSObject
// 建立桥接
@property (nonatomic, strong) AbstractImplementor *implementor;
//上下左右 把调用实现类方法的桥接行为包装起来
- (void)command_up;
- (void)command_down;
- (void)command_left;
- (void)command_right;
@end
@implementation AbstractSystem
- (void)command_up {
[self.implementor loadCommand:kUp];
}
- (void)command_down {
[self.implementor loadCommand:kDown];
}
- (void)command_left {
[self.implementor loadCommand:kLeft];
}
- (void)command_right {
[self.implementor loadCommand:kRight];
}
- (void)loadSystem {
}创建实现类的子类PSPImplementor
1 | @interface PSPImplementor : AbstractImplementor |
创建抽象类的子类PSPSystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@interface PSPSystem : AbstractSystem
- (void)command_up;
- (void)command_down;
@end
@implementation PSPSystem
- (void)command_up {
[self.implementor loadCommand:kUp];
}
- (void)command_down {
[self.implementor loadCommand:kDown];
}
@end使用
1 | PSPSystem *pspSystem = [[PSPSystem alloc] init]; |
代理模式
是什么?
为其他对象提供一种代理可以控制对这个对象的访问。
怎么用?
通俗讲一个例子,同学A给同学B送东西,但是同学A临时有事去不了,这时同学A需要找了另外同学C帮他去送东西,我们就说C是A的代理,送东西这件事就是他们之间的协议。
- 协议:用来指定代理双方要做什么事情(送东西)
- 代理:根据指定的协议,完成协议规定的事情(同学C)
- 委托:根据指定的协议,指定代理去完成的事情(同学A)
优缺点?
- 优点:职责清晰、高扩展性。
- 缺点:实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
在iOS中对象是不支持多继承的,而协议可以多继承。1
2
3
4// 当前协议继承了三个协议,这样其他三个协议中的方法列表都会被继承过来
@protocol LoginProtocol <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
- (void)userLoginWithUsername:(NSString *)username password:(NSString *)password;
@end
单例模式
是什么?
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。如
UIApplication
、NSNotificationCenter
、NSFileManager
。怎么用?
需要控制实例对象数目,节省系统资源。
优缺点?
- 优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。避免了对资源的多重占用比如写文件操作。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
代码实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 初始化
static Myclass _instance;
static dispatch_once_t onceToken;
+ (id)shareInstance {
dispatch_once(&onceToken, ^{
if(_instance == nil)
_instance = [MyClass alloc] init];
});
return _instance;
}
// 销毁
+ (void)dealloc {
onceToken = 0;
_instance = nil;
}
备忘录模式
是什么?
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
备忘录模式总共需要三种对象- 保存对象(即备忘录)
- 使用对象
- 管理备忘录对象
怎么用?
- 功能比较复杂的,但是需要维护或记录属性历史的类。
- 需要保存的属性只是众多属性的一小部分时。
一般而言,运用备忘录的思想很多,完整的运用备忘录模式的很少。
优缺点?
- 优点:提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。实现了信息的封装,使得用户不需要关心状态的保存细节。
- 缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
代码实现:
定义备忘录
1
2
3@interface Memo : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) NSInteger height;定义需要备份的抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@class Memo;
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) NSInteger height;
- (Memo *)saveState;
- (void)recoverStateFromMemo:(Memo *)memo;
@implementation User
- (Memo *)saveState {
Memo *m = [Memo new];
m.age = _age;
m.height = _height;
return m;
}
- (void)recoverStateFromMemo:(Memo *)memo {
_age = memo.age;
_height = memo.height;
}
@end使用
1
2
3
4
5
6
7
8
9
10
11
12User *user = [User new];
user.name = @"Bob";
user.age = 18;
user.height = 168;
NSMutableArray *memoArray = [NSMutableArray array];
for (int i=0; i<10; i++) {
[memoArray addObject:[user saveState]];
user.age += 1;
user.height += 0.8;
}
/// 恢复到23岁时候的状态
[user recoverStateFromMemo:memoArray[5]];
生成器模式/建造者模式
是什么?
将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现。
其整体思想是分离“什么”与“如何”。将复杂对象的构建过程分解为客户-指导者-生成器(client-director-builder)
的关系。这样将更容易管理和复用整个过程。Builder
抽象建造者类- 为创建一个
Product
对象的各个部件指定抽象接口。
- 为创建一个
ConcreteBuilder
建造者类- 实现
Builder
的接口以构造和装配该产品的各个部件。 - 定义并明确它所创建的表示。
- 提供一个检索产品的接口
- 实现
Director
导向器类- 构造一个使用
Builder
接口的对象。
- 构造一个使用
Product
产品类- 表示被构造的复杂对象。
ConcreateBuilder
创建该产品的内部表示并定义它的装配过程。 - 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
- 表示被构造的复杂对象。
怎么用?
- 需要创建设计各种部件的复杂对象。创建对象的算法应该独立于部件的装配方式。常见于构建组合对象。
- 构建过程需要以不同的方式(例如,部件或表现的不同组合)构建对象。
优缺点?
- 优点:扩展性、封装性强
- 缺点:产品必须有共同点,范围有限制。如内部变化复杂,会有很多的建造类。
代码实现:
定义
Product
1
2
3
4
5
6
7@interface Car : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *price;
@end
@implementation Car
@end定义
Builder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@interface CarBuilder : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *price;
-(Car *)makeCar;
@end
@implementation CarBuilder
- (Car *)makeCar {
Car *car = [[Car alloc] init];
car.name = self.name;
car.price = self.price;
return car;
}
@end定义两个
ConcreteBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13@interface BMWCar : BuilderCar
@end
@implementation BMWCar
- (instancetype)init {
self = [super init];
if (self) {
self.name = @"BMW M5";
self.price = @"80万";
}
return self;
}
@end
1 | @interface AudiCar : BuilderCar |
- 定义
Director
1 | @interface Director : NSObject |
使用
1
2BuilderCar *car = [[BMWCar alloc] init];
Director *dir = [Director creatBuickCar:car];与工厂模式的区别
- 建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个”导演类”的角色。
- 在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。
- 与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。
- 工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;
- 建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。
命令模式
是什么?
命令模式主要把请求对象封装成一个命令进行传递,由命令者和接受者组成。
怎么用?
在某些场合,比如要对行为进行”记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将”行为请求者”与”行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
优缺点?
- 优点:降低了系统耦合度,新的命令可以很容易添加到系统中去。
- 缺点:可能会导致某些系统有过多的具体命令类。
代码实现: 放大缩小红色视图
定义
Receiver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15@interface Receiver : NSObject
@property (assign, nonatomic) CGFloat width;
@property (strong, nonatomic) UIView * redView;
- (void)amplifyView:(CGFloat)pamameter;
- (void)reduceView:(CGFloat)pamameter;
@end
@implementation Receiver
- (void)amplifyView:(CGFloat)pamameter {
}
-(void)reduceView:(CGFloat)pamameter{
}
@end定义抽象命令类
Command
1
2
3
4
5
6@protocol CommandProtocol <NSObject>
/// 执行命令
- (void)excute;
/// 撤销命令
- (void)backExcute;
@end定义命令类
ConcreteCommand
1 | @interface AmplifyCommand : NSObject<CommandProtocol> |
定义调用者
Invoker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42@interface Invoker : NSObject
+ (instancetype)sharedInstance;
// 回退指令
- (void)rollBack;
// 添加操作指令
- (void)addAndExcute:(id <CommandProtocol>)command;
@end
@interface Invoker()
@property (nonatomic, strong) NSMutableArray *mArr; //存储操作指令的数组
@end
@implementation Invoker
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static Invoker *cls = nil;
dispatch_once(&onceToken, ^{
cls = [[[self class] alloc] init];
cls.mArr = [[NSMutableArray alloc]init];
});
return cls;
}
- (void)rollBack {
// 1.获取数组中的最后一个操作
id <CommandProtocol> command = self.mArr.lastObject;
// 2.操作调用,撤销的步骤
[command backExcute];
// 3.删除最后操作
[self.mArr removeLastObject];
}
// 添加操作指令
- (void)addAndExcute:(id <CommandProtocol>)command {
// 1.把操作添加到数组
[self.mArr addObject:command];
// 2.让操作调用实现的协议方法
[command excute];
}
@end使用
1
2
3
4
5
6
7
8/// 初始化接收者
Receiver *receiver = [[Receiver alloc]init];
receiver.redView = [UIView new];
receiver.width = 50;
/// 初始化命令并绑定接收者
AmplifyCommand *command = [[AmplifyCommand alloc]initWithReceiver:receiver paramter:10];
/// 执行命令
[[Invoker sharedInstance] addAndExcute:command];
组合模式/部分整体模式
是什么?
组合模式描述了一组对象,它们的处理方式与相同类型对象的单个实例相同。组合的目的是将对象“组合”成树结构以表示部分-整体层次结构,实现让客户统一对待单个对象和组合。
为部分
Leaf
对象和整体Composite
对象定义统一的Component
接口。
单个Leaf
对象直接实现Component
接口,Composite
对象将请求转发给它们的子组件
这使客户端能够通过Component
接口统一处理Leaf
和Composite
对象,Leaf
对象直接执行请求,Composite
对象将请求向下递归转发到它们的子组件。这使得客户端类更易于实现、更改、测试和重用。- 怎么用?
- 应该表示部分-整体层次结构,以便客户端可以统一处理部分和整体对象
- 部分-整体层次结构应表示为树结构。
- 用在部分、整体场景,如树形菜单,文件、文件夹的管理。
- 优缺点:
- 优点:高层模块调用简单、节点自由增加。
- 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
代码实现:
定义
Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 也可以用代理实现
@interface Component : NSObject {
NSString *name;
}
- (Component *)MyInit:(NSString *)myName;
- (void)add:(Component *)c;
- (void)remove:(Component *)c;
-(void)display:(int)depth;
@end
@implementation Component
- (Component *)MyInit:(NSString *)myName {
name = myName;
return self;
}
- (void)add:(Component *)c {
}
- (void)remove:(Component *)c {
}
- (void)display:(int)depth {
}
@end定义
Leaf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19@interface Leaf : Components
- (Leaf *)MyInit:(NSString *)myName;
@end
@implementation Leaf
- (Leaf *)MyInit:(NSString *)myName {
name = myName;
return self;
}
- (void)add:(Components *)c {
NSLog(@"Can not add a leaf");
}
- (void)remove:(Components *)c {
NSLog(@"Can not remove from a leaf");
}
- (void)display:(int)depth {
NSLog(@"[%dLevel]%@",depth,name);
}
@end定义
Composite
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@interface Composite : Components {
NSMutableArray *childrenArr;
}
- (Composite *)MyInit:(NSString *)myName;
@end
@implementation Composite
- (Composite *)MyInit:(NSString *)myName {
name = myName;
childrenArr = [NSMutableArray new];
return self;
}
- (void)add:(Components *)c {
[childrenArr addObject:c];
}
- (void)remove:(Components *)c {
[childrenArr removeObject:c];
}
- (void)display:(int)depth {
NSLog(@"[%dLevel]%@",depth,name);
for(ComComponents *component in childrenArr) {
[component Display:depth+1];
}
}
@end使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Composite *root = [[Composite alloc] MyInit:@"root"];
[root Add:[[Leaf alloc] MyInit:@"Leaf A"]];
[root Add:[[Leaf alloc] MyInit:@"Leaf B"]];
Composite *comp = [[Composite alloc] MyInit:@"Composite X"];
[comp Add:[[Leaf alloc]MyInit:@"Leaf XA"]];
[comp Add:[[Leaf alloc]MyInit:@"Leaf XB"]];
[root Add:comp]; // 添加到根节点
Composite *comp2 = [[Composite alloc] MyInit:@"Composite XY"];
[comp2 Add:[[Leaf alloc] MyInit:@"Leaf XYA"]];
[comp2 Add:[[Leaf alloc] MyInit:@"Leaf XYB"]];
[comp Add:comp2];
[root Add:[[Leaf alloc] MyInit:@"Leaf C"]];