GoLang设计模式17 - 访客模式

GoLang设计模式17 - 访客模式

说明

访客模式是一种行为型设计模式。通过访客模式可以为struct添加方法而不需要对其做任何调整

来看一个例子,假如我们需要维护一个对如下形状执行操作的库:

  1. 方形(Square)
  2. 圆形(Circle)
  3. 长方形(Rectangle)

以上图形的struct都继承自一个共同的shape接口。公司内有多个团队都在使用这个库。假设现在有一个团队想要为这些图形struct添加一个获取面积的方法(getArea())。有如下几种方法可以解决这种问题。

方法一

第一种方案就是直接在shape接口中添加getArea()方法。这样实现shape接口的每个struct都需要实现getArea()方法。这个方案看起来可行,但是却有一些问题:

  1. 作为一个公用库的维护者,有时候不想为了添加一个额外的行为就调整已经做过严格测试的代码
  2. 使用这个库的团队可能会提出添加更多行为的请求,比如getNumSides()getMiddleCoordinates()。面对这种情况,我们通常都不想持续修改这个库,而是希望这些团队继承我们这个库,自己实现自己的需求。

方法二

第二个方案就是由提出需求的团队自己实现相关的行为逻辑。要获取shape的面积就可以根据struct的类型作如下的实现:

?123456789if 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}

以上的代码仍然是有问题的:这种方案不能充分利用接口的特性,反而还需要添加额外的类型来检查代码,造成整体结构的脆弱。此外,在运行时获取对象的类型可能会存在一些性能问题,在一些语言中甚至还不能获取对象的类型。

方法三

第三种方案就是使用访客模式来解决这个问题。我们可以定义一个如下的访客接口:

?12345678type visitor interface { visitForSquare(square) visitForCircle(circle) visitForTriangle(triangle)}

接口中的三个函数visitforSquare(square)visitForTriangle(triangle)visitForCircle(circle)允许我们分别为SquareCircleTriangle三个struct分别添加函数。

现在可以开始考虑一个问题了:为什么我们不在visitor接口中添加一个visit(shape)方法,而是为每种形状单独写了一个visit方法?原因很简单:Go语言不支持

接下来在shape接口中添加一个accept方法:

?1func accept(v visitor)

每一个实现shape的struct都需要定义这个方法。额,等等,我们刚才好像提到过不想修改现有的shape struct。但是要使用访客模式就不得不修改相关的shape struct,不过这些修改只需要做一次。假如我们还希望添加注入getNumSides()(获取边数)、getMiddleCoordinates()(获取中心坐标),此时就不需要再对相关的struct做任何调整了,可以直接使用前面定义的accept(v visitor)方法了。

基本上就是这样了,只需修改实现shape接口的struct一次,之后想再添加多少个额外的行为都可以使用同一个accept()方法。接下来看下具体是怎么做的。

让struct square实现一个accept()方法:

?123func (obj *squre) accept(v visitor){ v.visitForSquare(obj)}

同样,circletriangle也需要实现accept()方法。

现在想要添加getArea()方法的团队就可以实现visitor接口并在相关的方法中自行添加计算面积的逻辑:

如areaCalculator.go:

?12345678910111213type 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实例,然后进行如下简单的调用就可以了:

?123sq := &square{}ac := &areaCalculator{}sq.accept(ac)

同理,另一个想要获取形状中心坐标的团队也可以像前面那样自己实现visitor接口并添加相关的方法:

middleCoordinates.go:

?12345678910111213141516type 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:

?123456type shape interface { getType() string accept(visitor)}

square.go:

?1234567891011type square struct { side int} func (s *square) accept(v visitor) { v.visitForSquare(s)} func (s *square) getType() string { return "Square"}

circle.go:

?1234567891011type circle struct { radius int} func (c *circle) accept(v visitor) { v.visitForCircle(c)} func (c *circle) getType() string { return "Circle"}

rectangle.go:

?123456789101112type rectangle struct { l int b int} func (t *rectangle) accept(v visitor) { v.visitForRectangle(t)} func (t *rectangle) getType() string { return "rectangle"}

visitor.go:

?12345678type visitor interface { visitForSquare(*square) visitForCircle(*circle) visitForRectangle(*rectangle)}

areaCalculator.go:

?123456789101112131415161718type 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:

?12345678910111213141516171819type 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:

?12345678910111213141516func 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)}

输出内容为:

?1234567Calculating area for squareCalculating area for circleCalculating area for rectangle Calculating middle point coordinates for squareCalculating middle point coordinates for circleCalculating middle point coordinates for rectangle

代码已上传至GitHub: zhyea / go-patterns / visitor-pattern

END!!!

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部