设计模式-(13)访问者模式 (swift版)
一,概念
访问者模式,是行为型设计模式之一。访问者模式是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。
访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
可以对定义这么理解:有这么一个操作,它是作用于一些元素之上的,而这些元素属于某一个对象结构。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。
使用场景:
(1)对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
(2)需要对一个对象结构中的对象进行很多不同的且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
二,类图
(1)Visitor:接口或者抽象类,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法数理论上来讲与元素个数是一样的,因此,访问者模式要求元素的类族要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果这样则不适合使用访问者模式。
(2)ConcreteVisitor1、ConcreteVisitor2:具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为。
(3)Element:元素接口或者抽象类,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素都要可以被访问者访问。
(4)ConcreteElementA、ConcreteElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
(5)ObjectStructure:定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问。
三,代码用例:
protocol Bill { func acceptVisitor(visitor: AccountBookVisitor) -> Void } struct ConsumeBill: Bill { var amount: Double var item: String init(amount: Double, item: String) { self.amount = amount self.item = item } func acceptVisitor(visitor: AccountBookVisitor) -> Void { visitor.operation(bill: self) } } struct IncomeBill: Bill { var amount: Double var item: String init(amount: Double, item: String) { self.amount = amount self.item = item } func acceptVisitor(visitor: AccountBookVisitor) -> Void { visitor.operation(bill: self) } }
import Foundation protocol AccountBookVisitor { func operation(bill: ConsumeBill) -> Void func operation(bill: IncomeBill) -> Void } class Boss: AccountBookVisitor { var totleConsume = 0.0 var totleIncome = 0.0 func operation(bill: ConsumeBill) { totleConsume += bill.amount } func operation(bill: IncomeBill) { totleIncome += bill.amount } func pureIncome() { print("pure income: \(totleIncome - totleConsume)") } } class Accountant: AccountBookVisitor { var consumeList = [String: Double]() var incomeList = [String: Double]() func operation(bill: ConsumeBill) { consumeList[bill.item] = bill.amount } func operation(bill: IncomeBill) { incomeList[bill.item] = bill.amount } func showSituation(){ print("consumeList: \(consumeList)") print("incomeList: \(incomeList)") } }
class AccountBook { var billList = [Bill]() func show(visitor: AccountBookVisitor) { for bill in billList { bill.acceptVisitor(visitor: visitor) } } }
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let accountBook = AccountBook() accountBook.billList.append(IncomeBill(amount: 100, item: "project_A")) accountBook.billList.append(ConsumeBill(amount: 150, item: "project_B")) accountBook.billList.append(IncomeBill(amount: 200, item: "project_C")) let boss = Boss() accountBook.show(visitor: boss) boss.pureIncome() let accountant = Accountant() accountBook.show(visitor: accountant) accountant.showSituation() } }
上面的代码中,可以这么理解,账本以及账本中的元素是非常稳定的,这些几乎不可能改变,而最容易改变的就是访问者这部分。
访问者模式最大的优点就是增加访问者非常容易,我们从代码上来看,如果要增加一个访问者,你只需要做一件事即可,那就是写一个类,实现AccountBookViewer接口,然后就可以直接调用AccountBook的show方法去访问账本了。
如果没使用访问者模式,一定会增加许多if else,而且每增加一个访问者,你都需要改你的if else,代码会显得非常臃肿,而且非常难以扩展和维护。
其实如果能很好理解 多态 和 重载,对于这个模式就会亲切很多。
四,总结
优点:
1、使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
2、添加新的操作或者说访问者会非常容易。
3、将对各个元素的一组操作集中在一个访问者类当中。
4、使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
5、可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。
缺点:
1、增加新的元素会非常困难。
2、实现起来比较复杂,会增加系统的复杂性。
3、破坏封装,如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态,但使用访问者模式的时候,为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构,就像收入和支出类必须提供访问金额和单子的项目的方法一样。
适用性:
1、数据结构稳定,作用于数据结构的操作经常变化的时候。
2、当一个数据结构中,一些元素类需要负责与其不相关的操作的时候,为了将这些操作分离出去,以减少这些元素类的职责时,可以使用访问者模式。
3、有时在对数据结构上的元素进行操作的时候,需要区分具体的类型,这时使用访问者模式可以针对不同的类型,在访问者类中定义不同的操作,从而去除掉类型判断。