目录

一、概述

二、框架模式,框架,设计模式

1、框架模式

(1)MVC

(2)MVVM

(3)MVP

2、设计模式

(1)原型模式(Prototype pattern)

(2)简单工厂模式、工厂模式、抽象工厂模式(Factory Method pattern)

(3)建造者模式(Builder Pattern)

(4)单例模式(Singleton Pattern)

(5)适配器模式(Adapter Pattern)

(6)桥接模式(Bridge Pattern)

(7)外观模式(Facade pattern)

(8)中介者模式(Mediator Pattern)

(9)观察者模式(Observer Pattern)

(10)组合模式(Composite Pattern)

(11)访问者模式(Visitor Pattern)

(12)装饰模式(Decorator pattern)

(13)责任链模式(Chain-of-responsibility pattern)

(14)模版方法模式(Template method pattern)

(15)策略模式(Strategy pattern)

(16)命令模式(Command Pattern)

(17)享元模式(Flyweight Pattern)

(18)代理模式(Proxy pattern)

(19)备忘录模式(Memento Pattern)

总结



一、概述

无论是初出茅庐还是代码老手,平时工作或面试的时候,经常会提到“设计模式”这个词,还记得刚接触iOS还不算入门的时候,第一次给领导汇报工作进度,就把MVC设计模式写在报告里,瞬间感觉自己写的东西好像专业一些了!

后来接触项目越来越多,MVC更是耳熟能详,但随着学习积累,突然发现以前学东西不够系统和全面。那么问题第一个问题来了,MVC是不是设计模式?面试中也经常会问,例如:你经常使用的设计模式有哪些,MVC……无论是别人问我,也或是我问别人,这个答案都几乎没什么毛病。但既然这篇内容打算细聊设计模式,那答案可能不是那么简单的!

平时开发中,也会接触很多设计模式,知道名字的或者不知道的,其实都在用。那么常用的设计模式有哪些?代理模式、单例模式……那么到底有多少种设计模式?下文会详细的聊聊这个话题。


二、框架模式,框架,设计模式

首先搞清楚“框架模式”,“框架”,“设计模式”这三个概念:

框架通常是代码重用,而设计模式是设计重用,架构则介于两者之间,部分代码重用,部分设计重用,有时分析也可重用。有很多程序员往往把框架模式和设计模式混淆,认为MVC是一种设计模式。实际上它们完全是不同的概念。

在软件生产中有三种级别的重用:内部重用,即在同一应用中能公共使用的抽象块;代码重用,即将通用模块组合成库或工具集,以便在多个应用和领域都能使用;应用框架的重用,即为专用领域提供通用的或现成的基础结构,以获得最高级别的重用性。
框架与设计模式虽然相似,但却有着根本的不同。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述,它比框架更抽象;框架可以用代码表示,也能直接执行或复用,而对模式而言只有实例才能用代码表示;设计模式是比框架更小的元素,一个框架中往往含有一个或多个设计模式,框架总是针对某一特定应用领域,但同一模式却可适用于各种应用。可以说,框架是软件,而设计模式是软件的知识。

框架模式有哪些?
MVC、MTV、MVP、CBD、ORM、MVVM等等;
框架有哪些?
C++语言的QT、MFC、gtk,Java语言的SSH 、SSI,php语言的 smarty(MVC模式),python语言的django(MTV模式)等等
设计模式有哪些?
工厂模式、适配器模式、策略模式等等
简而言之:框架是大智慧,用来对软件设计进行分工;设计模式是小技巧,对具体问题提出解决方案,以提高代码复用率,降低耦合度。

如果打个比喻,我会用对象编程思维里的几个产物来比喻设计模式、架构和框架:
一、设计模式就像是接口
抽象得无法再抽象了,基本上设计得有水平的接口,看上去简单,但其实包含了设计者的关系归纳取舍。
二、架构就像是抽象类
因为架构带有不完整的实现、轻量的公共的实现,留有大量容器去给指定的程序员开发
三、框架就像是类
因为框架必须提供完整的功能配套,偶尔留下的一些关系变化,也不是主角,它就像已经可以用的摇控器

那么概述里的第一个问题,MVC是不是设计模式?你还会那么简单的回答吗!!!MVC本质上是一种架构,数据,渲染,控制分离,因为这种架构很优雅,所以设计优雅架构时可以以此作为规则,因此MVC看做设计模式的一种也不算错。相信肯定还有不同的理解和看法,欢迎回复区一起讨论和学习。下面结合实际代码一个个的去了解。

1、框架模式

重点了解MVC、MVVM、MVP

(1)MVC

MVC是model-view-control的简称。

View——顾名思义,就是存放视图使用的。

Model——即模型。模型一般都有很好的可复用性,统一管理一些数据。在上面的例子中,数据库是不是可以作为一个模型呢?答案是肯定的。所以,我们就把数据库的所有操作都放在Model里面执行——包括但不限于数据库的创建、插入、查询、更新和删除

Controller——控制器,充当一个CPU的功能,即该应用程序所有的工作都由Controller统一调控。它负责处理View和Model的事件。具体怎么调控和处理?在下面的MVC原理里面,我们将详细讲解。

MVC模式能够完成各司其职的任务模式,由于降低了各个环节的耦合性,大大优化Controller的代码量,当程序调试时,如果某一个功能没有按照既定的模式工作,可以很方便的定位到到底是Controller还是View还是Model出了问题,而且还利于程序的可复用性。

MVC模式虽然是iOS编程中使用最广泛的模式,但论起复杂程度,MVC模式可以算是众多设计模式之首。通常情况下,MVC模式需要综合使用target-action模式、delegate模式、Notification或KVO模式等。下图是斯坦福大学的iOS一堂关于iOS介绍的公开课上所使用的示例图,这张图像也生动的描绘出来了MVC模式的工作原理,接下来的原理讲解也是依托于这张图像:

1、 Controller和View之间可以通信,Controllor通过outlet(输出口)控制View,View可以通过target-action、delegate或者data source(想想UITableVeiwDatasource)来和Controller通信;

2、 Controller在接收到View传过来的交互事件(View就是完成让人和程序的交互的呀,比如按B1按钮)之后,经过一些判断和处理,把需要Model处理的事件递交给Model处理(比如刚才的例子中的保存到数据库),Controller对Model使用的是API;

3、 Model在处理完数据之后,如果有需要,会通过Notification或者KVO的方式告知Controller,事件已经处理完,Controller再经过判断和处理之后,考虑下一步要怎么办(是默默无闻的在后台操作,还是需要更新View,这得看Controller的处理)。这里的无线天线很有意思,Model只负责发送通知,具体谁接收这个通知并处理它,Model并不关心,这一点非常重要,是理解Notification模式的关键。

4、 Model和View之间不直接通信!

按照上面的原理,我们知道了M、V、C之间的各司其职——Model不保存控件,View不做数据库操作(但这个也不是绝对,如果需要View做一些数据缓存工作,还是需要保存一些临时数据的),而Controller就充当了两者之间的协调器。

如此,MVC的原理已经理出来一个头绪了,那么我们来看一个实际的例子,来验证如何使用MVC模式。在这个例子中,View通过target-action模式向Controller传递消息,Controller通过API调用Model里面的方法来处理从View那接收到的消息;Model处理完数据之后,通过Notification模式向Controller传递一个消息,最终Controller通过一个方法(即Notification的接收方法)弹出来一个对话框显示Model已经处理完成。

MVC小总结:

M应该做的事:
1.给ViewController提供数据
2.给ViewController存储数据提供接口
3.提供经过抽象的业务基本组件,供Controller调度C应该做的事:
1.管理View Container的生命周期
2.负责生成所有的View实例,并放入View Container
3.监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。V应该做的事:
1.响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
2.界面元素表达

(2)MVVM

上边聊完了MVC,但在实际开发中,M和V的职责很清晰了,那剩下来的任务都交给C来做了,可能包括V的生命周期代理方法,网络请求,处理各种代理,数据解析和封装等等等,随着项目增大,C会变得很臃肿,不利于维护管理以及BUG定位,于是就出现了改进版本的MVVM。

其中MVVM的M和V同MVC的M和V,但又有区别,那VM又是什么?看下边原理图:

MVVM把View和Contrller都放在了View层(相当于把Controller一部分逻辑抽离了出来),Model层依然是服务端返回的数据模型。而VM,也就是ViewModel充当了一个UI适配器的角色,也就是说View中每个UI元素都应该在ViewModel找到与之对应的属性。除此之外,从Controller抽离出来的与UI有关的逻辑都放在了ViewModel中,这样就减轻了Controller的负担。

  • View层:视图展示。包含UIView以及UIViewController,View层是可以持有ViewModel的。
  • ViewModel层:视图适配器。暴露属性与View元素显示内容或者元素状态一一对应。一般情况下ViewModel暴露的属性建议是readOnly的,至于为什么,我们在实战中会去解释。还有一点,ViewModel层是可以持有Model的。
  • Model层:数据模型与持久化抽象模型。数据模型很好理解,就是从服务器拉回来的JSON数据。而持久化抽象模型暂时放在Model层,是因为MVVM诞生之初就没有对这块进行很细致的描述。按照经验,我们通常把数据库、文件操作封装成Model,并对外提供操作接口。(有些公司把数据存取操作单拎出来一层,称之为DataAdapter层,所以在业内会有很多MVVM的变种,但其本质上都是MVVM)。
  • Binder:MVVM的灵魂。可惜在MVVM这几个英文单词中并没有它的一席之地,它的最主要作用是在View和ViewModel之间做了双向数据绑定。如果MVVM没有Binder,那么它与MVC的差异不是很大。

我们发现,正是因为View、ViewModel以及Model间的清晰的持有关系,所以在三个模块间的数据流转有了很好的控制。

那么双向数据绑定如何实现?

随着MVVM问世后,随之伴生而来的就是RAC(ReactiveCocoa),RAC是函数式+响应式编程结合,首先得去理解何为响应式函数编程(FRP)。

看了许多介绍,举一个最通俗易懂的例子——在命令式编程环境中, a = b+c表示将表达式的结果赋给 a,而之后改变 b 或 c的值不会影响 a。但在响应式编程中,a的值会随着 b或 c的更新而更新,意味着声明了一种绑定关系,b、c的变化会直接影响到a。

之前在iOS工作中,类之间的传值,无非就是block、delegate代理、KVO、notification这几种方法。在RAC中,同样具备替代KVO、delegate代理、通知、UI target、计时器timer、数据结构等各种方法。依据响应式函数编程,RAC方法本身更加简洁明了,通过提供信号的方式(RACSignal)可以捕捉当前以及未来的属性值变化,而且无需持续观察和更新代码。可直接在block中将逻辑代码加入其中,使得代码紧凑,更加直观。

虽然RAC功能强大,使得MVVM实现起来更优雅,但成也RAC,败也RAC,因为RAC造成了学习成本高和DEBUG困难,使得一些想要尝试MVVM的人望而却步。

但要纠正两点:

  • MVVM绝不等于RAC,所以MVVM并不存在DEBUG难的问题。
  • MVVM正是因为跟RAC不对等,所以博文中“MVVM一个首要的缺点是,MVVM的学习成本和开发成本都很高”这句话也是不成立的。

MVVM架构本身并不复杂,而且不用RAC我们依然可以通过KVO、类KVO的方式来帮我们实现View和ViewModel绑定器功能。

(3)MVP

MVP是单词Model View Presenter的首字母的缩写,分别表示数据层、视图层、发布层,它是MVC架构的一种演变。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

下面是原理图:

1、MVP分离了view和model层,Presenter层充当了桥梁的角色,View层只负责更新界面即可,这里的View我们要明白只是一个viewinterface,它是视图的接口,这样我们在做单元测试的时候可以非常方便编写Presenter层代码。

2、厚重的Controller层代码也得到了释放,之前我们开发的时候会对UIViewController、Activity、Fragment编写很多的业务逻辑,尽管大家会将Service层做分离,如net层,DB层等,但还是无法避免类似的问题,activity uicontroller无法重复利用是非常难以忍受的。

3、有一点还需要注意,presenter是双向绑定的关系,因此,在设计的时候就要注意接口和抽象的使用,尽可能的降低代码的耦合度,这也是mvp的宗旨。

2、设计模式

平时开发中,设计模式无处不在,只是没感觉到而已,那么iOS开发常用的设计模式有哪些?

原型模式(Prototype pattern)

简单工厂模式、工厂模式、抽象工厂模式(Factory Method pattern)

建造者模式(Builder Pattern)

单例模式(Singleton Pattern)

适配器模式(Adapter Pattern)

中介者模式(Mediator Pattern)

观察者模式(Observer Pattern)

组合模式(Composite Pattern)

访问者模式(Visitor Pattern)

装饰模式(Decorator pattern)

责任链模式(Chain-of-responsibility pattern)

模版方法模式(Template method pattern)

策略模式(Strategy pattern)

命令模式(Command Pattern)

享元模式(Flyweight Pattern)

代理模式(Proxy pattern)

备忘录模式(Memento Pattern)

桥接模式(Bridge Pattern)

外观模式(Facade pattern)

头瞬间大了三号,不过不用着急,这些设计模式在平时开发中,已经接触并使用过了,只是没有归结某种设计模式而已。

(1)原型模式(Prototype pattern)

    NSArray *array = [[NSArray alloc] initWithObjects:@1, nil];NSArray *arrayCopy = [array copy];

实际开发中,这样的代码肯定没少写。原型模式是创建型模式的一种,其特点在于通过「复制」一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的「原型」,这个原型是可定制的。

原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。

(2)简单工厂模式、工厂模式、抽象工厂模式(Factory Method pattern)

1>简单工厂模式

把很容易变化的地方用一个单独的类来做这个创造实例的过程,这个就是工厂。

看一个计算器的例子:


Operation operationAdd = ^(int a, int b){return a + b;
};
Operation operationSub = ^(int a, int b){return a - b;
};
Operation operationMul = ^(int a, int b){return a * b;
};
Operation operationDiv= ^(int a, int b){return a / b;
};+(Operation) createOperate:(NSInteger)operate {Operation oper = nil;switch (operate) {case 0:oper = operationAdd;break;case 1:oper = operationSub;break;case 2:oper = operationMul;break;case 3:oper = operationDiv;break;default:break;}return oper;
}//使用
Operation oper = nil;
oper = [OperationFactory createOperate:0];
int result = oper(1, 2);
NSLog(@"%d", result);

2>工厂模式

//定义汽车类
@interface Car : NSObject
+(Car *) produce;
@end//定义特斯拉汽车类
@interface Tesla : Car
@end
@implementation
+(Car *) produce {return [Tesla new];
}
@end//定义奥迪汽车类
@interface Audi : Car
@end
@implementation
+(Car *) produce {return [Audi new];
}
@end

“工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

上述代码中,特斯拉(Tesla)和奥迪(Audi)都继承了汽车类(Car),其子类Tesla和Audi都拥有了produce方法,但其produce方法生产汽车的特性,是由各汽车厂家决定的。

再看一个例子:

在 Cocoa Touch 框架中,以 NSArray 举例,将原有的 alloc+init (相当于不同对象调用 new 方法) 拆开写:

id obj1 = [NSArray alloc]; // __NSPlacehodlerArray *
id obj2 = [NSMutableArray alloc];  // __NSPlacehodlerArray *
id obj3 = [obj1 init];  // __NSArrayI *
id obj4 = [obj2 init];  // __NSArrayM *

发现 + alloc 后并非生成了我们期望的类实例,而是一个__NSPlacehodlerArray 的中间对象,后面的 - init- initWithXXXXX 消息都是发送给这个中间对象,再由它做工厂,生成真的对象。这里的 __NSArrayI__NSArrayM 分别对应 ImmutableMutable(后面的 I 和 M 的意思)

于是顺着思路猜实现,__NSPlacehodlerArray 必定用某种方式存储了它是由谁 alloc 出来的这个信息,才能在 init 的时候知道要创建的是可变数组还是不可变数组。

3>抽象工厂模式

抽象工厂模式是对象的创建模式,它是工厂方法模式的进一步推广。

  假设一个子系统需要一些产品对象,而这些产品又属于一个以上的产品等级结构。那么为了将消费这些产品对象的责任和创建这些产品对象的责任分割开来,可以引进抽象工厂模式。这样的话,消费产品的一方不需要直接参与产品的创建工作,而只需要向一个公用的工厂接口请求所需要的产品。

  通过使用抽象工厂模式,可以处理具有相同(或者相似)等级结构中的多个产品族中的产品对象的创建问题。

在上面抽象工厂模式的定义中涉及到了一个名词:产品族。他的意思是有多类产品,每一类产品中又分多种相似的产品。下面是百度百科的解释:

是指位于不同产品等级结构中,功能相关联的产品组成的家族。
一般是位于不同的等级结构中的相同位置上。显然,每一个产
品族中含有产品的数目,与产品等级结构的数目是相等的,形
成一个二维的坐标系,水平坐标是产品等级结构,纵坐标是产
品族。叫做相图。---百度百科

抽象工厂模式的优点

(1)分离接口和实现

客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。

(2)使切换产品族变得容易

因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从Intel系列到AMD系列只需要切换一下具体工厂。

抽象工厂模式的缺点

不太容易扩展新的产品:如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。

何时使用

(1)一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

(2)这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

(3)同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。(比如:Intel主板必须使用Intel CPU、Intel芯片组)

(4)系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

下面我们来看看上面这个实例的代码:

#import <Foundation/Foundation.h>/*!*  CPU父类**  @since V1.0*/
@interface CpuBase : NSObject@property int pins;//针脚数
-(void)calculate;@end#import "CpuBase.h"/*!*  Intel的CPU子类**  @since V1.0*/
@interface IntelCPU : CpuBase
@end#import <Foundation/Foundation.h>/*!*  MainBoard父类**  @since V1.0*/
@interface MainBoardBase : NSObject@property int cpuHoles;//CPU插槽数
-(void)installCpuHoles;//统计CPU插槽数@end#import "MainBoardBase.h"/*!*  Intel的主板子类**  @since V1.0*/
@interface IntelMainBoard : MainBoardBase@end#import <Foundation/Foundation.h>
#import "CpuBase.h"
#import "MainBoardBase.h"/*!*  工厂基类**  @since V1.0*/
@interface FactoryBase : NSObject
/*!*  创建CPU*  @return 返回CPU实例**  @since V1.0*/
-(CpuBase*)createCpu;
/*!*  创建主板**  @return 返回主板实例**  @since V1.0*/
-(MainBoardBase*)createMainBoard;@end#import "FactoryBase.h"
#import "IntelCPU.h"
#import "IntelMainBoard.h"/*!*  Intel工厂子类**  @since V1.0*/
@interface IntelFactory : FactoryBase@end#import <Foundation/Foundation.h>
#import "CpuBase.h"
#import "MainBoardBase.h"
#import "FactoryBase.h"/*!*  电脑工程师类**  @since V1.0*/
@interface ComputerEngineer : NSObject@property(nonatomic,retain)CpuBase* cpu;//CPU基类
@property(nonatomic,retain)MainBoardBase* mainBoard;//主板基类
-(void)makeComputer:(FactoryBase*)factoryBase;
/*!*  组装硬件**  @param factoryBase 工厂基类**  @since V1.0*/
-(void)prepareHardwares:(FactoryBase*)factoryBase;@end客户端类:
- (void)viewDidLoad
{[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.//创建装机工程师对象ComputerEngineer* cf = [[ComputerEngineer alloc] init];//客户选择并创建需要使用的产品对象FactoryBase* af = [[IntelFactory alloc] init];
//    FactoryBase* af = [[AMDFactory alloc] init];//告诉装机工程师自己选择的产品,让装机工程师组装电脑[cf makeComputer:af];
}

小总结:简单工厂,工厂方法与抽象工厂对比

简单工厂:工厂可以创建同一系列的产品,产品的接口一致,但工厂就要根据参数进行判断到底创建哪种产品。例如:卖早饭的张婆婆:可以做茶叶蛋,包子,稀饭

工厂方法:可以有多种工厂,工厂有共同的接口,一个工厂只能产生一种产品,比起简单工厂,工厂方法就不需要判断,耦合度低了不少。 例如:刘老板:只卖包子的包子铺,只卖稀饭的稀饭庄

抽象工厂:可以产生多个系列的产品,有2个维度的产品。例如:KFC老板:可乐系列产品、汉堡系列产品,每种系列产品又分大,中,小三种。

如果这样来看应该很容易就能区分他们之间的关系了。

(3)建造者模式(Builder Pattern)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。

建造者模式包含如下角色:

  • Builder:抽象建造者(为创建一个产品对象的各个部件指定抽象接口)
  • ConcreteBuilder:具体建造者(实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并 提供一个检索产品的接口)
  • Director:指挥者(构造一个使用Builder接口的对象)
  • Product:产品角色(表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口)

实序图

建造者模式主要是用于创建一些复杂的对象,这些对象内部构建间的建造顺序是稳定的,但是对象内部的构建通常面临着复杂的变化。

建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了改产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。

例子:

我来到了别克4S店,目的就是想买一辆别克汽车Product(LHBuickCar)。

#import <Foundation/Foundation.h>@interface LHBuickCar : NSObject@property (nonatomic, copy)NSString *name;// 名字
@property (nonatomic, copy)NSString *level;// 级别
@property (nonatomic, copy)NSString *price;// 价格
// 还有等等好多好多参数,但是我只关心这些@end@implementation LHBuickCar// 让他做跟自我介绍
- (NSString *)description{return [NSString stringWithFormat:@"我是一辆:%@ %@车 价格:%@",self.name,self.level,self.price];
}@end

刚进门,迎面就过来一个销售美女,她就是今天的主角Director(LHDirector4S),根据我的要求给我介绍不同的车辆。

#import <Foundation/Foundation.h>
#import "LHBuickCar.h"
#import "LHBuickBuilder.h"@interface LHDirector4S : NSObject// 根据客户的要求介绍一辆别克车
+ (LHBuickCar *)creatBuickCar:(LHBuickBuilder *)builder;@end#import "LHDirector4S.h"@implementation LHDirector4S+ (LHBuickCar *)creatBuickCar:(LHBuickBuilder *)builder{LHBuickCar *buickCar = [builder makeBuickCar];return buickCar;
}
@end

我也不知道我要买哪一款车,所以销售也不知道重点给我介绍哪一款。于是,销售给了我4款车的宣传册(LHBuickExcelleGTBuilder、LHBuickVeranoBuilder、LHBuickRegalBuilder、LHBuickLacrosseBuilder)。我大概看了一下,他的格式是一样的,无非就是具体的参数不一样。那我们可以先定义出这个宣传册的固定格式:LHBuickBuilder。

#import <Foundation/Foundation.h>
#import "LHBuickCar.h"@interface LHBuickBuilder : NSObject@property (nonatomic, copy)NSString *name;// 名字
@property (nonatomic, copy)NSString *level;// 级别
@property (nonatomic, copy)NSString *price;// 价格- (LHBuickCar *)makeBuickCar;@end#import "LHBuickBuilder.h"@implementation LHBuickBuilder- (LHBuickCar *)makeBuickCar{LHBuickCar *buickCar = [[LHBuickCar alloc] init];buickCar.name = self.name;buickCar.level = self.level;buickCar.price = self.price;return buickCar;
}@end

下面是每款车宣传册的具体配置

别克英朗

#import "LHBuickBuilder.h"@interface LHBuickExcelleGTBuilder : LHBuickBuilder@end#import "LHBuickExcelleGTBuilder.h"@implementation LHBuickExcelleGTBuilder- (instancetype)init
{self = [super init];if (self) {self.name = @"别克英朗";self.level = @"A级车";self.price = @"10.99-15.99万";}return self;
}@end

别克威朗

#import "LHBuickBuilder.h"@interface LHBuickVeranoBuilder : LHBuickBuilder@end#import "LHBuickVeranoBuilder.h"@implementation LHBuickVeranoBuilder- (instancetype)init
{self = [super init];if (self) {self.name = @"别克威朗";self.level = @"A+级车";self.price = @"13.59-19.99万";}return self;
}@end

别克君威

#import "LHBuickBuilder.h"@interface LHBuickRegalBuilder : LHBuickBuilder@end#import "LHBuickRegalBuilder.h"@implementation LHBuickRegalBuilder- (instancetype)init
{self = [super init];if (self) {self.name = @"别克君威";self.level = @"B级车";self.price = @"17.89-27.99万";}return self;
}@end

别克君越

#import "LHBuickBuilder.h"@interface LHBuickLacrosseBuilder : LHBuickBuilder@end#import "LHBuickLacrosseBuilder.h"@implementation LHBuickLacrosseBuilder- (instancetype)init
{self = [super init];if (self) {self.name = @"别克君越";self.level = @"C级车";self.price = @"22.58-33.98万";}return self;
}@end

看了几眼就蒙了,不懂啊,我就跟美女说:我想用尽量少的钱买尽量舒适高档的车,预算15万左右

于是美女二话不说,拿起别克威朗宣传册就开始滔滔不绝的给我讲解起来,什么性价比最好的A+级车(我哪里知道什么叫A/B/C级车啊)、价格符合预期、外形漂亮、内饰高档。。。

5分钟后,我得到了她所说的信息:(下面看客户端的调用)

#import "ViewController.h"
#import "LHBuickVeranoBuilder.h"
#import "LHDirector4S.h"
#import "LHBuickCar.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];LHBuickBuilder *builder = [[LHBuickVeranoBuilder alloc] init];LHBuickCar *buickCar = [LHDirector4S creatBuickCar:builder];NSLog(@"%@",buickCar.description);
}- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];
}@end

(4)单例模式(Singleton Pattern)

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

在 Cocoa Touch 框架中,最常见的使用了单例模式的就是 UIApplication 类了。每个应用程序有且仅有一个 UIApplication 的实例,它由 UIApplicationMain 函数在程序启动时创建为单例对象,之后,对同一 UIApplication 实例可以通过其 sharedApplication 类方法进行访问。

单例用来集中管理对类的对象所提供的资源,例如应用程序中需要用集中式的类来协调其服务,这个类就应该生成单一的实例。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁。

(5)适配器模式(Adapter Pattern)

在设计模式中,适配器模式有时候也称包装样式或者包装(wrapper)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。

适配器模式包含两种,一种是类适配器,另一种是对象适配器类适配器是通过类的继承实现的适配,而对象适配器是通过对象间的关联关系,组合关系实现的适配。二者在实际项目中都会经常用到,由于对象适配器是通过类间的关联关系进行耦合的,因此在设计时就可以做到比较灵活,而类适配器就只能通过覆写源角色的方法进行拓展,在实际项目中,对象适配器使用到的场景相对较多。在iOS开发中也推荐多使用组合关系,而尽量减少继承关系,这是一种很好的编程习惯

对象适配器:

类适配器:

类适配器模式中适配器和被适配者是继承关系。request 方法里会去调用 super 的 specificRequest 方法,达到将类的接口转换成客户希望的另一个接口。

适配器模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。

适配器模式优点

  • 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
  • 增加了类的透明性。我们访问的是目标角色,但是实现却在源角色里。
  • 提高了类的复用度。源角色在原有系统中还是可以正常使用的。
  • 灵活性非常好。不想要适配器时,删掉这个适配器就好了,其他代码不用改。

例子:

//Target
#import <Foundation/Foundation.h>@protocol Target <NSObject>- (void)userExpectInterface;@end//Adaptee
#import <Foundation/Foundation.h>@interface Adaptee : NSObject- (void)doSometing;@end@implementation Adaptee- (void)doSometing
{NSLog(@"adaptee doing something!");
}@end//Adapter
#import "Target.h"
#import "Adaptee.h"@interface Adapter : NSObject<Target>@property (strong, nonatomic) Adaptee *adaptee;- (id)initWithAdaptee:(Adaptee *)adaptee;@end@implementation Adapter@synthesize adaptee = _adaptee;- (id)initWithAdaptee:(Adaptee *)adaptee
{if (self = [super init]) {_adaptee = adaptee;}return self;
}- (void)userExpectInterface
{[self.adaptee doSometing];
}@end//main
#import <Foundation/Foundation.h>
#import "Adapter.h"
#import "Adaptee.h"
#import "Target.h"int main(int argc, const char * argv[])
{@autoreleasepool {Adaptee *adaptee = [[Adaptee alloc]init];id<Target> object = [[Adapter alloc]initWithAdaptee:adaptee];[object userExpectInterface];}return 0;
}

(6)桥接模式(Bridge Pattern)

设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

• 第一种设计方案是为每一种形状都提供一套各种颜色的版本。

• 第二种设计方案是根据实际需要对形状和颜色进行组合。

对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

桥接模式有以下几种角色:

抽象角色(Abstraction): 抽象的定义,并保存一个Implementor对象的引用。

扩展抽象角色(RefineAbstraction): 拓展Abstraction。

抽象实现角色(Implementor): 定义实现类的接口,提供基本操作,其实现交给子类实现。

具体实现角色(ConcreteImplementor): 实现Implementor接口,在程序运行时,子类对象将替换其父类对象,提供给Abstraction具体的业务操作方法。

其中,Abstraction为抽象化角色,定义出该角色的行为,同时保存一个对实现化角色的引用;Implementor是实现化角色,它是接口或者抽象类,定义角色必需的行为和属性;RefinedAbstraction为修正抽象化角色,引用实现化角色对抽象化角色进行修正;ConcreteImplementor为具体实现化角色,实现接口或抽象类定义的方法或属性。

桥接模式的应用

1. 何时使用

  • 系统可能有多个角度分类,每一种角度都可能变化时

2. 方法

  • 把这种角度分类分离出来,让它们单独变化,减少它们之间的耦合(合成/聚合复用原则)

3. 优点

  • 抽象和实现分离。桥梁模式完全是为了解决继承的缺点而提出的设计模式

  • 优秀的扩展能力
  • 实现细节对客户透明。客户不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装

4. 缺点

  • 会增加系统的理解与设计难度。由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程

5. 使用场景

  • 不希望或不适用使用继承的场景
  • 接口或抽象类不稳定的场景
  • 重用性要求较高的场景

6. 应用实例

  • 开关。我们可以看到的开关是抽象的,不用管里面具体怎么实现
  • 手机品牌与手机软件。两者间有一条聚合线,一个手机品牌可以有多个手机软件

7. 注意事项

  • 不要一涉及继承就考虑该模式,尽可能把变化的因素封装到最细、最小的逻辑单元中,避免风险扩散
  • 当发现类的继承有n层时,可以考虑使用该模式

例子1:

1. Abstraction抽象类

#import "Implementor.h"@interface Abstraction@property (strong, nonatomic) Implementor imp;//约束子类必须实现该构造函数
- (Abstraction *) initWithImp:(Implementor *) imp;//自身的行为和属性
- (void) request();@end@implementation Abstraction//自身的行为和属性
- (void) request() {self.imp.doSomething();
}@end

2. Implementor抽象类

@interface Implementor
@end@implementation Implementor//自身的行为和属性
- (void) doSomething() {
}@end

3. ConcreteImplementor

这里可以编写多个具体实现类。

@interface ConcreteImplementorA : Implementor
-(void) doSomething;
@end
@implementation Implementor
-(void) doSomething() {NSLog(@"具体实现A的doSomething执行");
}
@end

4. RefinedAbstraction

@interface RefinedAbstraction : Abstraction
//约束子类必须实现该构造函数
- (RefinedAbstraction *) initWithImp:(Implementor *) imp;
//修正父类行为
-(void) request;
@end
@implementation Implementor
//覆写构造函数
//约束子类必须实现该构造函数
- (RefinedAbstraction *) initWithImp:(Implementor *) imp {[super initWithImp:imp];
}
//修正父类行为
-(void) request {super.request();super.imp.doAnything();
}
@end

5. 使用

Implementor imp = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imp);
abs.request();

例子2:

我们可以让手机既可以按照手机品牌来分类,也可以按手机软件来分类。由于实现的方式有多种,桥接模式的核心意图就是把这些实现独立出来,让它们各自地变化,这就使得没中实现的变化不会影响其他实现,从而达到应对变化的目的。

1. 手机品牌抽象类

#import "HandsetSoft.h"@interface HandsetBrand : NSObject
@property (strong, nonatomic)  HandsetSoft soft;
//设置手机软件
- (HandsetBrand *) initWithSoft:(HandsetSoft *) soft;
//运行
- (void) run();
@end@implementation HandsetBrand
- (HandsetBrand *) initWithSoft:(HandsetSoft *) soft {self.soft = soft;
}
- (void) run;
@end

2. 手机软件抽象类

@interface HandsetSoft : NSObject
- (void) run();
@end
@implementation HandsetSoft
- (void) run() {
}
@end

3. 各类手机品牌

@interface HandsetBrandA : HandsetBrand
- (void) run();
@end
@implementation HandsetBrandA
- (void) run() {soft.run();
}
@end

4. 各类手机软件

@interface HandsetGame : HandsetSoft
- (void) run;
@end
@implementation HandsetGame
- (void) run {NSLog(@"运行手机游戏");
}
@end@interface HandsetAddressList : HandsetSoft
- (void) run;
@end
@implementation HandsetAddressList
- (void) run {NSLog(@"运行通讯录");
}
@end

5. 使用

HandsetGame *game = [HandsetGame new];//使用A品牌手机
HandsetBrand  *handsetBrand = [HandsetBrand initWithSoft:game];
NSLog(@"A品牌手机:");[handsetBrand.soft run];//使用A品牌手机
handsetBrand = [HandsetBrandA initWithSoft:game];;
NSLog(@"A品牌手机:");
handsetBrand.run();handsetBrand.soft = [HandsetAddressList new];
[handsetBrand.soft run];

这样我现在如果想要增加一个功能,比如音乐播放器,那么只有增加这个类就可以了,不会影响到其他任何类,类的个数增加也只是一个;如果是要增加S品牌,只需要增加一个品牌的子类就可以了,个数也是一个,不会影响到其他类。这显然符合开放-封闭原则。

而这里用到的合成/聚合复用原则是一个很有用处的原则,即优先使用对象的合成或聚合,而不是继承。究其原因是因为继承是一种强耦合的结构,父类变,子类就必须变。

(7)外观模式(Facade pattern)

为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。

外观模式在开发过程中运用频率非常高,尤其现在各种第三方SDK“充斥”在我们的开发中,这些SDK大多会使用外观模式。通过一个外观类是的整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也能够对用户屏蔽很多实现细节。外观模式也是我们封装API的常用手段,例如网络模块、ImageLoader模块等。还有我们平时使用的ide,ide就是外观类,客户端也就是开发人员通过debug按钮调用其debug子系统等等。


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

(8)中介者模式(Mediator Pattern)

中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。

我们开发的程序是由大量的类来组成的,随着程序功能的不断增加,类和类之间的依赖关系也跟着趋于复杂,而中介者模式便能解决这个问题。

如图所示,6 个 VC 类之间的交互可能特别多,如果让他们相互依赖,然后管理这些 VC 之间的关系是一件非常繁琐的事情,我们要处理各个 VC 之间的关系,每当一个 VC 要跳转到另外个 VC,我们需要包含新的 VC 的头文件。而使用中介者模式,让 VC 之间的交互变成 VC 和中介者的交互,用中介者来管理多对多的复杂的对象群,降低了各个对象之间的耦合,减少了对象之间逻辑的复杂度,但也可能导致中介者类中的实现过于复杂,不过可以用类别方式,将中介者类中的相关方法按照所属模块或功能拆分,这样可以解决终结者类过于臃肿(组件化实现方案中的路由实现)。

例子:UINavigationController 就是一个中介者,如下图所示

视图控制器的切换以及相关特性设置都是与 UINavigationController 做交互。由 UINavigationController 去做集中管理。

self.navigationController.viewControllers = ...
self.navigationController.navigationBar.tintColor = ...
self.navigationController.toolbar = ...
self.navigationController.delegate = ..

(9)观察者模式(Observer Pattern)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

在 Cocoa Touch 框架中Notification、KVO 都实现了观察者模式。通知是由一个中心对象为所有观察者提供变更通知,KVO 是被观察的对象直接向观察者发送通知。

观察者模式是一种对象行为型模式,其主要优点如下。

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  2. 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

注意:当任务完成或终止时,记得要移除目标与观察者之间的依赖关系

(10)组合模式(Composite Pattern)

将对象组合成树形结构以表示“部分-整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性。

何时使用组合模式: 

当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。

自定义控件时,就是把一些基本的控件组合起来,通过编程写成一个定制的控件。这就是典型的组合模式应用。

组合模式的好处

  • 组合模式定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这些组合对象又可以被组合,这样不断地递归下去,客户端代码中,任何用的的基本对象的地方都可以使用组合对象了。
  • 组合模式让客户可以一致地使用组合结构和单个对象。

标准组成

  • Component:组合中的抽象接口
  • Composite:子结点
  • Leaf:子结点

以树为例:

Composite,Leaf 指一类结点,并不是唯一,其中 Leaf 是无子结点,也可以说是 Composite 的一种特殊情况。结合 UIView 的代码,下面结合代码,应该可以进一步加深理解组合模式。

在Cocoa Touch框架中使用组合模式

在Cocoa Touch 框架中,UIView被组织成一个组合结构。每个UIView的实例可以包含UIView的其他实例,形成统一的树形结构。让客户端对单个UIView对象和UIView的组合统一对待。

例子:

新建Component类,并定义抽象接口:

@interface Component : NSObject
@property(nonatomic,strong)NSString *name;
- (Component *)initWithName:(NSString *)myName;
- (void)add:(Component *)c;
- (void)remove:(Component *)c;
- (void)display:(int)depth;
@end@implementation Component-(Component *)initWithName:(NSString *)myName {self = [super init];if (self != nil) {self.name = myName;}return self;
}-(void)add:(Component *)c {return;
}
-(void)remove:(Component *)c {return;
}
-(void)display:(int)depth {return;
}
@end

叶子类:

@interface Leaf : Component- (Leaf *)initWithName:(NSString *)myName;
@end
@implementation Leaf-(Leaf *)initWithName:(NSString *)myName {self = [super init];if (self != nil) {self.name = myName;}return self;
}
-(void)add:(Component *)c {NSLog(@"Can not add a leaf");
}
-(void)remove:(Component *)c {NSLog(@"Can not remove from a leaf");
}
-(void)display:(int)depth {NSLog(@"[%dLevel]%@",depth, self.name);
}
@end

整体组合类Composite:

@interface Composite : Component
{NSMutableArray *childrenArr;
}
- (Composite *)initWithName:(NSString *)myName;@end
@implementation Composite-(Composite *)initWithName:(NSString *)myName {self = [super init];if (self != nil) {self.name = myName;childrenArr = [NSMutableArray new];}return self;
}- (void)add:(Component *)c {[childrenArr addObject:c];
}
- (void)remove:(Component *)c {[childrenArr removeObject:c];
}
- (void)display:(int)depth {NSLog(@"[%dLevel]%@",depth, self.name);for(Component *component in childrenArr) {[component display:depth+1];}
}
@end

使用:

Composite *root = [[Composite alloc] initWithName:@"root"];
[root add:[[Leaf alloc] initWithName:@"Leaf A"]];
[root add:[[Leaf alloc] initWithName:@"Leaf B"]];Composite *comp = [[Composite alloc] initWithName:@"Composite X"];
[comp add:[[Leaf alloc]initWithName:@"Leaf XA"]];
[comp add:[[Leaf alloc]initWithName:@"Leaf XB"]];
[root add:comp];Composite *comp2 = [[Composite alloc] initWithName:@"Composite XY"];
[comp2 add:[[Leaf alloc] initWithName:@"Leaf XYA"]];
[comp2 add:[[Leaf alloc] initWithName:@"Leaf XYB"]];
[comp add:comp2];[root Add:[[Leaf alloc] initWithName:@"Leaf C"]];
Leaf *leaf = [[Leaf alloc] initWithName:@"Leaf D"];[root add:leaf];
[root remove:leaf];
[root display:1];

组合模式的主要意图是让树形结构中的每个节点具有相同的抽象接口。这样整个结构可以作为一个统一的抽象结构使用,而不是暴露其内部表示。对每个结点的任何操作,可以通过协议或抽象基类中定义的相同接口来进行

(11)访问者模式(Visitor Pattern)

访问者模式是一种将算法与对象结构分离的软件设计模式。表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

1.Visitor 抽象访问者角色,为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。

2.ConcreteVisitor.具体访问者角色,实现Visitor声明的接口。

3.Element 定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。

4.ConcreteElement 具体元素,实现了抽象元素(Element)所定义的接受操作接口。

5.ObjectStructure 结构对象角色,这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)。

例子:

#import <Foundation/Foundation.h>
@protocol VisitorProtocol;@protocol ElementProtocol <NSObject>/***  接收访问者**  @param visitor 访问者对象*/
- (void)accept:(id <VisitorProtocol>)visitor;/***  元素公共的操作*/
- (void)operation;@end
#import <Foundation/Foundation.h>
#import "ElementProtocol.h"@protocol VisitorProtocol <NSObject>- (void)visitElement:(id <ElementProtocol>)element;@end
#import <Foundation/Foundation.h>
@protocol ElementProtocol;@interface ElementCollection : NSObject/***  添加元素**  @param element 元素*  @param key     元素的键值*/
- (void)addElement:(id <ElementProtocol>)element withKey:(NSString *)key;/***  获取所有元素的键值**  @return 所有元素的键值*/
- (NSArray *)allKeys;/***  根据元素键值获取元素**  @param key 元素的键值**  @return 元素*/
- (id <ElementProtocol>)elementWithKey:(NSString *)key;@end
#import "ElementCollection.h"
#import "ElementProtocol.h"@interface ElementCollection ()@property (nonatomic, strong) NSMutableDictionary  *elementsDictionary;@end@implementation ElementCollection- (instancetype)init {self = [super init];if (self) {self.elementsDictionary = [NSMutableDictionary dictionary];}return self;
}- (void)addElement:(id <ElementProtocol>)element withKey:(NSString *)key {NSParameterAssert(element);NSParameterAssert(key);[self.elementsDictionary setObject:element forKey:key];
}- (NSArray *)allKeys {return self.elementsDictionary.allKeys;
}- (id <ElementProtocol>)elementWithKey:(NSString *)key {NSParameterAssert(key);return [self.elementsDictionary objectForKey:key];
}@end

(12)装饰模式(Decorator pattern)

Decorator装饰模式是一种结构型模式,它主要是解决:“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀(多继承)。继承为类型引入的静态特质的意思是说以继承的方式使某一类型要获得功能是在编译时。所谓静态,是指在编译时;动态,是指在运行时。

修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

Category 就是实现了装饰的设计模式。Category 是 Objective-C 的语言功能,通过它可以给类添加方法的接口与实现,而不必子类化。 从这个设计模式的描述联想到 Category,就没什么难理解了。

(13)责任链模式(Chain-of-responsibility pattern)

责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。

责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间发生耦合。此模式将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

Cocoa Touch 中的事件处理流程--响应者链就实现了责任链模式。以点击为例,首先通过 hit-test view 的流程找到被点击的视图,被点击的视图如果不处理点击事件,则沿着响应者链向上回溯,比如给父视图发消息,让父视图去处理,父视图不处理则继续沿着响应者链向上回溯,直到有对象处理它为止,如果都不处理,则该事件丢弃。

响应者链(Responder Chain)

先来说说响应者对象(Responder Object),顾名思义,指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。

UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。图一展示了响应者链的基本构成:

从图一中可以看到,响应者链有以下特点:

1、响应者链通常是由视图(UIView)构成的;

2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super View);

3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;

4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者

需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;

5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

事件分发(Event Delivery)

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。
hitTest:withEvent:方法的处理流程如下:
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
若返回NO,则hitTest:withEvent:返回nil;
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

加入用户点击了View E,下面结合图二介绍hit-test view的流程:

1、A是UIWindow的根视图,因此,UIWindow对象会首相对A进行hit-test;

2、显然用户点击的范围是在A的范围内,因此,pointInside:withEvent:返回了YES,这时会继续检查A的子视图;

3、这时候会有两个分支,B和C:

点击的范围不再B内,因此B分支的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;

点击的范围在C内,即C的pointInside:withEvent:返回YES;

4、这时候有D和E两个分支:

点击的范围不再D内,因此D的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;

点击的范围在E内,即E的pointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,E的hitTest:withEvent:会将E返回,再往回回溯,就是C的hitTest:withEvent:返回E--->>A的hitTest:withEvent:返回E。

至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。

不难看出,这个处理流程有点类似二分搜索的思想,这样能以最快的速度,最精确地定位出能响应触摸事件的UIView。

说明:

1、如果最终hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;

2、hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。

3、我们可以重写hitTest:withEvent:来达到某些特定的目的。

(14)模版方法模式(Template method pattern)

定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以重定义算法的某些特定步骤而不改变该算法的结构。

模板方法可以提高可扩展性与可复用性,比如 UIView 类中的定制绘图,UIView 的结构不改变,只是继承 UIView,再重载 - (void)drawRect:(CGRect)rect
方法。所以 - (void)drawRect:(CGRect)rect 就是模板方法,默认什么都不做或者只是做了部分操作,缺少特性操作,用来给子类选择重载与实现的方法。

(15)策略模式(Strategy pattern)

策略(Strategy)模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。看到策略模式的时候有的时候跟简单工厂相比较,其实有很大的迷惑性,都是继承多态感觉没有太大的差异性,简单工厂模式是对对象的管理,策略模式是对行为的封装。可以先简单的看一下结构图:

举一个常见的例子,验证 UITextField 输入是否有效。有两个算法分别是验证邮箱的和验证电话号码的。可以通过 if else 这样的判断代码来决定执行哪个算法。也可以通过策略模式,将算法封装起来,如下图

Strategy 是这一系列算法的父类,ConcreteStrategyA, B, C。是三种算法,给 Context 对象添加一个 Strategy 类型的属性,里面存放着 ConcreteStrategyA 或者 B,C。然后 Context 对象就知道去执行哪个算法。也就知道自己需要执行什么策略。

策略模式首先将算法都封装起来了,易于理解,且易于切换和扩展。

角色一:Context 用于操作策略的上下文环境 场景类的对象配置有一个具体策略对象的实例,场景对象使用策略接口调用有具体策略类定义的算法。
角色二:Stragety 抽象策略
角色三:ConcreteStragety 具体策略

例子:

假设应用程序中需要用UITextField以接受用户的输入,然后要在应用程序的处理中使用这个输入值。应用程序有个文字输入框,只接受字母,即a-z或A-Z,还有个输入框只接受数值型的值,即0~9。为了保证每个字段的输入有效,需要在用户结束文本框的编辑时做些验证。
  这个时候我们可以把数据验证放大UITextField的委托方法textFieldDidEndEditing:之中。UITextfield的实例每当失去焦点时会调用这个方法。如果不用策略模式,代码通常会写成下面的样子:

- (void)textFieldDidEndEditing:(UITextField *)textField
{if (textField == letterTextField) {//验证是否是字母} else if (textField == numberTextField){//验证是否是数字} else if ...
}

要是有更多不同类型的文本框,条件语句还会继续下去,代码越来越臃肿,难以维护。如果能去掉这些条件语句,代码会更易管理,将来对代码的维护也会容易得多。(如果代码中有很多条件语句,就可能意味着需要把它们重构成各种策略模式)
  所以现在的目标是把这些验证检查提到各种策略类中,这样它们就能在委托或者其他方法中重用。每个验证都从文本框取出输入值,然后根据所需的策略进行验证,最后返回个BOOL值; 如果验证失败,还会返回一个NSError实例。返回的NSError可以解释失败的原因。

这里不把接口声明为协议,而是声明为抽象基类。抽象基类更适合解决这种问题,因为它更容易重构各种具体策略子类的某些共同行为。

使用策略模式后客户端代码的样子:

- (void)textFieldDidEndEditing:(UITextField *)textField
{if ([textField isKindOfClass:[CustomTextField class]]) {[(CustomTextField*)textField validate];}
}

(16)命令模式(Command Pattern)

将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象。命令模式也支持撤销操作。

在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

模式中角色

抽象命令(Command):定义命令的接口,声明执行的方法。

具体命令(ConcreteCommand):具体命令,实现要执行的方法,它通常是“虚”的实现;通常会有接收者,并调用接收者的功能来完成命令要执行的操作。

接收者(Receiver):真正执行命令的对象。任何类都可能成为一个接收者,只要能实现命令要求实现的相应功能。

调用者(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

客户端(Client):命令由客户端来创建,并设置命令的接收者。

就如同看电视,遥控器是命令管理者,遥控器按钮每次按下 都是创建一个命令 发给接收器。 每个电视都有一个一个接收器。接受器 和电视关联 并且可以对电视做部分的操作。

对应关系如下:

电视机  对应   viewController.view  ;   接收器 对应 receiver

遥控器 对应  Invoker  负责 生成命令  rollBack 操作。

每个 具体的命令 都是通过 执行  CommandProtocol 来处理。

例子1:改变一个视图的明暗程度的小实例项目(未使用命令模式)

首先在ViewController添加三个按钮,并设置好相关属性和监听点击事件:

然后创建任务执行者对象Receiver类,创建的实例对象负责执行调整View背景明亮暗度

因为在非命令模式下,撤销逻辑不好实现,这里就不提供实现了

最后展示效果:

其实到这里,Receive对象相当于ViewController的代理,代理完成控制属于Viewcontroller管理和控制的View 的背景明亮暗度调整。只不过这个代理不是那么抽象而且也不是遵循了某个协议的。是具体而直接完成逻辑业务的。这个简单的模式,在项目或者不考虑拓展性或者 某个模块功能固定了,可以使用这个模式。

但是有的业务需求会需要记录存储和取出执行任务的或者信息传递命令的状态,像这里如果要添加撤销操作,就需要记录之前的操作,然后根据之前的操作回 退反过来操作,这时候,命令模式就能比较方便的实现这个逻辑了。命令模式其实就是将发送的命令信息抽象成一个类,然后具体信息就创建具体的类,并通过回调 者添加并执行命令对象的执行命令涉及到的任务方法,同时存储记录这个命令,那么这时候因为每次操作都能存储下来,所以再来设计撤销操作就很容易了。这就是 命令模式的一个优点。

例子2:改变一个视图的明暗程度(使用命令模式)

分析之前的代码:

为了能够行为进行“记录、撤销/重做、事务”等处理,我们可以使用命令模式:

  • 设定接受者
  • 将改变明暗的操作抽象成对象
  • 撤销操作

(17)享元模式(Flyweight Pattern)

享元模式(英语:Flyweight Pattern)是一种软件设计模式。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于当大量物件只是重复因而导致无法令人接受的使用大量内存。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。

tableViewCell 的重用机制就是实现了享元模式。在要使用一个 Cell 的时候,会先去重用池里看看 tableView 有没有可以重用的 cell,如果有重用该 cell,没有创建一个,这就是享元模式。

享元模式主要有两个关键组件,可共享的享元对象和保存它们的享元池。

举另一个实现例子,画面上需要显示 100 个相同的图案,可以只生成一个包含该图案 image 的 imageView。其它 99 个只需要去享元池里去拿这个 imageView 实例的信息,然后在页面里直接绘制图案,这样就不需要生成 100 个图案实例。

享元模式通过共享一部分必须的对象,减少对象的创建,节省大量的内存。

(18)代理模式(Proxy pattern)

所谓的代理者是指一个类可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、内存中的大对象、文件或其它昂贵或无法复制的资源。

当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少内存用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。

著名的代理模式例子为引用计数(英语:reference counting)指针对象。

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。

代理设计模式的英文名是 Proxy pattern,和我们常见的 delegate(委托) 没关系。

例子:

@protocol SubjectProtocol<NSObject>-(void)displayImage;@end@interface Subject : NSObject<SubjectProtocol>@end

Proxy中的subject属性相当于UML中的delegate指向了真实对象的引用:

@interface SubjectProxy()@property (strong,nonatomic)  Subject  *subject;@property (strong,nonatomic)  NSString  *fileName;@end@implementation SubjectProxy-(instancetype)initWithFileName:(NSString *)fileName{self=[super init];if (self) {self.fileName=fileName;}return self;
}-(void)displayImage{if (!self.subject) {self.subject=[[RealSubject alloc]initWithFileName:self.fileName];}[self.subject displayImage];
}@end

RealSubject的实现:

@interface RealSubject()@property (strong,nonatomic) NSString *fileName;@end@implementation RealSubject-(instancetype)initWithFileName:(NSString *)fileName{self=[super init];if (self) {self.fileName=fileName;}return self;
}-(void)loadFromDisk{NSLog(@"Loading--%@",self.fileName);
}-(void)displayImage{NSLog(@"Display--%@",self.fileName);
}@end

使用:

Subject *sub=[[SubjectProxy alloc]initWithFileName:@"FlyElephant"];
Subject *proxy=[[SubjectProxy alloc]initWithFileName:@"Book"];
[sub displayImage];
[proxy displayImage];

iOS开发中我们对委托用的比较多,理解代理模式也很容易,代理在Objective-C中有一种更重要的例子是用于引用计数指针对象,当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。

(19)备忘录模式(Memento Pattern)

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

Cocoa Touch 框架中归档可以实现备忘录模式,Cocoa 的归档是对对象及其属性还有同其他对象间的关系进行编码,形成一个文档,该文档可以保存于文件系统,也可在进程或网络间传输,最后又可以通过解档将文档解码成与该对象归档时状态一致的对象。

既将对象保存一个备份放置到其它地方,可以随时使用备份将该对象恢复到原先保存的状态,用来储存关键对象的关键状态。

备忘录模式
设计存储中心,指定存储接口,实现存储机制。

优化存储方案
统一存储规范,实现灵活多变的存储机制(FastCoder 本地序列化,转NSData,比对象实现NSCopying存储好)

例子:存储UIView的状态,数据库回退

备忘录中心

#import <UIKit/UIKit.h>
#import "MementoCenterProtocol.h"@interface MementoCenter : NSObject/**存储备忘录对象@param object 值@param key 键*/
+ (void)saveMementoObject:(id<MementoCenterProtocol>)object withKey:(NSString *)key;/**获取备忘录对象@param key 键@return 值*/
+ (id)mementoObjectWithKey:(NSString *)key;@end
#import "MementoCenter.h"@implementation MementoCenter+ (void)saveMementoObject:(id)object withKey:(NSString *)key {}+ (id)mementoObjectWithKey:(NSString *)key {return nil;
}@end

备忘录中心协议,存储的对象必须满足这个协议,才能存储到备忘录中心

#import <Foundation/Foundation.h>@protocol MementoCenterProtocol <NSObject>/**获取状态@return 状态值*/
- (id)currentState;/**从某种状态恢复@param state 状态值*/
- (void)recoverFromState:(id)state;@end

存储对象(苹果)

#import <Foundation/Foundation.h>
#import "MementoCenterProtocol.h"@interface Apple : NSObject<MementoCenterProtocol>@end
#import "Apple.h"@implementation Apple- (id)currentState {return self;
}- (void)recoverFromState:(id)state {}@end

使用:

#import "ViewController.h"
#import "Apple.h"
#import "MementoCenter.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];Apple *apple = [[Apple alloc] init];///save[MementoCenter saveMementoObject:[apple currentState] withKey:@"Apple"];///get[apple recoverFromState:[MementoCenter mementoObjectWithKey:@"Apple"]];
}- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];// Dispose of any resources that can be recreated.
}@end

总结

1、Prototype原型模式是一种创建型设计模式,它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。关联的的几种设计模式,予以区分。

  • Singleton单件模式解决的问题是:实体对象个数问题(这个现在还不太容易混)
  • AbstractFactory抽象工厂模式解决的问题是:“一系列互相依赖的对象”的创建工作
  • Builder生成器模式解决的问题是:“一些复杂对象”的创建工作,子对象变化较频繁,对算法相对稳定
  • FactoryMethor工厂方法模式解决的问题是:某个对象的创建工作。

iOS开发~细聊设计模式相关推荐

  1. 开发必看 | iOS开发常用设计模式!

    ios开发学习中,经常弄不清楚ios的开发模式,今天我们就来进行简单的总结和探讨~ (一)代理模式 应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现. 优势:解耦合 敏捷 ...

  2. IOS开发中的几种设计模式

    ios开发学习中,经常弄不清楚ios的开发模式,今天我们就来进行简单的总结和探讨~ (一)代理模式 应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现. 优势:解耦合 敏捷 ...

  3. iOS开发设计模式详解

    在软件开发中一般认为有23种设计模式(design pattern),这是软件开发中的较高的境界了.在iOS开发中最常用的有哪些设计模式呢?我们今天来分析一下: 一.[代理模式] 使用场景:当一个类的 ...

  4. 浅谈设计模式在iOS开发实战项目中的应用

    在我们日常的开发中设计模式伴随着项目的各个模块,巧妙地使用设计模式可以让我们写出更高效,简洁,优美的代码.可是因为对于设计模式的不熟悉,很多高效的设计模式并没有被很好地使用起来,而最近也正好在revi ...

  5. [贝聊科技]如何在iOS开发中更好的做假数据?

    当工期比较紧的时候,项目开发中会经常出现移动端等待后端接口数据的情形,不但耽误项目进度,更让人有种无奈的绝望.所以在开发中,我们常常自己做些假数据,以方便开发和UI调试.然而做假数据方法不同,效率和安 ...

  6. iOS开发系列--iOS应用架构谈

    转自:Casa Taloyum 目录 iOS应用架构谈 (一)开篇 iOS应用架构谈 (二)view层的组织和调用方案 iOS应用架构谈 (三)网络层设计方案 iOS应用架构谈 (四)动态部署方案 i ...

  7. iOS开发-面试总结(九)

    iOS面试指导 一 经过本人最近的面试和对面试资料的一些汇总,准备记录这些面试题,以便ios开发工程师找工作复习之用,本人希望有面试经验的同学能和我同时完成这个模块,先出面试题,然后会放出答案. 1. ...

  8. iOS开发常用三方库、插件、知名博客

    TimLiu-iOS iOS开发常用三方库.插件.知名博客等等,期待大家和我们一起共同维护,同时也期望大家随时能提出宝贵的意见(直接提交Issues即可). 持续更新... 版本:Objective- ...

  9. iOS开发第三方大全

    UI 下拉刷新 EGOTableViewPullRefresh- 最早的下拉刷新控件. SVPullToRefresh- 下拉刷新控件. MJRefresh- 仅需一行代码就可以为UITableVie ...

最新文章

  1. python无法选择安装位置图_python怎么安装?(教程图解)
  2. 20210614 什么是状态?什么是状态空间?
  3. 反向传播算法 Backpropagation Algorithm
  4. 【转】C#中[STAThread]的作用
  5. 修改主从服务器,搭建BIND主从服务器
  6. k8s创建pod加入容器_K8S容器编排之POD健康检测(2)
  7. JAVA在PDF指定位置赋值
  8. 《树莓派开发实战(第2版)》——1.2 封装树莓派
  9. SQLite查询优化(转)
  10. 实现统计二叉树叶子节点个数的算法
  11. 用计算机抽样,利用计算机代替随机数骰子进行随机抽样
  12. c++之 推箱子小游戏
  13. tomcat安装以及部署jpress
  14. 【linux kernel】linux内核如何唤醒线程
  15. javascript操作select元素一例
  16. FCN网络(Fully Convolutional Networks for Semantic Segmentation)
  17. 用python语言写小程序_小程序用什么语言开发?python语言开发可以开发吗?
  18. html .ani文件,ANI文件格式 | 学步园
  19. 计算机领域网络顶级会议,【分享】计算机领域的一些顶级会议【已搜索,无重复】 - 信息科学 - 小木虫 - 学术 科研 互动社区...
  20. ubuntu 下正确安装android手机驱动

热门文章

  1. 物联网技术在公共建筑能源管理系统中的应用
  2. 宝塔安装gitlab
  3. 生成圆锥内的均匀分布的单位向量(Generating uniform unit random vectors in a cone)
  4. android弧形背景,Android 弧形进度条
  5. 回声的来源和消除(转载)
  6. jQuery插件之【jqGrid】常用语法整理-【更新】
  7. Run自动打开软件时需用管理员方式打开解决方法
  8. 基于循环神经网络的机器翻译(英翻中)
  9. 【极简壁纸】简单高效美观的壁纸网站 1
  10. 【非常详细】思科与华为设备命令对照表