行为型设计模式—访问者模式

访问者模式:将数据结构对象与数据操作分离的设计模式,可以在不改变数据结构对象类结构的前提下定义作用于这些对象的新的操作。

访问模式结构:

  • 访客接口 (Visitor) 声明了一系列以表示对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
  • 具体访客 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
  • 元素 (Element) 接口,声明了一个方法来 "接收" 访问者。 该方法必须有一个参数被声明为访问者接口类型。
  • 具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访客对象中的合适方法。

有点像迭代器模式的结构,可以把迭代器看成访问者模式的特例,迭代器是吧遍历操作与具体数据结构解耦,而访问者模式把任何操作与一类对象解耦。

访问者模式通过将算法与对象结构分离来工作,这里说的算法指的是对对象的操作。为此,需要定义了一个表示算法的接口--Visitor。该接口将为对象结构中的每个类(一般称为元素类)提供一个方法。每个方法都将元素类的一个实例作为参数。表示对象结构的所有元素类也会实现一个Element接口,该接口定义了接受访问者的方法Accpet。此方法将访问者接口的实现作为参数。当Accpet方法被调用时,访问者实例对应的方法就会被调用,通过访问者完成对元素类实例的操作。

访问者模式允许你在结构体中添加行为, 而又不会对结构体造成实际变更。 假设代码库中包含不同的形状结构体, 如:

  • 方形
  • 圆形
  • 三角形

上述每个形状结构体都实现了通用形状接口。

首先定义一个如下访问者接口:

go 复制代码
type visitor interface {
    visitForSquare(square)
    visitForCircle(circle)
    visitForTriangle(triangle)
}

可以使用 visit­For­Square­(square)visit­For­Circle­(circle)以及 visit­For­Triangle­(triangle)函数来为方形、 圆形以及三角形添加相应的功能。

accept接受方法添加至形状接口中。如果添加任何其他行为, 比如 get­Num­Sides获取边数和 get­Middle­Coordinates获取中点坐标 , 可使用相同的 accept­(v visitor)函数, 而无需对形状结构体进行进一步的修改。

go 复制代码
func accept(v visitor)

定义对象接口

go 复制代码
type Shape interface {
    getType() string
    accept(Visitor)
}

实现接口

go 复制代码
type Square struct {
    side int
}

func (s *Square) accept(v Visitor) {
    v.visitForSquare(s)
}

func (s *Square) getType() string {
    return "Square"
}
go 复制代码
type Circle struct {
    radius int
}

func (c *Circle) accept(v Visitor) {
    v.visitForCircle(c)
}

func (c *Circle) getType() string {
    return "Circle"
}
go 复制代码
type Rectangle struct {
    l int
    b int
}

func (t *Rectangle) accept(v Visitor) {
    v.visitForrectangle(t)
}

func (t *Rectangle) getType() string {
    return "rectangle"
}

实现不同的访问方法

go 复制代码
type AreaCalculator struct {
    area int
}

func (a *AreaCalculator) visitForSquare(s *Square) {
    // Calculate area for square.
    // Then assign in to the area instance variable.
    fmt.Println("Calculating area for square")
}

func (a *AreaCalculator) visitForCircle(s *Circle) {
    fmt.Println("Calculating area for circle")
}
func (a *AreaCalculator) visitForrectangle(s *Rectangle) {
    fmt.Println("Calculating area for rectangle")
}
go 复制代码
package main

import "fmt"

type MiddleCoordinates struct {
    x int
    y int
}

func (a *MiddleCoordinates) visitForSquare(s *Square) {
    // Calculate middle point coordinates for square.
    // Then assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for square")
}

func (a *MiddleCoordinates) visitForCircle(c *Circle) {
    fmt.Println("Calculating middle point coordinates for circle")
}
func (a *MiddleCoordinates) visitForrectangle(t *Rectangle) {
    fmt.Println("Calculating middle point coordinates for rectangle")
}

执行代码

go 复制代码
package main

import "fmt"

func main() {
    square := &Square{side: 2}
    circle := &Circle{radius: 3}
    rectangle := &Rectangle{l: 2, b: 3}

    areaCalculator := &AreaCalculator{}

    square.accept(areaCalculator)
    circle.accept(areaCalculator)
    rectangle.accept(areaCalculator)

    fmt.Println()
    middleCoordinates := &MiddleCoordinates{}
    square.accept(middleCoordinates)
    circle.accept(middleCoordinates)
    rectangle.accept(middleCoordinates)
}

执行结果

go 复制代码
Calculating area for square
Calculating area for circle
Calculating area for rectangle

Calculating middle point coordinates for square
Calculating middle point coordinates for circle
Calculating middle point coordinates for rectangle

访客模式有如下优点

  • 解耦了数据结构与数据操作,使得操作集合可以独立变化。
  • 可以通过扩展访问者角色,实现对数据集的不同操作,程序扩展性更好。
  • 元素具体类型并非单一,访问者均可操作。
  • 各角色职责分离,符合单一职责原则。

与此同时它也有以下缺点

  • 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则。
  • 具体元素变更困难:具体元素增加属性、删除属性等操作, 会导致对应的访问者类需要进行相应的修改,尤其当有大量访客类时,修改范围太大。
  • 违背依赖倒置原则:为了达到"区别对待",访问者角色依赖的是具体元素类型,而不是抽象接口。

访问者模式与其他设计模式:

  • 将访问者模式视为 命令模式 的加强版本, 其对象可对不同类的多种对象执行操作。
  • 使用访问者对整个 组合模式树 执行操作。
  • 可以同时使用访问者和 迭代器模式 来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。
相关推荐
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
杜杜的man3 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家3 小时前
go语言中package详解
开发语言·golang·xcode
llllinuuu3 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s3 小时前
Golang--协程和管道
开发语言·后端·golang
王大锤43913 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
产幻少年4 小时前
golang函数
golang
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
半桶水专家4 小时前
用go实现创建WebSocket服务器
服务器·websocket·golang