GoLang设计模式17 - 访客模式
说明
访客模式是一种行为型设计模式。通过访客模式可以为struct添加方法而不需要对其做任何调整。
来看一个例子,假如我们需要维护一个对如下形状执行操作的库:
- 方形(Square)
- 圆形(Circle)
- 长方形(Rectangle)
以上图形的struct都继承自一个共同的shape接口。公司内有多个团队都在使用这个库。假设现在有一个团队想要为这些图形struct添加一个获取面积的方法(getArea())。有如下几种方法可以解决这种问题。
方法一
第一种方案就是直接在shape接口中添加getArea()方法。这样实现shape接口的每个struct都需要实现getArea()方法。这个方案看起来可行,但是却有一些问题:
- 作为一个公用库的维护者,有时候不想为了添加一个额外的行为就调整已经做过严格测试的代码
- 使用这个库的团队可能会提出添加更多行为的请求,比如
getNumSides()、getMiddleCoordinates()。面对这种情况,我们通常都不想持续修改这个库,而是希望这些团队继承我们这个库,自己实现自己的需求。
方法二
第二个方案就是由提出需求的团队自己实现相关的行为逻辑。要获取shape的面积就可以根据struct的类型作如下的实现:
if shape.type == square {
//Calculate area for squre
} elseif shape.type == circle {
//Calculate area of triangle
} elseif shape.type == "triangle" {
//Calculate area of triangle
} else {
//Raise error
}
以上的代码仍然是有问题的:这种方案不能充分利用接口的特性,反而还需要添加额外的类型来检查代码,造成整体结构的脆弱。此外,在运行时获取对象的类型可能会存在一些性能问题,在一些语言中甚至还不能获取对象的类型。
方法三
第三种方案就是使用访客模式来解决这个问题。我们可以定义一个如下的访客接口:
type visitor interface {
visitForSquare(square)
visitForCircle(circle)
visitForTriangle(triangle)
}
接口中的三个函数visitforSquare(square)、visitForTriangle(triangle)、visitForCircle(circle)允许我们分别为Square、Circle和Triangle三个struct分别添加函数。
现在可以开始考虑一个问题了:为什么我们不在visitor接口中添加一个visit(shape)方法,而是为每种形状单独写了一个visit方法?原因很简单:Go语言不支持。
接下来在shape接口中添加一个accept方法:
func accept(v visitor)
每一个实现shape的struct都需要定义这个方法。额,等等,我们刚才好像提到过不想修改现有的shape struct。但是要使用访客模式就不得不修改相关的shape struct,不过这些修改只需要做一次。假如我们还希望添加注入getNumSides()(获取边数)、getMiddleCoordinates()(获取中心坐标),此时就不需要再对相关的struct做任何调整了,可以直接使用前面定义的accept(v visitor)方法了。
基本上就是这样了,只需修改实现shape接口的struct一次,之后想再添加多少个额外的行为都可以使用同一个accept()方法。接下来看下具体是怎么做的。
让struct square实现一个accept()方法:
func (obj *squre) accept(v visitor){
v.visitForSquare(obj)
}
同样,circle和triangle也需要实现accept()方法。
现在想要添加getArea()方法的团队就可以实现visitor接口并在相关的方法中自行添加计算面积的逻辑:
如areaCalculator.go:
type areaCalculator struct{
area int
}
func (a *areaCalculator) visitForSquare(s *square){
//Calculate are for square
}
func (a *areaCalculator) visitForCircle(s *square){
//Calculate are for circle
}
func (a *areaCalculator) visitForTriangle(s *square){
//Calculate are for triangle
}
比如要计算正方形的面积,我们先创建一个square实例,然后进行如下简单的调用就可以了:
sq := &square{}
ac := &areaCalculator{}
sq.accept(ac)
同理,另一个想要获取形状中心坐标的团队也可以像前面那样自己实现visitor接口并添加相关的方法:
middleCoordinates.go:
type middleCoordinates struct {
x int
y int
}
func (a *middleCoordinates) visitForSquare(s *square) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}
func (a *middleCoordinates) visitForCircle(c *circle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}
func (a *middleCoordinates) visitForTriangle(t *triangle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}
UML类图
通过前面的说明,我们可以总结出访客模式的类图:

如下是前面的例子的类图:

代码说明
shape.go:
type shape interface {
getType() string
accept(visitor)
}
square.go:
type square struct {
side int
}
func (s *square) accept(v visitor) {
v.visitForSquare(s)
}
func (s *square) getType() string {
return "Square"
}
circle.go:
type circle struct {
radius int
}
func (c *circle) accept(v visitor) {
v.visitForCircle(c)
}
func (c *circle) getType() string {
return "Circle"
}
rectangle.go:
type rectangle struct {
l int
b int
}
func (t *rectangle) accept(v visitor) {
v.visitForRectangle(t)
}
func (t *rectangle) getType() string {
return "rectangle"
}
visitor.go:
type visitor interface {
visitForSquare(*square)
visitForCircle(*circle)
visitForRectangle(*rectangle)
}
areaCalculator.go:
type areaCalculator struct {
area int
}
func (a *areaCalculator) visitForSquare(s *square) {
//Calculate area for square. After calculating the area assign in to the area instance variable
fmt.Println("Calculating area for square")
}
func (a *areaCalculator) visitForCircle(s *circle) {
//Calculate are for circle. After calculating the area assign in to the area instance variable
fmt.Println("Calculating area for circle")
}
func (a *areaCalculator) visitForRectangle(s *rectangle) {
//Calculate are for rectangle. After calculating the area assign in to the area instance variable
fmt.Println("Calculating area for rectangle")
}
middleCoordinates.go:
type middleCoordinates struct {
x int
y int
}
func (a *middleCoordinates) visitForSquare(s *square) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for square")
}
func (a *middleCoordinates) visitForCircle(c *circle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for circle")
}
func (a *middleCoordinates) visitForRectangle(t *rectangle) {
//Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
fmt.Println("Calculating middle point coordinates for rectangle")
}
main.go:
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)
}
输出内容为:
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
代码已上传至GitHub: zhyea / go-patterns / visitor-pattern
END!!!
GoLang设计模式17 - 访客模式的更多相关文章
- C++设计模式:访客模式
访客模式:通俗的说, 就是定义一个访问者角色, 当对指定角色进行访问时要通过访问者进行访问. 访客模式的侵入性适中,仅在被访问的类里面加一个对外提供接待访问者的接口. 访客模式的优点: 符合单一职责原 ...
- GoLang设计模式3 - 抽象工厂模式
之前我们介绍了工厂设计模式,现在我们再看一下抽象工厂设计模式.抽象工程模式顾名思义就是对工厂模式的一层抽象,也是创建型模式的一种,通常用来创建一组存在相关性的对象. UML类图大致如下: 类图比较复杂 ...
- GoLang设计模式07 - 责任链模式
责任链模式是一种行为型设计模式.在这种模式中,会为请求创建一条由多个Handler组成的链路.每一个进入的请求,都会经过这条链路.这条链路上的Handler可以选择如下操作: 处理请求或跳过处理 决定 ...
- [设计模式] 17 中介者模式 Mediator Pattern
在GOF的<设计模式:可复用面向对象软件的基础>一书中对中介者模式是这样说的:用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变 ...
- GoLang设计模式10 - 中介者模式
中介者模式是一种行为型设计模式.在中介者模式中创建了一个中介对象来负责不同类间的通信.因为这些类不需要直接交互,所以也就能避免它们之间的直接依赖,实现解耦的效果. 中介者模式的一个典型案例是老式小火车 ...
- GoLang设计模式12 - 空对象模式
空对象设计模式是一种行为型设计模式,主要用于应对空对象的检查.使用这种设计模式可以避免对空对象进行检查.也就是说,在这种模式下,使用空对象不会造成异常. 空对象模式的组件包括: Entity:接口,定 ...
- JavaScript设计模式-17.装饰者模式(下)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- GoLang设计模式06 - 对象池模式
这次介绍最后一个创建型模式--对象池模式.顾名思义,对象池模式就是预先初始化创建好多个对象,并将之保存在一个池子里.当需要的时候,客户端就可以从池子里申请一个对象使用,使用完以后再将之放回到池子里.池 ...
- 设计模式之第17章-备忘录模式(Java实现)
设计模式之第17章-备忘录模式(Java实现) 好男人就是我,我就是曾小贤.最近陈赫和张子萱事件闹得那是一个沸沸扬扬.想想曾经每年都有爱情公寓陪伴的我现如今过年没有了爱情公寓总是感觉缺少点什么.不知道 ...
随机推荐
- Water 2.4 发布,一站式服务治理平台
Water(水孕育万物...) Water 为项目开发.服务治理,提供一站式解决方案(可以理解为微服务架构支持套件).基于 Solon 框架开发,并支持完整的 Solon Cloud 规范:已在生产环 ...
- [hdu4388]Stone Game II
不管是否使用技能,发现操作前后所有堆二进制中1的个数之和不变.那么对于一个堆其实可以等价转换为一个k个石子的堆(k为该数二进制的个数),然后就是个nim游戏. 1 #include<bits/s ...
- [luogu4548]歌唱王国
(可以参考hdu4652,因此推导过程比较省略) 类似的定义$f_{i}$和$g_{i}$,同样去插入$len$个字符,但注意到并不是任意一个位置都可以作为结尾,$i+j$可以作为结尾当且仅当$s[0 ...
- Pickle的简单用法
Python中pickle的用法 pickle存在的意义 在python的文件操作里面,我们常常需要将python容器里面的一些东西把它写成一个二进制文件存放在硬盘里面来永久保存. 在不借助pickl ...
- 最简单的Python3启动浏览器代码
#encoding=utf-8 from selenium import webdriver import time from time import sleep dr = webdriver.F ...
- 洛谷 P4663 - [BalticOI 2008]魔法石(dp)
题面传送门 A:我该是有多无聊来写这种题的题解啊 B:大概是因为这题题解区里没有题解所以我来写一篇了,说明我有高尚的济世情怀(大雾 跑题了跑题了 首先看到字典序第 \(i\) 小小可以自然地想到按位决 ...
- R语言实战-Part 2笔记
R 语言实战(第二版) part 2 基本方法 -------------第6章 基本图形------------------ #1.条形图 #一般是类别型(离散)变量 library(vcd) he ...
- C/C++ Qt StatusBar 底部状态栏应用
Qt窗体中默认会附加一个QstatusBar组件,状态栏组件位于主窗体的最下方,其作用是提供一个工具提示功能,当程序中有提示信息是可以动态的显示在这个区域内,状态栏组件内可以增加任何Qt中的通用组件, ...
- LetNet、Alex、VggNet分析及其pytorch实现
简单分析一下主流的几种神经网络 LeNet LetNet作为卷积神经网络中的HelloWorld,它的结构及其的简单,1998年由LeCun提出 基本过程: 可以看到LeNet-5跟现有的conv-& ...
- 【Redis集群原理专题】分析一下相关的Redis集群模式下的脑裂问题!
技术格言 世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程. 什么是脑裂 字面含义 首先,脑裂从字面上理解就是脑袋裂开了,就是思想分家了,就是有了两个山头,就是有了 ...