Go言語でVisitorパターンを実装してみる

ZOZO Advent Calendar 2024 25日目の記事になります。今回はVisitorパターンについて簡単に紹介したいと思います。

Visitorパターンについて

Visitor パターンは、オブジェクト指向プログラミング およびソフトウェア工学 において、 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。

wikiから引用

Visitor パターン - Wikipedia

説明だけでは実装イメージがつきにくいのですが、重要なのは既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる部分かと思います。
オブジェクトを郵便の配達物に見立てると、配達物を配達するような処理を間に挟む感覚が近いのかなと思います。

実装例

実装の例として、受け取った文字列をJSON、またはYAMLに変換するロジックを実装します。

まず最初にVisitorインタフェースを定義します。

type Visitor interface {
    VisitParseJSON(*ParseJSON)
    VisitParseYAML(*ParseYAML)
}

次がJSONに変換する処理になります。

type ParseJSON struct {
    Name   string
    Sample string
}

func (p *ParseJSON) Accept(visitor Visitor) {
    visitor.VisitParseJSON(p)
}

type RawData struct {
    Data []byte
}

func (c *RawData) VisitParseJSON(p *ParseJSON) {
    var ParseJSON ParseJSON
    json.Unmarshal(c.Data, &ParseJSON)
    marshalJSON, err := json.Marshal(ParseJSON)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(marshalJSON))
}

次にYAMLに変換する処理になります。

type ParseYAML struct {
    Name   string `yaml:"Name"`
    Sample string `yaml:"Sample"`
}

func (p *ParseYAML) Accept(visitor Visitor) {
    visitor.VisitParseYAML(p)
}

func (c *RawData) VisitParseYAML(p *ParseYAML) {
    var ParseYAML ParseYAML
    yaml.Unmarshal(c.Data, &ParseYAML)
    marshalYaml, err := yaml.Marshal(ParseYAML)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(marshalYaml))
}

上記でJSON、またはYAMLの変換ロジックが完成したのでこの関数の呼び出し部分を作成します。

func main() {
    jsonData := `{"Name": "json_value", "Sample": "bar"}`

    rawData := RawData{Data: []byte(jsonData)}
    parseJSON := ParseJSON{}
    parseJSON.Accept(&rawData)

    ParseYAML := ParseYAML{}
    ParseYAML.Accept(&rawData)

}

上記の呼び出し方法より、JSONの変換やYAMLの変換ロジックの呼び出しはAcceptメソッドを介して行われます。これにより新しく別の変換処理を行いたい場合はVisitorインタフェース内にメソッドを追加し、処理を追加するだけで済みます。

下記が処理の全体コードになります。

package main

import (
    "encoding/json"
    "fmt"

    "gopkg.in/yaml.v3"
)

type Element interface {
    Accept(visitor Visitor)
}

type Visitor interface {
    VisitParseJSON(*ParseJSON)
    VisitParseYAML(*ParseYAML)
}

type ParseJSON struct {
    Name   string
    Sample string
}

func (p *ParseJSON) Accept(visitor Visitor) {
    visitor.VisitParseJSON(p)
}

type RawData struct {
    Data []byte
}

func (c *RawData) VisitParseJSON(p *ParseJSON) {
    var ParseJSON ParseJSON
    json.Unmarshal(c.Data, &ParseJSON)
    marshalJSON, err := json.Marshal(ParseJSON)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(marshalJSON))
}

type ParseYAML struct {
    Name   string `yaml:"Name"`
    Sample string `yaml:"Sample"`
}

func (p *ParseYAML) Accept(visitor Visitor) {
    visitor.VisitParseYAML(p)
}

func (c *RawData) VisitParseYAML(p *ParseYAML) {
    var ParseYAML ParseYAML
    yaml.Unmarshal(c.Data, &ParseYAML)
    marshalYaml, err := yaml.Marshal(ParseYAML)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(marshalYaml))
}

func main() {
    jsonData := `{"Name": "json_value", "Sample": "bar"}`
         fmt.Println("Convert JSON")
    rawData := RawData{Data: []byte(jsonData)}
    parseJSON := ParseJSON{}
    parseJSON.Accept(&rawData)

         fmt.Println("Convert YAML")
    ParseYAML := ParseYAML{}
    ParseYAML.Accept(&rawData)

}

実行結果は下記になります。

$ go run  main.go
Convert JSON
{"Name":"json_value","Sample":"bar"}
Convert YAML
Name: json_value
Sample: bar

まとめ

Visitorパターンを簡単に紹介いたしました。作成するプログラムに対して機能追加が増えそうな場合は予めVisitorパターンを利用すると機能の追加が行いやすくなるので選択肢として実装方法を知っておくと役に立つかなと思いました。