设计模式对于开发来说是挺重要的一个东西,对于不同的情况和需求选择不同的设计模式来开发,可以让你创造出复用的方法用于解决很多相同的问题。也能让你的代码更加结构清晰容易理解和修改,同时可以降低代码的耦合 。
即使你平时没有对这个东西有太多的注意,但无形中你在平时开发时也肯定已经使用到了很多设计模式。设计模式也会有不断的发展和出新,没有说哪个绝对的好,只能说哪个模式相对来说更加适合于处理哪种问题。另外下面说的模式也不都是同属一个类型,有的是针对结构的,有的是针对行为或者创建的等等。
设计模式的主要目的就是要将代码不变的和变化的部分区分开,尽量使得不变的部分可以复用
1.王者的荣耀之MVC
MVC这个名字对于你肯定再熟悉不过了,这也是苹果最推荐的模式,它也是Cocoa框架的基础。
它将对象分为3个不同的角色,将不同任务代码分拆给不同的角色。
Model: 模型就是用来表示你的程序里的数据,也定义了你可以如何的操作这些数据
View: 视图负责将模型数据内容用视觉效果呈现出来,同时也控制与用户的交互响应。
Controller: 控制器就是一个协调工作的中间人,它可以访问数据模型然后使用视图来展示数据,它也会监听处理交互或模型变化的事件,必要的时候去改变模型数据。

实际情况通常就是:Model通知Controller数据变化了,Controller就去刷新View的显示。当用户做了一些操作后,View也能通知到Controller,Controller就会根据具体的用户操作去更新Model的数据。
看上面的图会可能会想如果把Controller给去掉,把Model的Notify和View的Update连起来,把View的User Actions和Model的Update的连起来这样整个程序不是更简洁类更少了嘛,看起来也直接了很多,效率也更高嘛。

没错,这个想法其实就类似我们写代码可以把所有的逻辑都在一个方法里,这个方法写的又长功能又强大,调用它工作都完成了。可是你想想为什么我们没有这样做,而是分了很多类分了很多方法去实现。很大的原因就是代码复用的问题,另外只有按职责拆分的细了你今后修改代码或者查找bug才更容易定位。所以如果有Controller的存在,我们同一个样式的View可以用来展示不同的Model数据,而如果Controller没了,那么每一个Model都要有自己对应的View了。
当然MVC的问题也已经有很多人说过的,就是越来越臃肿的Controller。因为所有的逻辑都会在Controller里面,如果是一个非常复杂的界面,那么这个Controller就会越来越难以维护。所以才会有了Fat-Model和Thin-Model之争,才会有后来发展出的MVCS、MVVM等等新的模式。不过这些定的模式也并不是有多神奇,本质上都是对MVC的继续细分,将臃肿的Controller或者Fat-Model的一部分任务再独立出来,达到一个减负的目的,这里就当他们属于一类了,不一一去讲,对那些在MVC基础上发展出的新模式有兴趣的小伙伴可以自行baidu或者google一下,会大把介绍的文章。
2.可能被滥用的Singleto Pattern
Singleto Pattern(单例模式)用来保证某个类只会有一个具体的实例对象存在,这个实例对象可供全局访问。
这个单例对象通常使用懒加载的方式创建出来,通过类似叫做shared或者default或者standard的类方法来获取它。
比如苹果官方的一些单例对象:UserDefaults.standard, UIApplication.shared, UIScreen.main, ........
创建一个单例的方法很简单,如果你是objective-C的,那么就用GCD的dispatch_once()方法来保证初始化方法只会被调用一次,这样这个类也就只会有一个实例对象了。
如果你是用Swift的,那么恭喜你,有更简单的方法。因为Swift可以设置方法为private私有的。你就可以给初始化init()方法设置为私有,然后用一个静态变量保存一个实例对象供全局调用。如下面:
class SomeClass {
//用一个静态变量存着这个实例对象
static let shared = SomeClass()
// 将初始化init方法设为私有
private init() {
//初始化代码...
}
}
这样你就可以在任何地方使用SomeClass.shared来访问那个唯一的实例对象了。
不过往往我们都会滥用Singleto Pattern,不是因为我们真的需要它,而只是为了“偷懒”,为了可以很轻松的在程序任何地方访问到那个唯一的实例对象。滥用单例最大问题就是它最突出的特点:即在任何地方任何对象都有权访问和修改这个单例对象,你无法控制,而且你也不知道是谁、是在哪里被改了的。
具体问题和解决方法你可以看看这两篇文章:
《Are Singletons Bad》
《Nuts and Bolts of Dependency Injection in Swift》
3.统一入口的Facade
Facade(外观模式)给一堆复杂的子系统对外统一提供了单一的接口,使用方只要调用统一的方法,方法内部会自动选择性地调用正确的子系统的接口,而不是给使用方一堆子系统的类和接口。一般可以用作将本来需要调用多个子系统方法去完成一个任务给包装起来,使用方只需调用这个包装后的简单而相对稳定的接口即可完成任务调度。

Facade模式分离了使用方的接口和下层系统的实现,可以减少外部和内部的依赖。另一个优点就是如果下层的类或者类的接口发生变化时候,使用Facade模式后,这种变化对使用方调用API没有任何影响。
举一个实际运用的例子,比如在iOS开发时,有个挺多人用的数据缓存持久化开源库,叫做YYCache。我们可以通过一个叫做YYCache的类和方法来进行数据的缓存和读取操作,而实际这个YYCache内部会持有两个实例对象,一个是YYDiskCache,一个是YYMemoryCache。YYDiskCache就是磁盘缓存操作类,YYMemoryCache就是内存缓存操作类。而我们一般使用中就直接使用YYCache这个类和他的方法即可,而它的内部就会去选调用合适的缓存对象和方式。比如说我们调用YYCache对象的objectForKey()方法来根据一个key取出对应的缓存数据,它的内部实现就是先调用YYMemoryCache对象方法,看看内存缓存里是否有这个key的缓存,有就返回,没有就再问YYDiskCache对象磁盘缓存里是否有这个key对应的缓存数据。这里YYMemoryCache和YYDiskCache就像是上面我们描述的不同的子系统,而YYCache就是提供给使用方的一个统一的API接口。
4. 功能组装Decorator
Decorator(装饰者模式)意义就是在不该改变某个类的原本实现的情况下,可以动态的给这个类添加新的方法和功能。这能将很多继承关系变成组合的关系,从而可以减少类的数量和继承复杂度,更加方便扩展和复用。
其实Objective-C中的Category和Swift中的Extension其实就是Decorator模式的一种实现技术,它就能在不侵入原本类实现的情况下给类添加新的方法,不过Category和Extension不能定义属性,除非用runtime的方法。
如果想要实现相对标准的Decorator模式,可以看看简书里的这篇文章的示例,具体代码我就不在这里重复了:《装饰者模式》
总之平时应该多用组合,减少继承。
5.随处可见的Delegation
Delegation(委托模式)在Cocoa中是非常常见的一种模式,平时开发的时候也一定用过UITableViewDelegate,UIScrollViewDelegate。。。。等等代理。
Delegation可以理解为一个对象将它的某一些任务委托给另一个对象(delegate)去做,在合适的时候请求delegate对象去完成某个任务,同时保持对delegate对象的一个引用。它是一对一的关系。这样能增加灵活性,使得使用Delegation的类更加可定制。
Delegation具体如何使用应该就不用多详细介绍了,大家应该已经很熟了,这里单独写出来主要因为它确实很常用,另一个原因是为了介绍一下它跟下面这个Adapter模式的关系。
6.转接插头Adapter
Adapter(适配器模式)能让拥有完全不同接口的不同类一起协同工作,通过将对象封装起来仅暴露出一个标准的接口来进行交互。
与上面提过的Facade(外观模式)不同,Facade意义在于“简化接口”,将访问一堆子系统的一堆接口流程给他统一接口。
Adapter的意义在于“转换接口”,就好像我们现实生活中的电源适配器是转换了电的“接口”,又或者我们用的各种转换线,比如USB转Lightning线就是我们能真实感知的转换接口的工具。
Adapter模式里通常有这样几个成员:
Interface:适配器标准接口,在iOS里一般是一个Protocol协议定义的一些方法
Adapter:适配器对象,它就是用来包装被适配者,一般它会持有对应的被适配者。同时它满足Interface定义的协议,使用方通过调用它实现的协议方法即可达到与被适配者协同工作的效果
Adaptee:被适配者
Client :使用方客户
Client想与不同的Adaptee协同工作的时候,Client与Adaptee都不需做任何改变,只要Client切换使用不同的Adapter即可达到效果。
比如我们模拟一个现实生活的Lightning转USB连接线情况来看看这个Adapter模式的实现:
//两个用来辅助的结构体类型
struct USBData {
var content : String
}
struct LightningData {
var content : String
}
//Adaptee(被适配者)
class USBSocketDevice {
func sendData(data: USBData) {
print("send data to USBSocketDevice: \(data.content)")
}
}
//Interface(定义的统一接口)
protocol LightningProtocol {
func sendData(data: LightningData)
}
//Adapter(适配器)
class LightningToUSBLine : LightningProtocol{
//它一般会持有被适配者
var usbDevice : USBSocketDevice
init(device: USBSocketDevice) {
usbDevice = device
}
//实现interface接口协议做接口转换
func sendData(data: LightningData) {
usbDevice.sendData(data: USBData(content: data.content))
}
}
//Client
class LightningSocketDevice {
var lightningLine : LightningProtocol
init(line : LightningProtocol) {
//Client只需要知道Interface接口即可,外部传入不同的转换线对象就可以实现
lightningLine = line
}
func sendData(data: String) {
lightningLine.sendData(data: LightningData(content: data))
}
}
好的,现在想想我们再接到上个Delegation结尾留下的一个问题,就是Delegation和Adapter有什么关系。我的理解就是从广义上来看,Delegation就是一种Adapter,比如你再回想一下什么是Adapter,Adapter其实就是就是衔接不同种类对象可以协同工作的一个适配器。Delegation中的delegate对象其实在某种情况某个角度来看就是Adapter!就比方说拿大家非常常用的UITableViewDelegate来看,如果我们在didSelectionRowForIndexPath()这个代理方法里弹了个AlertView提示用户,那么这个delegate不就是让TabelView和AlertView协同工作了么?当用户点击了TabelView某一行,AlertView就弹出来了。再详细点类比如下:
UITableViewDelegate:就是上面说的Interface,它是一个protocol协议,定义很多接口方法,其中包括那个我们最常用的“用户点击了某一行cell”的事件方法。
UITableView的属性delegate<UITableViewDelegate>:它就相当于我们上面说的Adapter,它满足并实现了定好的Interface接口,通常会是我们的Controller。
UITableView:是上面说的Client,它会调用Adapter(即delegate属性)的Interface接口来达到更其他对象协同工作的效果
delegate具体实现里用到的其他对象:这些都相当于上面说的Apdatee。
当然你也可以说Delegation就是Delegation,Adapter就是Adapter。主要看你从什么角度去理解和看待吧,我个人觉得其实不用那么死板的。
7. 监视间谍Observer
Observer(观察者模式)让一个对象状态变化的时候可以通知到其他的对象,且这个对象并用不知道谁正在观察它的状态,这是一种低耦合的设计,它是一对多的关系。
在iOS开发中观察模式主要两种实现技术:Notification 和 KVO。
实现步骤都是要观察者先对某个被观察者的状态进行一个注册,之后当被观察者的状态变化的时候就会通知到已注册的观察者。
Notification和KVO具体使用方法就不细说了,一般大家都用过,网上搜一下就有了,或者看看官方的文档:
《KVO》地址
《Notification》地址
不过使用Observer的方法需要注意尽量避免跨层通知,除非是这是必要的。另外因为被观察者对他的观察者是一无所知的,也就是说使用上面方法人人都能观察某个对象的状态,这中间如果出了问题是很难去排查的。
8.生产流水线Factory
Factory(工厂模式)就是用来生产能提取出相同特性产品的,这里的产品就是类的实例化对象。它的特点就在于不直接创建对象,而是使用类或对象的工厂方法来创建对象产品,并且返回一个抽象类型的对象。
根据复杂的层级区别又有分为简单工厂,工厂方法,抽象工厂。是不是好像很复杂的样子。。。。。这三类工厂模式从前到后依次抽象的东西越来越多,也有人将简单工厂和工厂方法分为一类,将前者看做后者的一个特例。
简单工厂
简单工厂真的很简单,就是通过将同类型的产品抽象出相同的特性,然后通过一个工厂类去创建某一个具体的产品对象,返回给用户一个抽象的产品类。
简单说,简单工厂就是一个专门生产某类产品的类。
因为本人平时弹吉他,我就以乐器来举例子。下面的Fender、Gibson是吉他品牌名称
//抽象产品协议(也可以是抽象类)
protocol Guitar {
//统一的演奏特性
func play()
}
//具体产品类
class FenderGuitar : Guitar {
func play() {
print("fender guitar playing")
}
}
class GibsonGuitar : Guitar {
func play() {
print("gibson guitar playing")
}
}
//工厂类
class GuitarSeller {
class func sellGuitar(brand: String) -> Guitar? {
if brand == "Fender" {
return FenderGuitar()
} else if brand == "Gibson" {
return GibsonGuitar()
} else {
return nil
}
}
}
//使用者使用场景
func usingSample() {
//调用工厂类创建一个产品
let guitar = GuitarSeller.sellGuitar(brand: "Fender")
//调用抽象产品方法来使用这个产品,不关心具体类的名称
guitar?.play()
}
可以看到使用了简单工厂之后使用方不用负责具体产品的创建,也无需知道产品的真是类型,只知道产品具有这一类产品的抽象特性(即我们例子里的play演奏),便可以使用这个产品。
一把在业务场景简单,同时具体产品类比较少的时候可以使用。如果具体产品越来越多,每添加一个新产品就要修改这个工厂类,同时这个工厂类就会变得非常庞大也就越来越难维护。请继续往下看
工厂方法
如果上面例子中的Seller最后发现卖的吉他品牌产品太多了,不堪重负,觉得还是只卖一种牌子的吉他比较好,也能做的比较专业,同时能给客户提供更好的维护售后服务。这就会演变出很多的具体Seller(具体的工厂),他们都只经营一种具体品牌(具体的产品),但是他们有个共同的特性可以抽象出来,就是销售吉他(抽象的工厂)。
在这种情况下,购买某个牌子的吉他不再由传递的参数决定,而是由你跟哪个品牌的seller(具体工厂)去买而决定。
这就引申出工厂方法,请看下面代码的例子:
//抽象产品协议(也可以是抽象类)
protocol Guitar {
//统一的演奏特性
func play()
}
//具体产品类
class FenderGuitar : Guitar {
func play() {
print("fender guitar playing")
}
}
class GibsonGuitar : Guitar {
func play() {
print("gibson guitar playing")
}
}
//抽象工厂协议(也可以是抽象类)
protocol GuitarSeller {
func sellGuitar() -> Guitar
}
//具体工厂类
class FenderSeller : GuitarSeller {
func sellGuitar() -> Guitar {
return FenderGuitar()
}
}
class GibsonSeller : GuitarSeller {
func sellGuitar() -> Guitar {
return GibsonGuitar()
}
}
//使用者使用场景
func usingSample() {
let seller : GuitarSeller = GibsonSeller()
let guitar = seller.sellGuitar()
guitar.play()
}
使用了工厂方法以后,如果以后新增了一个具体产品(比如XX牌子吉他类),只需新增一个对应的具体工厂(XX牌子吉他Seller),而无需修改其他已有的任何类。
抽象工厂
继续,假如某天我们不止要玩吉他,还想搞乐队了,那需要其他的乐器了,比如我们除了买吉他还要买个Bass(贝斯->长得吉他的低音乐器)。又比如说Fender和Gibson品牌不光有吉他,还有贝斯了那会是怎样?
这就可以用到抽象工厂模式了
//抽象产品协议(也可以是抽象类)
protocol Guitar {
func play()
}
protocol Bass {
func tapping()
}
//具体产品类
class FenderGuitar : Guitar {
func play() {
print("fender guitar playing")
}
}
class GibsonGuitar : Guitar {
func play() {
print("gibson guitar playing")
}
}
class FenderBass : Bass {
func tapping() {
print("fender Bass tapping")
}
}
class GibsonBass : Bass {
func tapping() {
print("gibson Bass tapping")
}
}
//抽象工厂协议(也可以是抽象类)
protocol InstrumentSeller {
func sellGuitar() -> Guitar
func sellBass() -> Bass
}
//具体工厂类
class FenderSeller : InstrumentSeller {
func sellGuitar() -> Guitar {
return FenderGuitar()
}
func sellBass() -> Bass {
return FenderBass()
}
}
class GibsonSeller : InstrumentSeller {
func sellGuitar() -> Guitar {
return GibsonGuitar()
}
func sellBass() -> Bass {
return GibsonBass()
}
}
//使用者使用场景
func usingSample() {
let seller : InstrumentSeller = GibsonSeller()
let guitar : Guitar = seller.sellGuitar()
let bass : Bass = seller.sellBass()
guitar.play()
bass.tapping()
}
工厂方法模式是生产单个同类型的不同产品
而抽象工厂模式生产的是多个不同类型的不同产品
工厂方法模式通过增加具体子类即可增加新产品
抽象工厂模式必须修改抽象的父类接口才能创建新产品
未完待续。。。。。
网友评论