序言
最近在看设计模式方面的资料,写这篇,一方面是为了做笔记,整理归纳,方便后续查看。二也是为了方便有同样需求的小伙伴查阅,省去网上搜索相关资料的时间,大部分都是借鉴参考其他人的,只是做了整理,总结,如有不喜勿喷。如有错误,欢迎指正。
先来一张设计模型图
摘自百度百科的介绍
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
接下来我们分别详细介绍创建型模式
,行为型模式
, 结构型模式
一 创建型模式
摘自百度百科的定义 创建型模式
1.在软件工程中,
创建型模式
是处理对象
创建的设计模式
,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
2.创建型模式由两个主导思想构成。一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合的方式。
3.创建型模式旨在将系统与它的对象创建、结合、表示的方式分离。这些设计模式在对象创建的类型、主体、方式、时间等方面提高了系统的灵活性。
目录
- 抽象工厂模式,提供一个创建相关或依赖对象的接口,而不指定对象的具体类。
- 工厂方法模式,允许一个类的实例化推迟到子类中进行。
- 原型模式,使用原型实例指定要创建的对象类型,通过复制原型创建新的对象。
- 单例模式,保证一个类只有一个实例,并且提供对这个实例的全局访问方式。
- 生成器模式,将一个复杂对象的创建与它的表示分离,使同样的创建过程可以创建不同的表示。
- 延迟初始化模式,将对象的创建,某个值的计算,或者其他代价较高的过程推迟到它第一次需要时进行。
- 对象池模式,通过回收不再使用的对象,避免创建和销毁对象时代价高昂的获取和释放资源的过程。
1.1 抽象工厂模式
摘自百度百科的定义 抽象工厂模式
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
在上面抽象工厂模式的定义中涉及到了一个名词:产品族。他的意思是有多类产品,每一类产品中又分多种相似的产品。下面是百度百科的解释:产品族
是指位于不同产品等级结构中,功能相关联的产品组成的家族。一般是位于不同的等级结构中的相同位置上。显然,每一个产品族中含有产品的数目,与产品等级结构的数目是相等的,形成一个二维的坐标系,水平坐标是产品等级结构,纵坐标是产品族。叫做相图。
实例
两种抽象产品:水果、蔬菜
四种具体产品:北方水果,南方水果,北方蔬菜,南方蔬菜
1.我们创建5个基类,分别为 Fruit(水果),Vegetable(蔬菜),FruitVegetable(水果蔬菜),NorthFruitVegetable(北方水果蔬菜),SouthFruitVegetable(南方水果蔬菜)。
2.其中 Fruit,Vegetable,FruitVegetable分别继承 NSObject,NorthFruitVegetable和SouthFruitVegetable继承自FruitVegetable。
更多同类型文章可以查看 iOS设计模式(4)抽象工厂模式
1.2 建造者模式
摘自百度百科的定义 建造者模式
建造者模式是设计模式的一种,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
实用范围
- 1 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 2 当构造过程必须允许被构造的对象有不同表示时。
角色
- 1 builder:为创建一个产品对象的各个部件指定抽象接口。
- 2 ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并 提供一个检索产品的接口。
- 3 Director:构造一个使用Builder接口的对象。
- 4 Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
更多同类型文章可以查看 iOS设计模式(7)建造者模式
1.3 工厂方法模式
摘自百度百科的定义 工厂方法模式
工厂方法模式(FACTORY METHOD)是一种常用的类创建型设计模式,此模式的核心精神是封装类中变化的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂;具体工厂;抽象产品;具体产品
简单理解
- 1 在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。
- 2 如何隔离出这个易变对象的变化,使得系统中“其它依赖该对象的对象”不随着需求的改变而改变,这就是本章要说的Factory Method模式了。
角色结构
抽象工厂(Creator)角色:
是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。具体工厂(Concrete Creator)角色:
这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。在上图中有两个这样的角色:BulbCreator与TubeCreator。抽象产品(Product)角色:
工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。在上图中,这个角色是Light。具体产品(Concrete Product)角色:
这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。
1.工厂方法模式是
简单工厂模式
的衍生,解决了许多简单工厂模式的问题。
2.工厂方法模式对简单工厂模式进行了抽象。
3.工厂方法模式(Factory Method pattern)是最典型的模板方法模式(Template Method pattern)应用。
何时使用工厂方法模式
- 编译时无法准确预期要创建的对象的类;
- 类想让其子类决定在运行时创建什么;
- 类有若干辅助类为其子类,而你想将返回哪个子类这一信息局部化。
1.4 原型模式
摘自百度百科的定义 原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
工作原理
通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建
解决问题
它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口
理解
原型模式是非常简单的一种设计模式, 在多数情况下可被理解为一种深复制的行为。在Objective-C中使用原型模式, 首先要遵循NSCoping协议
(OC中一些内置类遵循该协议, 例如NSArray, NSMutableArray等)。
总结下原型模式的使用情况:
- 需要创建的对象应独立于其类型与创建方式。也就是说我们想要的对象并不能够直接通过初始化函数来创建出来,其创建过程不具有普遍性且复杂。
- 要实例化类是在运行时决定的。在编写代码的时候并不知道哪种对象会被创建出来,其内部的结构如何复杂(例如:复杂程度取决于用户的操作)。
- 不想要与产品层次相对应的工厂层次。不通过工厂方法或者抽象工厂来控制产品的创建过程,想要直接复制对象
- 不同类的实例间的差异仅是状态的若干组合。因此复制相应数量的原型比手工实例化更加方便。
- 类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易。如果内部结构复杂,不容易重现。
常见的使用场景
结构复杂的类型, 例如《编程之道》中提及的Mark类型; 或者是相似类, 结构一致, 但是属性不同.
更多同类型文章可以查看 iOS 原型模式
1.5 单例模式
摘自百度百科的定义 单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例
单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
单例模式的优缺点
优点
- 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
- 因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点
不能被继承,不能有子类
不易被重写或扩展(可以使用分类)
同时,由于单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源;
实例代码 - 手写单例
// 实现copy协议
@interface SignalModel()
@end
+ (instancetype)shareInstance {
static SignalModel *_instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[super allocWithZone:NULL] init];
});
return _instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [self shareInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return self;
}
更多同类型文章可以查看 iOS 单例模式
二 结构型模式
摘自百度百科的定义 结构型模式
在软件工程中结构型模式是设计模式,借由一以贯之的方式来了解元件间的关系,以简化设计。
- 目录
- 适配器模式:将一个物件的界面'转接'成当事人预期的样子。
- 翻新界面模式: 同时使用多个类别的界面的适配器。
- 适配器导管:因除错目的而使用多个适配器。
- 聚集模式:一种组合模式的版本,包含用于聚集子成员的成员函式。
- 桥接模式:将一个抽象与实现解耦,以便两者可以独立的变化。
- 墓碑模式:一种中介的查询物件,包含物件的实际位址。
- 组合模式:树状结构的物件,每个物件有相同的界面
- 修饰模式:对一个执行的类别,若使用继承方式加上新功能可能会新类别的数量呈指数型地增加,可使用此模式来解决。
- 扩充模式:亦即框架,将复杂的程式码隐藏在简单的界面后
- 外观模式:对于已有的界面建立一个简化的界面以简化使用共通任务。
- 享元模式:通过共享以便有效的支持大量小颗粒对象。
- 代理模式:为其他对象提供一个代理以控制对这个对象的访问。
- 导线及过滤器模式:一串的处理者,其中每个处理者的输出是下一个的输入
- 私有类别资料模式:限制存取者/修改者的存取
2.1 适配器模式
摘自百度百科的定义 适配器模式
在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
理解
将一个类的接口转换成另外一个客户希望的接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
通常,适配器模式有两种实现方式:类适配器,对象适配器。
类适配器
类适配器主要通过继承来适配两个接口,在Objective-C中,多重继承可以通过实现协议,同时又继承基类来实现。要在OC中实现类适配器,首先要有客户端要使用的一套行为的协议,然后要用具体的适配器类来实现该协议,同时适配器类也要继承被适配者,如下图所示
由于类适配器模式采用多继承方式实现,在一定程度上增加了程序的耦合性,而且OC语言本身并不支持多重继承,只能通过实现协议并继承父类来实现。因此,在OC中较常用的还是下一种:对象适配器模式。
对象适配器
对象适配器不继承被适配的类,而是组合了一个对被适配的类的引用,如图所示。
iOS中的委托(Delegate)属于对象适配器。
使用适配器模式的情形
- 现有类的接口与需求不匹配;
- 想要复用一个类,该类能够同其他可能带有不兼容接口的类协作;
- 需要适配一个类的几个不同子类,可是让每个子类去子类化一个类适配器又不现实,此时可以使用对象适配器(委托)来适配其父类接口。
更多同类型文章可以查看 iOS学习笔记(6)——适配器(Adapter)模式
2.2 桥接模式
摘自百度百科的定义 桥接模式
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interfce)模式。
桥梁模式所涉及的角色有
-
抽象化(Abstraction)角色
:抽象化给出的定义,并保存一个对实现化对象的引用。 -
修正抽象化(Refined Abstraction)角色
:扩展抽象化角色,改变和修正父类对抽象化的定义。 -
实现化(Implementor)角色
:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。 -
具体实现化(Concrete Implementor)角色
:这个角色给出实现化角色接口的具体实现
总结
桥接模式的优点:
- 桥接模式使用聚合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
- 提高了系统的可扩展性,可以独立地对抽象部分和实现部分进行扩展。
- 可减少子类的个数
桥接模式的缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
桥接模式使用场景
- 不想在抽象与其实现之间形成固定的绑定关系;
- 抽象及其实现都应可以通过子类化独立进行扩展;
- 对抽象的实现进行修改不应影响客户端代码;
- 如果每个实现需要额外的子类以细化抽象,则说明有必要把它们分成两个部分;
- 想在带有不同抽象接口的多个对象之间共享一个实现。
更多同类型文章可以查看IOS设计模式浅析之桥接模式(Bridge)
2.3 组合模式
摘自百度百科的定义 组合模式
组合模式 将对象组合成树形结构以表示
部分-整体
的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚部分/整体
还有单个对象
与组合对象
的含义。
合成对象是模型-视图-控制器聚集模式的一部分。
组合模式最大的优点是他的节点可以自由增加,且调用节点方便。
标准组成
- Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
- Composite:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
- Leaf:在组合中表示叶子结点对象,叶子结点没有子结点
适用性
- 你想表示对象的部分-整体层次结构
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
常见例子
- UIView 典型的树形结构
- 遍历文件夹目录操作
总结:组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以像处理简单元素一样来处理复杂元素。(
使得用户对单个对象和组合对象的使用具有一致性
)
更多同类型文章可以查看 IOS设计模式-组合设计模式
2.4 装饰模式
摘自百度百科的定义 装饰模式
修饰模式,是
面向对象编程
领域中,一种动态地往一个类中添加新的行为的设计模式
。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。
通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。
这种模式动态地将额外的责任附加到一个对象上。在进行功能扩展时,装饰是子类化之外的一种灵活的备选方法。和子类化一样,采纳装饰模式可以加入新的行为,而又不必修改已有的代码。装饰将需要扩展的类的对象进行包装,实现与该对象相同的接口,并在将任务传递给被包装对象之前或之后加入自己的行为。装饰模式表达了这样的设计原则:类应该接纳扩展,但避免修改。
修饰模式是类继承的另外一种选择。
类继承在编译时候增加行为,而装饰模式是在运行时增加行为
。
何时使用装饰模式
- 想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 想要扩展一个类的行为,却做不到。类定义可能被隐藏,无法进行子类化;或者对类的每个行为的扩展,为支持每种功能组合,将产生大量的子类
- 对类的职责的扩展是可选的。
装饰模式在iOS中的实现
根据Objective-C的特性,有两种实现方式:
- 通过真正的子类实现装饰
- 通过分类实现装饰
解释
- 1 用类别实现可以达到装饰模式的效果,而且会更简单。类别是Objective-C的特性,它可以添加类的行为,而不用进行子类化,通过类别添加的方法不会影响类原来的方法,类别也成为类的一部分,并可由其子类继承。
- 2 虽然通过类别可以实现装饰模式,但是这并不是一种严格的实现,由类别添加的方法是编译时绑定的,而装饰模式是动态绑定的,另外类别也没有封装被扩展类的实例。类别适合装饰器不多的时候,上面的例子只有一个NetData装饰器,用类别实现会更轻量,更容易。
更多同类型文章可以查看 Objective-C中的装饰模式
2.5 外观模式
摘自百度百科的定义 外观模式
这种模式为子系统中的一组接口提供统一的接口。表观模式定义一个更高级别的接口,通过减少复杂度和隐藏子系统之间的通讯和依赖性,使子系统更加易于使用。
角色
-
Facade(外观类)
:整合子系统中的接口,客户端可以调用这个类的方法。 -
Clients(客户端)
:通过外观类提供的接口和各个子系统的接口进行交互。
适用场景
- 设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式。
- 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口。
- 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互
优点
- 实现了子系统与客户端之间的
松耦合
关系。 - 客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
更多同类型文章可以查看 iOS设计模式(8)外观模式
2.6 享元模式
摘自百度百科的定义 享元模式
享元模式(英语:Flyweight Pattern)是一种软件设计模式。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元
理解:运用共享技术有效地支持大量细粒度的对象。
使用场景
如果一个应用程序使用了大量的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式。
例子:我们在一个界面上生成100000个花,共有5种花,实现方式如下。
- 修改之前代码
// 花种类
typedef NS_ENUM (NSUInteger,FlowerType) {
FlowerPeachBlossomType = 0, // 桃花
FlowerClubsType = 1, // 梅花
FlowerPenoyType = 2, // 牡丹
FlowerCloveType = 3, // 丁香
FlowerCuckooType = 4, // 杜鹃
FloweJasmineType = 5, // 茉莉花
};
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self drawFlower];
}
- (void)drawFlower {
// 使用普通模式
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//新建对象
UIImageView *imageview = [self flowerViewWithType:flowerType];
imageview.frame = area;
[self.view addSubview:imageview];
}
}
}
- (UIImageView *)flowerViewWithType:(FlowerType)type {
UIImageView *flowerView = nil;
UIImage *flowerImage;
switch (type) {
case FlowerPeachBlossomType:
flowerImage = [UIImage imageNamed:@"peachBloss"];
break;
case FlowerClubsType:
flowerImage = [UIImage imageNamed:@"clubs"];
break;
case FlowerPenoyType:
flowerImage = [UIImage imageNamed:@"penoy"];
break;
case FlowerCloveType:
flowerImage = [UIImage imageNamed:@"clove"];
break;
case FlowerCuckooType:
flowerImage = [UIImage imageNamed:@"cuckoo"];
break;
case FloweJasmineType:
flowerImage = [UIImage imageNamed:@"jasmine"];
break;
default:
break;
}
flowerView = [[UIImageView alloc]initWithImage:flowerImage];
return flowerView;
}
每次新建一个FlowerView添加到View视图上,当生成大量的对象的时候,内存暴增。
代码上看,其实只有6种花放在不同的位置而已,那我们可以利用享元模式的思想,复用这6种花,然后绘制到不同位置,而不是增加对象添加到视图上。
我们采用享元模式进行修改
- FlowerView类
@interface FlowerView : UIImageView
@end
@implementation FlowerView
- (void)drawRect:(CGRect)rect {
[self.image drawInRect:rect];
}
@end
- FlowerFactory类
// FlowerFactory.h文件
#define kTotalNumberOfFlowerTypes 6
// 花种类
typedef NS_ENUM (NSUInteger,FlowerType) {
FlowerPeachBlossomType = 0, // 桃花
FlowerClubsType = 1, // 梅花
FlowerPenoyType = 2, // 牡丹
FlowerCloveType = 3, // 丁香
FlowerCuckooType = 4, // 杜鹃
FloweJasmineType = 5, // 茉莉花
};
NS_ASSUME_NONNULL_BEGIN
/**
花建造者
*/
@interface FlowerFactory : NSObject
/// 生成花视图
- (UIImageView *)flowerViewWithType:(FlowerType)type;
@end
----------------------------------------------
// FlowerFactory.m文件
@interface FlowerFactory()
/** flowerPool */
@property(nonatomic, strong)NSMutableDictionary *flowerPool;
@end
@implementation FlowerFactory
- (UIImageView *)flowerViewWithType:(FlowerType)type {
UIImageView *flowerView = [self.flowerPool objectForKey:[NSNumber numberWithInt:type]];
if (flowerView == nil) {
UIImage *flowerImage;
switch (type) {
case FlowerPeachBlossomType:
flowerImage = [UIImage imageNamed:@"peachBloss"];
break;
case FlowerClubsType:
flowerImage = [UIImage imageNamed:@"clubs"];
break;
case FlowerPenoyType:
flowerImage = [UIImage imageNamed:@"penoy"];
break;
case FlowerCloveType:
flowerImage = [UIImage imageNamed:@"clove"];
break;
case FlowerCuckooType:
flowerImage = [UIImage imageNamed:@"cuckoo"];
break;
case FloweJasmineType:
flowerImage = [UIImage imageNamed:@"jasmine"];
break;
default:
break;
}
flowerView = [[FlowerView alloc] initWithImage:flowerImage];
[self.flowerPool setObject:flowerView forKey:[NSNumber numberWithInt:type]];
}
return flowerView;
}
#pragma mark - lazy
- (NSMutableDictionary *)flowerPool {
if (_flowerPool == nil) {
_flowerPool = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
}
return _flowerPool;
}
@end
- FlyweightView类
@interface FlyweightView : UIView
@property (nonatomic, retain) NSArray *flowerList;
@end
@implementation FlyweightView
- (void)drawRect:(CGRect)rect {
for (NSDictionary *dic in self.flowerList) {
NSValue *key = (NSValue *)[dic allKeys][0];
FlowerView *flowerView = (FlowerView *)[dic allValues][0];
CGRect area = [key CGRectValue];
[flowerView drawRect:area];
}
}
@end
- 使用享元模式创建花
- (void)drawFlowerFlyweightPattern {
// 使用享元模式
FlowerFactory *factory = [[FlowerFactory alloc] init];
NSMutableArray *flowerList = [[NSMutableArray alloc] initWithCapacity:500];
for (int i = 0; i < 100000; ++i) {
@autoreleasepool {
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//重复利用对象
UIImageView *flowerView = [factory flowerViewWithType:flowerType];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
//新建对象
NSValue *key = [NSValue valueWithCGRect:area];
//新建对象
NSDictionary *dic = [NSDictionary dictionaryWithObject:flowerView forKey:key];
[flowerList addObject:dic];
}
}
FlyweightView *view = [[FlyweightView alloc]initWithFrame:self.view.bounds];
view.flowerList = flowerList;
self.view = view;
}
运行结果
可以看到内存已经降下来了,我们只是生成了对象flowerView,但是并没有add到FlyweightView上,[self.image drawInRect:rect];
使用image重新绘制了一个新的位置去显示。
项目连接地址 - FlyweightPatternDemo
2.7 代理模式
摘自百度百科的定义 代理模式
代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网上连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。
总结:为其他对象提供一种代理以控制对这个对象的访问
图中涉及的角色如下所示:
-
协议:
定义代理和委托的共同接口(方法) -
委托:
根据指定的协议,委托代理去完成实现指定接口(方法) -
代理:
根据指定的协议,实现委托需要实现的接口(方法)
优点
-
职责清晰:
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。 - 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
- 高扩展性
OC中常用的代理
-
UITableViewDataSource
,UITableViewDelegate
,UIScrollViewDelegate
等
三 行为型模式
摘自百度百科的定义 行为型模式
在软件工程中, 行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。
- 目录
- 职责链模式:处理命令物件或将之传到下一个可以处理的物件。
- 命令模式:命令物件将动作及参数封装起来。
具现化堆叠
:使用堆叠将递回函式转成重复执行。解释器模式
:实作特制化的编程语言以解决一系列特殊的问题。- 迭代器模式:迭代器用于存取包含器中元素而不用透露底层实作的方式。
- 调停者模式:对子系统中的界面集面提供一个统一的界面。
- 备忘录模式:使一个物件还原到前一个状态的能力(rollback)。
空物件模式
:扮演预设物件的角色。- 观察者模式:亦即发行/订阅或事件聆听者。物件注册去聆听由另一个物作所引发的事件。
弱参照模式
:将观察者与可观察间的藕合程度。- 协议栈:通讯是由许多封装成阶层式的层所处理。
- 状态模式:在执行可以部分改变物件的一种方法。
- 策略模式:在执行时依需求而选择算法。
规格模式
:以布林值的方式来重组事务逻辑。- 模板方法模式:描述一个程式的骨架。
- 访问者模式: 一种从物件中取出算法的方式。
单服务访问者模式
:最佳化访问者。使用一次后即行删除。阶层式访问者模式
:提供一种方式可以拜访阶层式数据结构如树中的每一个节点 。排程任务模式
:在特定区时或时间点执行排程任务(用于即时计算)。
3.1 职责链模式
摘自百度百科的定义 职责链模式
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间发生耦合。此模式将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
OC中事件响应链就是典型的责任链模式
责任链模式涉及到的角色
-
抽象处理者(Handler)角色
:定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。 -
具体处理者(ConcreteHandler)角色
:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家
何时使用责任链模式
- 有多个对象可以处理请求,而处理程序只有在运行时才能确定。
- 向一组对象发出请求,而不想显示指定处理请求的特定处理程序。
在游戏开发中经常使用到该设计模式,比如防御道具抵御伤害
更多同类型文章参考 iOS设计模式--责任链模式
3.2 命令模式
摘自百度百科的定义 命令模式
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。请求对象将一或多个动作绑定在特定的接收者上。命令模式将发出请求的对象和接收及执行请求的对象区分开来。
模式结构
-
Command:
定义命令的接口,声明执行的方法。 -
ConcreteCommand:
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 -
Receiver:
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 -
Invoker:
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。 -
Client:
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
模式分析
- 1.命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
- 2.每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
- 3.命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
- 4.命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
- 5.命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
模式优点
- 1.降低对象之间的耦合度。
- 2.新的命令可以很容易地加入到系统中。
- 3.可以比较容易地设计一个组合命令。
- 4.调用同一方法实现不同的功能。
模式缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
适用环境
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
本文持续更新中,更多内容,敬请期待。
3.3 解释器模式
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
3.4 迭代器模式
摘自百度百科的定义 迭代器模式
它可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现。其意图是提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
参与者
-
Iterator(迭代器)
迭代器定义访问和遍历元素的接口 -
ConcreteIterator (具体迭代器)
具体迭代器实现迭代器接口,对该聚合遍历时跟踪当前位置 -
Aggregate (聚合)
聚合定义创建相应迭代器对象的接口 -
ConcreteAggregate (具体聚合)
具体聚合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例
效果
- 它支持以不同的方式遍历一个聚合
- 迭代器简化了聚合的接口
- 在同一个聚合上可以有多个遍历
在Cocoa Touch框架中使用迭代器模式
- 苹果公司用自己命名规则“枚举器/枚举”改写了迭代器模式,用于相关基础类的各种方法。也就是说枚举就是苹果版本的迭代。
- 基础框架中的NSEnumerator类实现了迭代器模式。抽象NSEnumerator类的私有具体子类返回枚举器对象,能够顺序遍历各种几何--数组、集(set)、字典(值与键),把集合的对象返回给客户端。
- NSEnumerator可以枚举NSArray/NSDictionary和NSSet对象中的元素。它本身是个抽象类。它依靠几个工厂方法,如objectEnumerator或keyEnumerator,来创建并返回相应的具体枚举器对象。
- 从iOS4开始,有了另一种枚举Cocoa Touch框架中集合对象的方法,叫做基于块的枚举。
- 快速枚举,这个是苹果推荐的枚举方法。它允许把集合对象的枚举直接用作for循环的一部分,无需使用其他枚举器对象,而且比传统的基于索引的for循环效率更高,形如:
NSArray *arr = @[@"zhangsan",@"lisi",@"wangwu"];
for(NSString *item in arr) {
NSLog(@"%@",item);
}
总结
迭代器,其实就是集合的遍历方法,通常用循环,递归来实现,在创建一个集合类的时候都需要一个迭代器
实例代码
/// 迭代器模式
- (void)iterator {
//1.数组迭代器
NSArray *array = [NSArray arrayWithObjects:@1, @2, @3, @4, @5, nil];
// 获取数组的正序迭代器
NSEnumerator *enu1 = [array objectEnumerator];
// 获取数组的反序迭代器
NSEnumerator *enu2 = [array reverseObjectEnumerator];
// 遍历数组
id obj = nil;
// 正序,获取下一个需要遍历的元素
while (obj = [enu1 nextObject]) {
NSLog(@"%@", obj);
}
// 反序,获取下一个需要遍历的元素
while (obj = [enu2 nextObject]) {
NSLog(@"%@", obj);
}
}
相关文章参考 迭代器模式
3.5 中介者模式
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式包含以下主要角色。
抽象中介者(Mediator)角色:
它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。具体中介者(ConcreteMediator)角色:
实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。抽象同事类(Colleague)角色:
定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。具体同事类(Concrete Colleague)角色:
是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
中介者模式的结构图
或许看这幅图会比较难理解,借用网上的例图
使用中介之前如下图
使用中介之后如下图
中介者模式很好的处理了业务中组件化方案的强耦合的问题,我们iOS当中组件化的实现都是基于中介者的模式的。其中的Mediator起到至关重要的作用,Mediator就是我们封装的组件化的框架。
更多同类型文章参考 中介者模式(详解版)
3.6 备忘录模式
摘自百度百科的定义 备忘录模式
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
涉及角色:
-
Originator(发起人):
负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。 -
Memento(备忘录):
负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。 -
Caretaker(管理者):
负责备忘录Memento,不能对Memento的内容进行访问或者操作。
模式使用场景
- 1 论什么时候你想存储和恢复对象的状态,都可以考虑使用备忘录模式。即需要保存和恢复数据的相关状态场景。
- 2 提供一个可回滚的操作,能够回到之前的状态。比如:ID浏览器中的后退按钮,文件管理器上的backspac等。
- 3 用于获取状态的接口会暴露实现接口,需要将其屏蔽起来。
模式优点
- 状态恢复机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用存储起来的备忘录恢复状态
模式缺点
- 资源消耗过大,特别是当保存对象的成员太多,那么久不可避免的需要占用大量的存储空间,每保存一次对象都需要消耗一定的资源。
在iOS常用的实现备忘录模式的模块有归档
、序列化
、CoreData
等。
3.7 观察者模式
摘自百度百科的定义 观察者模式
观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
理解
观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
在iOS常用的观察者模式的模块有通知
、KVO
等。
3.8 状态模式
摘自百度百科的定义 状态模式
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类
状态模式包含以下主要角色。
-
环境(Context)角色:
也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。 -
抽象状态(State)角色:
定义一个接口,用以封装环境对象中的特定状态所对应的行为。 -
具体状态(Concrete State)角色:
实现抽象状态所对应的行为。
状态模式的优点:
- 状态模式允许一个对象基于内部状态有不同的行为,将行为委托给状态对象执行,状态转化可以由Context也可以由状态行为控制,比较灵活;
状态模式的缺点:
- 状态模式的使用必然会增加系统类和对象的个数。状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
更多同类型文章参考 iOS开发-状态模式
3.9 策略模式
摘自百度百科的定义 策略模式
定义了一组算法(业务规则),封装了每个算法,这族的算法可互换代替(interchangeable)。
理解
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
应用场景:
- 1 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
- 2 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
- 3 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
优点
1、 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
2、 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
3、 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
2、 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量
更多同类型文章参考 iOS设计模式(5)策略模式
3.10 模板方法模式
摘自百度百科的定义 模板方法模式
模板方法模式 定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。
简单理解
定义一个父类,有父类定义接口规范,然后不同的行为在子类中实现。这样一方面提高了代码的复用性,另一方面还可以利用面向对象的多态性,在运行时选择一种具体子类。模板模式是一种基于继承的代码复用技术,是一种类行为型模式。模板模式应该是最简单的一种设计模式,只存在继承关系,代码也相对简单。
更多同类型文章参考 iOS设计模式(6)模板模式
3.11 访问者模式
摘自百度百科的定义 访问者模式
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
涉及角色
-
Visitor 抽象访问者角色
为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。 -
ConcreteVisitor
具体访问者角色,实现Visitor声明的接口。 -
Element
定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。 -
ConcreteElement
具体元素,实现了抽象元素(Element)所定义的接受操作接口。 -
ObjectStructure
结构对象角色,这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)
何时使用访问者模式
- 一个对象结构包含很多其他对象,它们有不同的接口,但是想对这些对象实施一些依赖其具体类型的操作。
- 需要对一个对象结构中的对象进行很多不相关的操作,但是不想让这些操作“污染”这些对象的类。可以将这些操作定义在访问者类中,在需要的时候通过访问者找到对象结构中的具体对象并实施操作。
- 定义的对象结构的类很少改变,但经常需要在这些结构上添加新的操作。
访问者模式优点
- 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由扩展。给对象结构中的元素增加新操作,不必修改整个对象结构,只需实现一个具体访问者角色就可以。
访问者模式缺点
增加新的数据结构很困难,访问者和目标类耦合在一起,如果需要在对象结构中添加新的元素类,那么访问者的父类和子类都要修改。访问者模式适合对象结构类改变较少的情况。
访问者角色要执行与元素角色相关操作,就必须让元素角色将内部属性暴露出来,这就意味着其它对象也可访问,这破坏了元素角色的封装性。
在ios中使用访问者模式
在Cocoa Touch框架中使用Core Graphics绘制一些简单的形状的视图,如圆形、矩形、线条等,根据需要在这些形状上可能还需要填充颜色,描边,添加阴影、渐变等效果。我们可以将形状的数据结构和绘制形状的操作分离。绘制形状的数据结构很少会改变,但是以后可能会添加更多的操作以绘制出不同的形状。
更多同类型文章参考 Objective-C 设计模式-访问者模式 (Visitor)
写文本的主要目的是为了自己加深对设计模式的理解和认识,俗话说的好,好记性不如烂笔头。另外也帮助需要的小伙伴。很多都是参考借鉴其他人的,不喜勿喷,喜欢的也帮忙点个赞吧。
项目连接地址 - DesignPatternDemo