设计模式之行为模式-iOS

160 阅读12分钟

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

经典的GOF设计模式有23个,分为创建型、结构型、行为三大类模式,这里主要介绍行为模式。 行为模式分为以下11种设计模式,这类模式负责对象间的高效沟通和职责委派。

一、责任链(Chain of Respnsibility Pattern)

它允许你将多个对象组成一条责任链,依次尝试处理请求,直到有一个对象能够处理该请求为止。

在责任链模式中,每个处理请求的对象都拥有相同的接口,它们可以选择自行处理请求,或者将请求传递给下一个对象。这样,请求会依次在责任链中传递,直到找到合适的处理者。

模式中的角色:

  • 处理者(Handler): 定义了处理请求的接口,并维护一个指向下一个处理者的引用。
  • 具体处理者(Concrete Handler):实现了处理请求的具体逻辑,如果能够处理请求,则进行处理,否则将请求传递给下一个处理者。

iOS 中的响应链便是职责链设计模式的一个实例。

二、命令(Command Design Pattern)

它将请求封装成一个对象,从而允许您使用不同的请求、队列或日志请求参数化客户端,并支持可撤销的操作。

主要角色:

  • Command(命令):定义了执行操作的接口。通常包含一个执行操作的方法。
  • Concrete Command(具体命令):实现Command接口,并且通过调用接收者的方法来完成具体的操作。
  • Receiver(接收者):执行实际操作的对象。具体命令会调用接收者的方法来执行操作。
  • Invoker(调用者):通过持有命令对象来执行请求。它只知道如何发送一个请求,但不知道如何执行该请求。
  • Client(客户端):创建具体命令对象,并设置它的接收者。

您可以将不同的操作封装成命令对象,并通过更换具体的命令对象来实现不同的操作或行为。它对于实现撤销、重做、队列操作或日志记录等场景非常有用,同时也能够降低系统间的耦合度。

三、迭代器

迭代器模式是一种行为设计模式,它允许你按照特定方式遍历集合对象,而不暴露其内部结构。通过迭代器模式,你可以透明地访问一个聚合对象的元素,而无需了解它们的内部实现。

例如,使用Swift中的IteratorProtocol协议和AnyIterator结构体,可以创建自定义的迭代器来遍历数组的元素:

let array = [1, 2, 3, 4, 5]

var iterator = array.makeIterator()
while let element = iterator.next() {
    // 处理每个元素
    print(element)
}

四、中介者模式

它通过使用一个中介者对象,来封装一组对象之间的交互。中介者模式可以减少对象之间的直接依赖关系,使得对象之间的通信更加松散和可维护。

在iOS开发中,一个常见的应用中介者模式的实例是使用NotificationCenter(通知中心)作为中介者来实现不同对象之间的通信。

NotificationCenter是iOS中用于实现发布-订阅模式的中介者类。它允许对象之间通过发送和订阅通知来进行通信,而不需要直接引用彼此。

需要注意的是,在使用NotificationCenter作为中介者时,需要小心管理通知的订阅和移除,以避免潜在的内存泄漏和不必要的通知处理。确保在对象不再需要监听通知时,及时移除观察者。

五、备忘录

它允许你捕获一个对象的内部状态,并在需要时将其恢复到之前的状态。备忘录模式提供了一种在不破坏封装性的情况下保存和恢复对象状态的方法。

有三个主要的角色:

  1. 发起人(Originator):发起人是需要保存状态的对象。它可以创建一个备忘录对象来保存当前状态,并可以使用备忘录对象来恢复之前的状态。
  2. 备忘录(Memento):备忘录是用于保存发起人对象状态的对象。它通常包含了发起人对象的部分或全部状态信息。
  3. 管理者(Caretaker):管理者是负责保存和恢复备忘录的对象。它可以保存多个备忘录对象,并在需要时将发起人对象恢复到之前的状态。

需要注意备忘录模式可能带来的内存消耗、性能损失和对象复杂性增加。

实例:在文本编辑器应用程序中保存和恢复用户编辑的文本内容。

// 备忘录
class TextMemento {
    let text: String
    init(text: String) {
        self.text = text
    }
}
// 发起人
class TextEditor {
    var text: String
    init(text: String) {
        self.text = text
    }
    func save() -> TextMemento {
        return TextMemento(text: text)
    }
    func restore(from memento: TextMemento) {
        text = memento.text
    }
}
// 管理者
class Caretaker {
    var mementos: [TextMemento] = []
    func addMemento(_ memento: TextMemento) {
        mementos.append(memento)
    }
    func getMemento(at index: Int) -> TextMemento {
        return mementos[index]
    }
}

六、观察者

它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象的状态发生变化时,会通知所有的观察者对象进行相应的更新。

三个主要的角色:

  1. 主题(Subject):主题是被观察的对象,它维护了一个观察者列表,并提供了添加、删除和通知观察者的方法。当主题的状态发生变化时,会通知所有的观察者对象。
  2. 观察者(Observer):观察者是监听主题对象的状态变化的对象。观察者定义了一个更新方法,当接收到主题通知时,会调用更新方法进行相应的处理。
  3. 具体主题(Concrete Subject)和具体观察者(Concrete Observer):它们是主题和观察者的具体实现类,根据具体业务需求实现相应的逻辑。

实例:RxSwift中的PublishSubject、KVO、通知中心等。

七、状态模式

它允许对象在内部状态改变时改变其行为。状态模式使得对象在运行时可以根据内部状态的变化而改变它的行为方式,而不是根据外部输入或条件判断来改变。

三个核心角色:

  1. 环境(Context):环境是包含了内部状态的对象,它具有一个当前状态对象,并在运行时根据当前状态对象来调用不同的行为方法。
  2. 抽象状态(State):抽象状态定义了一个接口或基类,用于封装各个具体状态类的公共方法。它通常包含了一些根据当前环境状态来执行的方法。
  3. 具体状态(Concrete State):具体状态是抽象状态的实现类,每个具体状态类负责不同的行为。具体状态类实现了抽象状态定义的接口或方法,实现自己所对应的行为逻辑。

实例:

视图根据状态不同,让具体状态对象执行方法。这样做符合开闭原则。

八、策略模式

它定义了一组可互换的算法,并将每个算法都封装成独立的对象,使得它们可以在运行时互相替换。这样可以让算法的变化独立于使用算法的客户端。

角色:

  1. 环境(Context):持有一个策略对象的引用,并在需要时调用策略对象的方法。
  2. 抽象策略(Strategy):定义了策略对象所需实现的接口或基类,声明了算法的方法。
  3. 具体策略(Concrete Strategy):实现了抽象策略中定义的算法。

实例:

支付应用中的不同支付方式。

可以看到,策略模式跟状态模式是比较类似的,不同点:

  1. 定义和目的:
  • 策略模式:定义一系列算法,并将算法封装成独立的对象,使它们可以互相替换。策略模式的目的是在运行时动态选择算法。
  • 状态模式:根据对象的内部状态改变对象的行为。状态模式的目的是实现状态和行为的解耦,使得状态的变化不影响行为。
  1. 对象关系:
  • 策略模式:一个对象使用策略对象来改变自己的行为,策略对象可以被多个对象共享。
  • 状态模式:一个对象具有多个状态对象,并且在不同状态下执行不同的行为,状态对象通常是特定于对象的。
  1. 变化方式:
  • 策略模式:在运行时可以动态切换不同的策略对象。
  • 状态模式:对象的状态在运行时可以改变,从而改变对象的行为。
  1. 职责分配:
  • 策略模式:主要关注的是算法的选择和切换,将算法的实现和调用者分离。
  • 状态模式:主要关注的是对象的状态和行为的变化,将状态的变化和行为的实现分离。

九、模板方法

它定义了一个算法的骨架,将一些步骤的具体实现延迟到子类中。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

主要的角色:

  1. 抽象类(Abstract Class):抽象类定义了一个模板方法,该方法包含了算法的骨架和一些抽象步骤,这些抽象步骤由子类来实现。抽象类可以包含一些具体方法,这些方法可以被模板方法调用。
  2. 具体类(Concrete Class):具体类是抽象类的子类,它实现了抽象类中定义的抽象步骤,完成算法的具体实现。

实例:UIViewController,里面各个生命周期方法就相当于一些抽象步骤。

十、访问者

用于将算法与对象的结构分离。它允许在不更改对象结构的情况下,定义新的操作并将其应用于对象的元素。

主要解决:稳定的数据结构和易变的操作耦合问题。

角色:

  • 访问者(Visitor)定义了访问对象的各个元素的方法,每个方法对应一个具体的元素类型。
  • 被访问者(Element)代表对象的结构,其中可以定义接受访问者的方法 Accept,该方法接受一个访问者对象作为参数,在这个方法中调用访问者对该元素进行操作的方法。

代码实例:

// 访问者接口
protocol Visitor {
    func visit(elementA: ConcreteElementA)
    func visit(elementB: ConcreteElementB)
}

// 具体访问者A
class ConcreteVisitorA: Visitor {
    func visit(elementA: ConcreteElementA) {
        // 对元素A进行操作的逻辑
        print("Visitor A visited Element A")
    }
    
    func visit(elementB: ConcreteElementB) {
        // 对元素B进行操作的逻辑
        print("Visitor A visited Element B")
    }
}

// 具体访问者B
class ConcreteVisitorB: Visitor {
    func visit(elementA: ConcreteElementA) {
        // 对元素A进行操作的逻辑
        print("Visitor B visited Element A")
    }
    
    func visit(elementB: ConcreteElementB) {
        // 对元素B进行操作的逻辑
        print("Visitor B visited Element B")
    }
}

// 元素接口
protocol Element {
    func accept(visitor: Visitor)
}

// 具体元素A
class ConcreteElementA: Element {
    func accept(visitor: Visitor) {
        visitor.visit(elementA: self)
    }
}

// 具体元素B
class ConcreteElementB: Element {
    func accept(visitor: Visitor) {
        visitor.visit(elementB: self)
    }
}

// 使用示例
let elementA = ConcreteElementA()
let elementB = ConcreteElementB()

let visitorA = ConcreteVisitorA()
let visitorB = ConcreteVisitorB()

elementA.accept(visitor: visitorA) // 输出: Visitor A visited Element A
elementA.accept(visitor: visitorB) // 输出: Visitor B visited Element A

elementB.accept(visitor: visitorA) // 输出: Visitor A visited Element B
elementB.accept(visitor: visitorB) // 输出: Visitor B visited Element B

十一、解释器模式

用于定义语言的文法,并解释该语言中的句子。它在给定源语言中定义了一组规则,并根据这些规则解释句子。

几个关键角色:

  1. 抽象表达式(Abstract Expression):定义了解释器的接口,具体的解释器将实现这个接口。
  2. 终结符表达式(Terminal Expression):表示语言中的一个终结符(是不可再分的基本单位),同时实现了抽象表达式的接口。通常一个语言中的终结符对应一个具体的解释操作。
  3. 非终结符表达式(Non-Terminal Expression):表示语言中的一个非终结符,由一个或多个终结符或其他非终结符组成。非终结符表达式通常会递归地调用其他表达式进行解释。
  4. 上下文(Context):包含解释器之外的一些全局信息,它对解释器的解释结果进行存储和传递。

实例:通过定义一个模板语言,然后根据模板语言解析和渲染界面。

假设我们有一个模板语言,其中包含一些占位符和特定的语法规则,例如 {{variable}} 表示一个变量,我们需要根据模板语言的定义,将模板解析为对应的界面。

// 抽象表达式接口
protocol Expression {
    func interpret(context: Context) -> String
}

// 终结符表达式 - 变量表达式
class VariableExpression: Expression {
    private var variableName: String
    
    init(variableName: String) {
        self.variableName = variableName
    }
    
    func interpret(context: Context) -> String {
        return context.getVariable(name: variableName) ?? ""
    }
}

// 非终结符表达式 - 模板解析表达式
class TemplateExpression: Expression {
    private var expressions: [Expression] = []
    
    init(template: String) {
        let regex = try! NSRegularExpression(pattern: "\{\{([a-zA-Z]+)\}\}", options: [])
        let matches = regex.matches(in: template, options: [], range: NSRange(location: 0, length: template.count))
        
        for match in matches {
            let range = match.range(at: 1)
            if let swiftRange = Range(range, in: template) {
                let variableName = String(template[swiftRange])
                let variableExpression = VariableExpression(variableName: variableName)
                expressions.append(variableExpression)
            }
        }
    }
    
    func interpret(context: Context) -> String {
        var result = ""
        for expression in expressions {
            result = expression.interpret(context: context)
            替换的方法...
        }
        return result
    }
}

// 上下文对象
class Context {
    private var variables: [String: String] = [:]
    
    func setVariable(name: String, value: String) {
        variables[name] = value
    }
    
    func getVariable(name: String) -> String? {
        return variables[name]
    }
}

// 使用示例
let template = "Hello, {{name}}! Today is {{day}}."
let context = Context()
context.setVariable(name: "name", value: "John")
context.setVariable(name: "day", value: "Monday")

let templateExpression = TemplateExpression(template: template)
let result = templateExpression.interpret(context: context)
print(result)  
// 输出: Hello, John! Today is Monday.