设计模式之结构型-iOS

113 阅读9分钟

设计模式是一种特定的解决问题的方法,是基于软件设计原则的具体实现。设计模式是从多种场景中提取出来的、经过实践验证的解决方案,通常提供了具体的代码实现和设计架构。常见的设计模式有单例模式、工厂模式、观察者模式、策略模式等等,这些模式为开发人员提供了特定场景下的具体解决方案,帮助开发人员实现更为优秀的应用程序。

经典的GOF设计模式有23个,分为创建型、结构型、行为三大类模式,这里主要介绍结构型模式。

结构型模式分为以下7种设计模式,这类模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。

一、适配器(Adapter Pattern)

用于将一个接口转换成另一个客户端所期望的接口。适配器模式使得原本由于接口不兼容而无法工作的类能够一起工作。

具体做法:

  1. 目标接口(Target Interface):将期望的接口方法抽取到协议中。
  2. 适配器(Adapter):实现目标接口,并包含一个对非兼容接口的引用。适配器将客户端的请求委托给被适配的对象,完成接口的转换。
  3. 被适配的类(Adaptee):已经存在的类,提供了一些有用的功能,但与目标接口不兼容。

使用时: 创建适配器调用目标接口中的方法。

二、桥接(Bridge Pattern)

桥接模式将抽象部分与实现部分分离,使它们可以独立地进行变化和演化。这种模式常用于有多个维度变化的情况,而且这些维度变化是独立的。

举例一个场景:

我们要制作烤肉,烤肉有:烤鸡翅、烤羊肉串等;味道有:微辣、特辣等;

现在,不使用设计模式时,需要实现的代码有4个类,分别是:烤微辣鸡翅、烤微辣羊肉串、烤特辣鸡翅、特辣羊肉串,在制作时分别调用对应的make方法实现对应的烤肉。

当我们新增一个中辣时,会发现又需要新建2个类,这样下来类就太多了。接下来我们看下桥接模式的实现:

  1. 抽取一个基类或者协议,所有烤肉类都需要继承,协议中有make方法,每个烤肉类需要实现它,里面是具体制作的逻辑;协议中还要有一个属性,这个属性是辣味程度类型。
  2. 辣味程度也要抽取基类或协议,所有辣味程度都是继承它;协议中有taste方法。每个具体的辣味程度类实现这个方法。
  3. 使用时,建立对应的烤肉对象,并设置好辣味程度对象,这样调用make方法就制作出来对应的一种烤肉。
  4. 现在来看,当需要扩展一种新的烤肉或者新的辣味程度时,只需要新建继承对应基类的类即可。

三、组合(Composite Pattern)

它允许你将对象组合成树状结构来表示部分整体层次关系,使得客户端对单个对象和组合对象的使用具有一致性。在组合模式中,组合对象和叶子对象具有相同的接口,可以被一致地使用。

  • 组件(Component):定义组合中对象的通用接口,可以是抽象类或接口。它声明了组合对象和叶子对象的方法,例如添加、删除或执行操作。
  • 叶子对象(Leaf):叶子对象表示组合中的叶节点对象,它没有子对象。它实现了组件接口所定义的操作。
  • 组合对象(Composite):组合对象表示组合中的容器节点,它可以包含叶子对象和其他组合对象。它实现了组件接口,并提供了添加、删除和遍历子组件的方法。在执行自己的操作时,它可能会递归调用其子组件的操作。

例子: 我们iOS中的UIView就是这样一种设计模式, 其内部有一个数组属性,数组中元素的类型又是UIView(或者其子类)。CALayerUIViewController也是一样。

  • 将基本属性和方法抽取到基类,持有一个装基类类型对象的数组(多态),并拥有添加和移除元素的方法。
  • 这个模式下,很方便的让树种每个节点递归执行方法,比如触摸事件查找最佳响应者时遍历调用子视图的hitTest方法。

四、装饰(Decorator Pattern)

装饰模式(Decorator Pattern)可以用于为现有的对象动态地添加新的行为或功能,同时保持原有对象的接口不变。

主要适用情况

  1. 当我们需要为一个类动态地添加新的行为或功能,而且希望这些功能可以组合和排列,以实现灵活的扩展时。
  2. 当我们希望为一个类的部分方法或行为添加新的功能,而不影响其他方法或行为时。

主要角色:

  1. 抽象组件(Component): 定义了被装饰对象的通用接口,可以是一个具体类或接口。
  2. 具体组件(Concrete Component): 实现了抽象组件接口的具体类,是被装饰的原始对象。
  3. 抽象装饰器(Decorator): 继承自抽象组件,通常持有一个抽象组件的引用,并在其中定义了一个与抽象组件接口相同的接口。
  4. 具体装饰器(Concrete Decorator): 继承自抽象装饰器,对抽象装饰器的接口进行实现,并在其中封装对抽象组件的调用,同时可以添加额外的功能。

例子: 抽象组件:画图形。

具体组件:画圆、画矩形。

抽象装饰器:画的方法,内部有一个抽象组件的属性。

具体装饰器:继承抽象装饰器,如边框、阴影装饰器。

五、外观(Facade Pattern)

通过使用外观模式,我们可以将复杂的子系统封装起来,提供一个简单易用的接口,降低客户端与子系统之间的耦合度,让客户端代码更加清晰和易于维护。

主要角色:

  1. 外观(Facade):外观类是外部客户端与子系统之间的接口。它包含了对子系统的各种操作,协调和委托给子系统来完成具体的任务。
  2. 子系统(Subsystems):子系统是一组相互关联的类或对象集合,用于完成一个复杂的业务逻辑。外观通过与子系统进行交互,实现对子系统的封装和调用。

例子:

// 使用外观类
let car = Car()
car.startCar()
car.stopCar()

但是内部的startCar()和stopCar()是较为复杂的几个类完成。
func startCar() {
    engine.start()
    lights.turnOn()
    musicPlayer.play()
    print("Car started.")
}
 func stopCar() {
    engine.stop()
    lights.turnOff()
    musicPlayer.stop()
    print("Car stopped.")
 }

六、享元(Flyweight Pattern)

旨在通过共享对象来有效地支持大量的细粒度对象。该模式通过将对象的状态分为可共享的内部状态和不可共享的外部状态,来减少系统中对象的数量并节省内存。

在享元模式中,内部状态存储在享元对象内部,可以被多个具体享元对象共享。而外部状态是在运行时由客户端传递给享元对象的,它们不可共享,且会影响享元对象的行为。

享元模式的关键是将对象的共享从对象级别转移到类级别,通过对象的内部状态来实现对象共享。这样一来,当需要创建新的对象时,可以先检查是否已经存在具有相同内部状态的对象,如果存在,则直接返回已有对象,而不是创建新对象。

使用享元模式的主要目的是减少内存使用量,特别适用于以下情况:

  • 系统中存在大量相似的对象,占用大量内存。
  • 对象的大部分状态可以外部化,并且可以通过参数传递给对象。
  • 不需求对象标识的情况下,可以将对象复用。

角色:

  • 享元接口(Flyweight):定义共享对象的接口,包含需要外部状态传递的方法。
  • 具体享元(Concrete Flyweight):实现享元接口,包含内部状态,并对内部状态进行复用。
  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,通常包含对象池或缓存等结构,以便复用已有对象。

例子:

系统的UITableView

  1. UITableViewCell:每个单元格需要显示不同的数据,但是 UITableViewCell 的视图结构是相同的。UITableViewCell 的视图结构可以看做是共享部分,即享元对象。
  2. UITableView:UITableView 是列表视图控件,它负责管理大量的 UITableViewCell 对象。UITableView 使用了对象池(对象缓存)机制,用来复用已有的 UITableViewCell 对象。这样,在滚动过程中,只需要创建可见范围内的单元格,而不是为每个单元格都创建一个新的对象,从而节省了内存和提高了性能。

系统的UIFont: 为了避免在运行时频繁创建和销毁字体对象的开销,UIFont 实现了享元模式。

七、代理(Proxy Pattern)

代理模式的核心思想是引入一个代理对象,代理对象可以替代原始对象执行特定的操作,同时对客户端隐藏了原始对象的实现细节。代理对象通常充当了客户端和实际对象之间的中介,对客户端透明地提供相同的接口。

角色:

  • 抽象主题(Subject):协议,协议中定义方法或属性。
  • 真实主题(Real Subject):遵守协议,实现协议中的方法或属性。
  • 代理(Proxy):持有对真实主题的引用,需要时让真实主题调用相应的接口。