在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式;如公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同; 这些被处理数据元素相对稳定,而访问方式多种多样的数据结构,使用访问者模式来处理比较方便,访问者能把处理方法从数据结构中分离出来,并可根据需要增加新的处理方法,且不用修改原程序代码与数据结构,从而提高了程序的扩展性和灵活性;
访问者模式(Visitor Pattern):
1.将作用于某种数据结构中的各元素的操作封装成独立的类,使其在不改变数据结构的前提下定义作用于这些元素的新的操作;
2.主要将数据结构 与 数据操作分离,在被访问的类里面 加一个对外提供接待访问者的接口;
3.访问者模式是行为型模式中最复杂的一种模式;
1) 扩展性好:能在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能;
2) 复用性好:可通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度;
3) 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由的演化而不影响系统的数据结构;
4) 符合单一职责原则.访问者模式把相关行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一;
1) 不符合迪比特法则: 具体元素对访问者公布细节,即访问者关注了其他类的内部细节,这样会造成具体元素变更比较困难;
2) 违背了依赖倒置原则: 访问者依赖的是具体元素,而不是抽象元素,如抽象访问者Action依赖的是具体的元素Wan和Woman;
3) 违背开闭原则: 增加元素很困难,在访问者模式中,每增加一个新的元素类,都要在每个具体访问者类中增加相应的具体操作;
当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式;
1. 对象结构相对稳定,但其操作算法经常变化的程序;
2. 对象结构中的对象需要提供多种不同且不相关的操作,同时要避免这些操作"污染"这些对象的结构的场景;
3. 对象结构包含很多类似的对象,希望对这些对象实施一些依赖与其具体类型的操作;
访问者模式实现的关键是如何将作用与元素的操作分离出来封装成独立的类,其结构图如下:
1) Visitor(抽象访问者):为每个具体元素类定义一个访问方法 visit(),该方法中的参数类型标识了被访问的具体元素;
2) ConcreteVisitor(具体的访问者):实现每个抽象访问者Visitor声明的各个访问操作,确定访问者访问一个元素时该做什么;
3) ObjectStructure(对象结构):包含元素角色的容器,提供让访问者对象遍历容器中所有元素的方法,由List、Set、Map聚合实现
4) Element(抽象元素): 声明一个包含接受操作的accept()方法, 被接受的访问者对象作为accept()方法的参数;
5) ConcreteElement(具体元素): 实现了accept()通常都是visitor.visit(this),另外它可能还包括本身业务逻辑的相关操作;
将评委分为男评委和女评委对歌手进行测评,当看完某个歌手的表演后,得到他们对该歌手不同的评价(评价有不同类型:成功 失败等)
object VisitorClient {
@JvmStatic
fun main(args: Array) {
//创建ObjectStructure
val playerName = "王浩" //选手姓名
val fail = Fail()//失败 ConcreteVisitor1
val success = Success()//成功 ConcreteVisitor2
val objectStructure = ObjectStructure() //评委元素对象结构
val maleJudge = MaleJudge(playerName) //男评委评价王浩 ConcreteElement1
val femaleJudge = FemaleJudge(playerName) //女评委评价王浩 ConcreteElement2
setJudgement(objectStructure, maleJudge, success)
println("========================================")
setJudgement(objectStructure, femaleJudge, fail)
}
/***
* 评价选手
* @objectStructure 评委元素对象结构
* @judge 评委 Element
* @judgement 评价结果 Visitor
*/
private fun setJudgement(objectStructure: ObjectStructure, judge: Judge, judgement: Action) {
objectStructure.add(judge) //将评委添加到元素列表中
objectStructure.accept(judgement)
objectStructure.remove(judge)
}
}
//对象结构类
class ObjectStructure {
//维护了一个集合
private var judges: MutableList = LinkedList()
//增加到list中
fun add(judge:Judge){
judges.add(judge)
}
//从list中移除
fun remove(judge:Judge){
judges.remove(judge)
}
//显示测评情况
fun accept(action: Action) {
for (person in judges) {
person.accept(action)
}
}
}
/**
* Element: 抽象元素 评委员
* 定义一个accept()方法, 接收一个访问者对象;
*/
abstract class Judge {
//提供一个方法,让访问者可以访问
abstract fun accept(action:Action)
}
/**
* 具体元素 男评委, 实现了accept()方法;
* name : 被评价人名字
*/
class MaleJudge(val name:String) :Judge(){
override fun accept(action: Action) {
action.getMaleResult(this,name)
}
}
/**
* 具体元素-女评委, 实现了accept()方法;
* 说明:
* 1.这里我们使用了双分派,即首先在客户端程序中,将具体的状态作为参数传递FemaleJudge中(第一次分派)
* 2.然后Woman类调用作为参数的 "具体方法" 中的getWomenResult方法,同时将自己(this)作为参数传入,完成第二次分派
*/
class FemaleJudge(val name:String) : Judge() {
override fun accept(action: Action) {
action.getFemaleResult(this,name)
}
}
/**
* 抽象访问者 visitor 判决结果
* 为该对象结构中的ConcreteElement 的每一个类声明一个visit操作;
*/
abstract class Action {
//得到男评委的评测结果
abstract fun getMaleResult(male:MaleJudge, name:String)
//得到女评委的评测结果
abstract fun getFemaleResult(female:FemaleJudge, name:String)
}
/**
* 具体访问者 ConcreteVisitor 判决失败
* 具体的访问者,实现每个Visitor声明的操作,是每个操作的实现部分;
*/
class Fail : Action() {
override fun getMaleResult(male: MaleJudge, name: String) {
println("男评委给的评价是歌手$name 失败未通过")
}
override fun getFemaleResult(female: FemaleJudge, name: String) {
println("女评委给的评价是歌手$name 失败未通过")
}
}
/**
* 具体访问者 ConcreteVisitor 判决成功
* 具体的访问者,实现每个Visitor声明的操作,是每个操作的实现部分;
*/
class Success : Action(){
override fun getMaleResult(male: MaleJudge, name: String) {
println("男评委给的评价是歌手$name 成功通过")
}
override fun getFemaleResult(female: FemaleJudge, name: String) {
println("女评委给的评价是歌手$name 成功通过")
}
}
程序运行结果
男评委给的评价是歌手王浩 成功通过
==============================
女评委给的评价是歌手王浩 失败未通过
在执行一个方法的时,仅仅需要方法调用者(Receiver)的 运行时类别(Run time type) (调用者的多态与重载) 例:有一个基类A,A有一个方法f,可被子类override,D1和D2是A的两个子类,在D1和D2中重写(override)了方法f,这样我们对方法f的调用,需要根据运行时具体接收者(是A或A的子类D1/D2)的类型来判断是调用的那个类的f方法;
在执行一个方法时,不仅要根据方法 调用者 的运行时类别(Run time type),还要根据方法中 实参 的运行时类别;双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型;例: 接上示例,基类A的方法f中有一个C类别的形参,C也是个基类,C也有 E1和E2两个子类,这样当我们调用方法f时不仅要考虑运行时调用者的类型, 还要考虑运行时方法中实参的类型,才能最终确定具体执行的是那个方法;
首先在客户端程序中,将具体的状态作为参数传递ConcreteElement中(第一次分派)
ConcreteElement类调用getResult(e:Element)方法,同时将自己(this)作为参数传入,完成第二次分派;
若我们要添加一个Wait状态类,考察Man类和Woman类的反应,只需增加一个Action子类在客户端调用即可,无需改其他类代码;