一文搞懂访问者设计模式

71 阅读2分钟

设计模式的提出已经有很长时间了,他可以帮助我们更好的组织代码和结构,也许有的人对此不屑一顾,认为代码是基于演化的而不应该囿于模式,但实际上随着代码规模的增大,如果我们还追求良好的维护性、扩展性,那设计模式是不可或缺的,今天就来介绍一种和变更有关的设计模式。

假设你维护一个人员管理系统,里面有各种类型人员,比如总经理、行政、商务。假设其他团队要求你提供一个获取人员考勤的实现,那么你该如何实现?

第一,将 getDays 方法添加到接口,然后每个类型都去实现,这似乎是比较好的解决方法,但是代价也比较高,你需要改动每一个类型的实现,你肯定不希望只有有需求就改动自己的宝贝代码吧

第二,让需求方自己实现,但是这并不总是可行的

第三,就是使用访问者模式实现

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

type visitor interface {
    visitForManage(manage)
    visitForAdmin(admin)
    visitForMarket(market)
}

我们可以使用上面接口中的方法来为总经理、行政、商务添加相应的功能。

你可能在想, 为什么我们不再访问者接口里面使用单一的 visit(people)方法呢? 这是因为 Go 语言不支持方法重载, 所以你无法以相同名称、 不同参数的方式来使用方法。

好了, 第二项重要的工作是将 accept接受方法添加至人员接口中。

func accept(v visitor)

所有形状结构体都需要定义此方法, 类似于:

func (obj * manage) accept(v visitor){
    v.visitForManage(obj)
}

但是我们不是不希望修改现有的实现吗? 确实是这样,但是在使用访问者模式时, 我们必须要修改结构体。 但这样的修改只需要进行一次。

如果后续添加任何其他行为, 我们将使用相同的 accept(v visitor)函数, 而无需对结构体进行进一步的修改。我们只需简单地定义访问者接口的具体实现即可。

接下来就看一下具体的代码实现吧

people.go:  元件

package main

type People interface {
    getType() string
    accept(Visitor)
}

 mamage.go:  具体元件

package main

type Manage struct {
    name string
}

func (s *Manage) accept(v Visitor) {
    v.visitForManage(s)
}

func (s *Manage) getType() string {
    return "Manage"
}

 admin.go:  具体元件

package main

type Admin struct {
    name string
}

func (c *Admin) accept(v Visitor) {
    v.visitForAdmin(c)
}

func (c *Admin) getType() string {
    return "Admin"
}

 market.go:  具体元件

package main

type Market struct {
    name string
}

func (t *Market) accept(v Visitor) {
    v.visitForMarket(t)
}

func (t *Market) getType() string {
    return "Market"
}

visitor.go:  访问者

package main

type Visitor interface {
    visitForManage(*Manage)
    visitForAdmin(*Admin)
    visitForMarket(*Market)
}

 daysCalculator.go:  具体访问者

package main

import (
    "fmt"
)

type DaysCalculator struct {
    days int
}

func (a *DaysCalculator) visitForManage(*Manage) {
    fmt.Println("Calculating visitForManage")
}

func (a *DaysCalculator) visitForAdmin(*Admin) {
    fmt.Println("Calculating visitForAdmin")
}
func (a *DaysCalculator) visitForMarket(*Market) {
    fmt.Println("Calculating visitForMarket")
}