这次我们要遵循两个基本原则:
找出 #变化 封装之
优先使用对象聚集,而不是继承
1.适配器
用于连接两种不同的东西,也称包装器。概念简单,就是我们常说的“加一层封装”,下面是它的类图:
在OC中,可以使用协议的模式,让实际的适配器遵循某些方法,对于具体的实现我们不用知道,我们可以通过调用适配器的方法直接得到结果。
实际使用中,和OC习惯的回调不大一样,这里会像java一样把protocol当做接口使用。使用的是实现该protocol的实例,直接调用protocol中定义的方法。
2.桥接模式
把抽象层次结构从实现抽离出来,使之独立变更。先看类图:
Abstraction接口中持有具体实现接口Implementor的实例,前者的operation实际调用的是实例对象的operationImp方法。注意:Abstraction可以和Implementor完全不同,比如前者是复杂操作,后者实现的是基本操作。
在以下情形,自然会想到使用这一模式:
- 不想在抽象于其实现之间形成固定的绑定关系(这样就能在运行时切换实现);
- 抽象及实现都应可以通过子类化独立进行拓展;
- 对抽象的实现进行修改不应该影响客户端代码;
- 如果每个实现都需要额外的子类以细化抽象,则说明有必要把他们分成两个部分;
- 想在带有不同抽象接口的多个对象之间共享一个实现。
看了两个例子,榴莲书上的游戏控制器&仿真器,和图形&绘图程序(下面有链接)的例子,这个模式应对的是类爆炸的问题,N*N的应对策略,就是两者都会独立拓展的情况。当一个模型有多个维度特性且需要拓展的时候,最好不要使用多继承(实现多个接口)。这样在后期拓展中很可能加一种类型,就会多出其他维度N-1个类,正如下面引用所说的一样。其实在日常使用中,我们已经经常使用这种模式,比如创建三种大小的毛笔,同时每种毛笔有五种颜色,这时我们肯定不会新建15个毛笔类,会将颜色聚合到毛笔当中作为属性。这个的难点在于我们往往不能很清楚的将多种属性分离,让他们独立开。
引用 : http://www.cnblogs.com/rush/archive/2011/06/29/2093743.html
当一种抽象类型可能有多种实现方式时,一般情况我们可以考虑使用继承来解决抽象类型的多种实现,在抽象类型中定义接口,而子类负责接口的具体实现。但这种做法缺乏灵活性,由于抽象类型和子类之间紧紧地绑定在一起,使得这种关系在运行时不能再修改,这使得它难以修改、扩展和重用不利于抽象和实现解耦,而且这也违背OOP原则:“优先使用对象聚集,而不是继承”。
结合例子再看概念,就发现绘图程序就是这里图形的具体实现。图形和绘图程序会同时拓展的。
3.工厂
简单工厂、工厂方法模式、抽象工厂
简单工厂——传入产品类型,将你想要的产品构造出来。通常内部有大的switch来判断,也常常会构造成一个单例。不易拓展产品种类。
工厂方法模式——一般的工厂模式,会有一个工厂和产品的接口,可以方便的增加产品种类,也很方便的动态在运行时决定使用哪种类型的产品,利用反射。
抽象工厂——用来生产不同产品族的全部产品,其中产品种类是不能改变的,因为改变它需要修改所有的工厂实现子类。但是定义哪个为产品种类,哪个为产品族可依据实际情况去选择。比如下图中,保证三个控件是不变的,变化的始终是风格类型。
4.生成器模式
用于大量构建相似的复杂对象,尤其是内部组件装配顺序不同,就会有差异的产品。此外对于多部件创建复杂(需要封装的)的产品也可以采用这个模式。
||或者具有多种固定属性(力量、敏捷、智力)同时还有由固定属性决定的表现属性(攻击、防御)的,或者拥有两种特质的比如怪物同时有普通怪、boss怪,同时他们都还有类别之分巨人、弓手等。||
下面的类图可以清晰的看到,Director将Builder的装配步骤和client隔离开。Builder的每一个builderPart返回的可以是对象本身,这样可以形成链式调用,在java中写十分爽,在OC中没什么卵用。
5.外观模式
显而易见,这是一个访问子系统的manager,我们经常使用这个,就是常说的统一入口或加一层封装(=。=)。但是需要注意的是,这个对于子系统的内部实现方式的修改是很方便的,但是一旦要增加新的外部接口,可能会要求修改外观类,违反开闭原则。所以我认为它是不适应子系统接口变化的,只适用于子系统实现变化。
6.组合模式
组合模式使用场景:
- 想获得对象抽象的树形结构(装饰器模式运用了组合模式)
- 想让客户端统一处理组合结构中的所有对象(比如遍历)
很明显,当一个图画由折线和点构成,我们可以使用树形链式结构保存它。
对于组合模式,有两个品种:
透明方式:
在Component中声明所有用来管理子对象的方法,如Add()方法,Remove()方法及GetChild()方法,所有实现Component接口的子类都具备这些方法,这使得Component和子类具备一致的行为接口,使得对客户端无需区别树叶和树枝对象。
大家可以回忆一下代理模式(Proxy)中,Proxy,RealSubject类和Subject接口具备一致的行为接口,从而使得被代理者对于客户端是透明的。
正由于我们的Composite和Leaf都具备一致的接口行为,但我们知道Leaf不应该具有Add(),Remove()及GetChild()方法,因为我们叶子节点不能再添加和移除节点了。
安全模式:
在透明模式基础上把Component中声明所有用来管理子对象的方法移到Composite中,在Composite实现子对象的管理方法,那么Leaf就没有子对象管理方法,这使得Composite和Leaf的行为接口不一致,所以客户端在调用时要知道树叶和树枝对象存在。
通常组合结构的内部表示不应暴露给客户端,因此组合模式总是和迭代器模式一起使用,以遍历组合对象的每一个项目。
插播一些OC接口的使用
*在接口中,我们可以声明属性。其实可以认为定义了一套get/set方法,但是没有实现的。如果这个接口的实现要使用它,需要在.m中来一个@synthesize。同时如果不需要使用到这个属性,最好来个@dynamic来忽略它(增强可读性)。
*重新声明在子类中重载的属性和方法是好习惯。
7.迭代器
迭代器提供了一种顺序访问聚合对象中元素的方法,而无需暴露结构的底层表示和细节。榴莲书 中描述了外部和内部迭代器区分是在客户端维护迭代器还是在集合对象内部维护,我认为最好是在外部维护,降低耦合性还可以独立变化。
外部迭代器 | 内部迭代器 |
---|---|
客户端需要知道外部迭代器才能使用,但提供了更多的控制 | 客户端无需知道任何外部迭代器,通过集合对象的特殊接口,一次访问一个元素或向全体发消息 |
客户端创建和维护外部迭代器 | 集合对象本身创建并维护外部迭代器 |
客户端可以使用不同外部迭代器实现多种类型的遍历 | 客户端可以在不修改客户端代码的情况下,选择不同的外部迭代器 |
看了书中给的内部迭代器的例子,可以传入一个block来对每一个节点操作,控制遍历的进行&终止等,不影响拓展。所以内部维护的迭代器也很好用,OC中常用集合对象都有内部迭代器。但java中多是取得iterator,自己while遍历的。
8.访问者
访问者模式分为两个角色,访问者和它访问的元素。元素可以是任何对象,但通常是“部分-整体”结构中的节点,见组合模式(容器性质)。访问者知道复杂结构中每个元素,可以访问每个元素的节点,并根据元素的特征、属性或操作执行任何操作。
注意
复杂结构可以包含很多其他对象,而且它们有不同的接口,对这些对象实施一些依赖于其具体类型的操作
需要对一个组合结构中的对象进行很多不相干的操作,但是不想让这些操作“污染”这些对象的类。将操作集中起来,放在访问者当中
复杂结构很少修改,但是经常定义新的操作
一个想法,很多时候网络数据流是不会变的(网络请求-增加-提交),但是会有很多业务类型会读取(访问)这个数据流,将其结果写到操作数据流当中去,是不是意味着我们可以将业务类型开放在一个访问者当中,不影响主流程。
比如订票的流程,整体大的框架是不变的(填写乘机人-填写联系人-填写邮寄地址-点击生单),但是会有很多新增的儿童购买成人票加入进来。
常见场景,发现新增操作会污染类,我们会选择将这个方法封装一下,使用一个XXXHandler去做这个事情,这时候XXXHandler就是访问者,同时XXXHandler不应该作为成员变量持有,而是作为一个方法的参数传入,再度解耦,这一步是比较容易忽视的一点。
9.装饰
当我们想要不影响其他对象,用动态、透明的方式为一个对象添加职责(不增加他对外暴露的方法)的时候,我们可以使用这个方法。观察类图,这里使用了类似builder的方法,每次处理完之后还是自己,可以动态配置,透明调用。在OC中,分类可以替代这个Decorator的继承,这个方法被榴莲书叫做范畴。它实现少量的装饰器的时候,比真正的子类方式更为轻量、更为容易。
10.责任链
使用场景:
有多个对象可以处理请求,而处理程序只有在运行时才能确定
向一组对象发出请求,而不想显示的指定处理请求的特定处理程序
对对象进行一系列的处理操作,是一种顺延式的处理。这个节点处理不了,就往下传递。
11.模板方法
抽象出共同的部分——模板方法(比如:把大象关冰箱里 打开冰箱门—把大象塞进去—关上冰箱门调用的顺序),然后在子类中实现或重写三个步骤方法的具体内容。一般模板方法只定义了调用顺序,是不可以修改的,必要时也可以重写模板方法。模板方法是父类调用子类的操作方法,形成一个控制结构流程倒转。
在iOS开发当中,ViewController的生命周期函数就是模板方法,我们重写了生命周期的各个函数,然后有一个不可见的模板方法按顺序调用了这些生命周期函数。
使用场景
需要一次性实现算法的不可变部分,并将可变的行为留给子类来实现
子类的共同行为应该被提取出来放到公共类中,避免代码重复。
需要控制子类的拓展。可以定义一个在特定点调用“钩子”操作的模板方法。子类可以通过对钩子操作的实现在这些点拓展功能。(钩子方法就是在某些操作步骤间预留的操作,可以调用以供拓展使用,一般为空。钩子在这里应该是作弊的代名词)
注意 : 可以是用抛出异常的方式,强制子类重写父类定义的步骤方法(打开冰箱门等)。其中钩子方法不必这么做,其实生命周期中的很多方法都是钩子方法,比如drawRect
、shouldAutorotateToInterfaceOrientation
等本身都是空方法。