Visitor pattern moves some logic of certain types of objects to an external entity(named the visitor), and that external entity/visitor is responsible for performing operations on the object. This pattern separates the logic to work with certain types of objects.
This article demonstrates Visitor pattern implementations in GoLang. Check the following examples.
Implementation
Here is a simple example of the Visitor pattern implementation in GoLang-
// Visitor pattern implementation in Go
package main
import "fmt"
type IElement interface {
visit(visitor IVisitor)
}
// First element
type Element1 struct {
content string
}
func NewElement1(content string) (element *Element1) {
element = &Element1{}
element.content = content
return
}
func (element *Element1) getContent() string {
return element.content
}
func (element *Element1) visit(visitor IVisitor) {
visitor.addContent(element)
}
// Second element
type Element2 struct {
content string
addMargin bool
}
func NewElement2(content string, addMarring bool) (element *Element2) {
element = &Element2{}
element.content = content
element.addMargin = addMarring
return
}
func (element *Element2) getContent() string {
return element.content
}
func (element *Element2) hasMargin() bool {
return element.addMargin
}
func (element *Element2) visit(visitor IVisitor) {
visitor.addContentWithMargin(element)
}
// Visitor interface
type IVisitor interface {
addContent(element *Element1)
addContentWithMargin(element *Element2)
}
type Visitor struct {
Output string
}
func NewVisitor() (visitor *Visitor) {
visitor = &Visitor{}
return
}
func (visitor *Visitor) addContent(element *Element1) {
visitor.Output += element.getContent()
}
func (visitor *Visitor) addContentWithMargin(element *Element2) {
if element.hasMargin() {
visitor.Output += "[margin]" + element.getContent() + "[/margin]"
} else {
visitor.Output += element.getContent()
}
}
// Demo
func main() {
// list of elements
elements := []IElement{
NewElement1("Element1: first element"),
NewElement2("Element2: second element without margin", false),
NewElement2("Element2: third element with margin", true),
NewElement1("Element1: last element"),
}
visitor := NewVisitor()
for _, element := range elements {
element.visit(visitor)
}
fmt.Println(visitor.Output)
}
Output will be as below-
Element1: first element
Element2: second element without margin
[margin]Element2: third element with margin[/margin]
Element1: last element
Examples
Here are a few examples of Visitor pattern implements in Golang-
Example #1: Hosting Cost Calculator
Let’s consider an example of a hosting service, and calculate the total cost of the hosting service used using the visitor pattern.
Service Interface
// service.go
package main
type Service interface {
Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64
}
Compute Service Struct
// compute_service.go
package main
type ComputeService struct {
price float64
quantity int
}
func NewComputeService(quantity int) (compute *ComputeService) {
compute = &ComputeService{}
compute.price = 10.50
compute.quantity = quantity
return
}
func (compute *ComputeService) GetPrice() float64 {
return compute.price
}
func (compute *ComputeService) GetQuantity() int {
return compute.quantity
}
func (compute *ComputeService) Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64 {
return hostingCalculatorVisitor.ComputeVisit(compute)
}
Database Service Struct
// database_service.go
package main
type DatabaseService struct {
price float64
backPrice float64
quantity int
backupEnabled bool
}
func NewDatabaseService(quantity int) (databaseService *DatabaseService) {
databaseService = &DatabaseService{}
databaseService.price = 100.00
databaseService.backPrice = 30.00
databaseService.backupEnabled = false
databaseService.quantity = quantity
return
}
func NewDatabaseService2(quantity int, backupEnabled bool) (databaseService *DatabaseService) {
databaseService = &DatabaseService{}
databaseService.price = 100.00
databaseService.backPrice = 30.00
databaseService.quantity = quantity
databaseService.backupEnabled = backupEnabled
return
}
func (databaseService *DatabaseService) GetBackPrice() float64 {
return databaseService.backPrice
}
func (databaseService *DatabaseService) GetPrice() float64 {
return databaseService.price
}
func (databaseService *DatabaseService) GetQuantity() int {
return databaseService.quantity
}
func (databaseService *DatabaseService) IsBackupEnabled() bool {
return databaseService.backupEnabled
}
func (databaseService *DatabaseService) Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64 {
return hostingCalculatorVisitor.DatabaseVisit(databaseService)
}
File Storage Service Struct
// file_storage_service.go
package main
type FileStorageService struct {
pricePerGB float64
quantity int
}
func NewFileStorageService(quantity int) (fileStorageService *FileStorageService) {
fileStorageService = &FileStorageService{}
fileStorageService.pricePerGB = 1.70
fileStorageService.quantity = quantity
return
}
func (fileStorageService *FileStorageService) GetPricePerGB() float64 {
return fileStorageService.pricePerGB
}
func (fileStorageService *FileStorageService) GetQuantity() int {
return fileStorageService.quantity
}
func (fileStorageService *FileStorageService) Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64 {
return hostingCalculatorVisitor.FileVisit(fileStorageService)
}
Serverless Service Struct
// serverless_service.go
package main
type ServerlessService struct {
hourlyPrice float64
totalHours int
}
func NewServerlessService(totalHours int) (serverlessService *ServerlessService) {
serverlessService = &ServerlessService{}
serverlessService.hourlyPrice = 0.32
serverlessService.totalHours = totalHours
return
}
func (serverlessService *ServerlessService) GetHourlyPrice() float64 {
return serverlessService.hourlyPrice
}
func (serverlessService *ServerlessService) GetTotalHours() int {
return serverlessService.totalHours
}
func (serverlessService *ServerlessService) Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64 {
return hostingCalculatorVisitor.ServerlessVisit(serverlessService)
}
Container Service Struct
// container_servie.go
package main
type ContainerService struct {
price float64
quantity int
}
func NewContainerService(quantity int) (containerService *ContainerService) {
containerService = &ContainerService{}
containerService.price = 5.60
containerService.quantity = quantity
return
}
func (containerService *ContainerService) GetPrice() float64 {
return containerService.price
}
func (containerService *ContainerService) GetQuantity() int {
return containerService.quantity
}
func (containerService *ContainerService) Accept(hostingCalculatorVisitor HostingCalculatorVisitor) float64 {
return hostingCalculatorVisitor.ContainerVisit(containerService)
}
Visitor Interface
// hosting_calculator_visitor.go
package main
type HostingCalculatorVisitor interface {
ComputeVisit(computeService *ComputeService) float64
ContainerVisit(containerService *ContainerService) float64
DatabaseVisit(databaseService *DatabaseService) float64
FileVisit(fileStorageService *FileStorageService) float64
ServerlessVisit(serverlessService *ServerlessService) float64
}
Visitor Interface Implementation
// hosting_calculator_visitor_impl.go
package main
type HostingCalculatorVisitorImpl struct{}
func NewHostingCalculatorVisitorImpl() (hcvi *HostingCalculatorVisitorImpl) {
hcvi = &HostingCalculatorVisitorImpl{}
return
}
func (hcvi *HostingCalculatorVisitorImpl) ComputeVisit(computeService *ComputeService) float64 {
return computeService.GetPrice() * float64(computeService.GetQuantity())
}
func (hcvi *HostingCalculatorVisitorImpl) ContainerVisit(containerService *ContainerService) float64 {
return containerService.GetPrice() * float64(containerService.GetQuantity())
}
func (hcvi *HostingCalculatorVisitorImpl) DatabaseVisit(databaseService *DatabaseService) float64 {
serviceCost := databaseService.GetPrice() * float64(databaseService.GetQuantity())
backupCost := 0.00
if databaseService.IsBackupEnabled() {
backupCost = databaseService.GetBackPrice() * float64(databaseService.GetQuantity())
}
return serviceCost + backupCost
}
func (hcvi *HostingCalculatorVisitorImpl) FileVisit(fileStorageService *FileStorageService) float64 {
return fileStorageService.GetPricePerGB() * float64(fileStorageService.GetQuantity())
}
func (hcvi *HostingCalculatorVisitorImpl) ServerlessVisit(serverlessService *ServerlessService) float64 {
return serverlessService.GetHourlyPrice() * serverlessService.GetHourlyPrice()
}
Demo
// main.go
package main
import "fmt"
func main() {
usedServices := []Service{
NewComputeService(3),
NewDatabaseService2(3, true),
NewFileStorageService(120),
NewServerlessService(720),
NewContainerService(2),
}
totalCost := calculateHostingCost(usedServices)
fmt.Printf("Total cost of hosting is: %f", totalCost)
}
func calculateHostingCost(services []Service) float64 {
hostingCalculatorVisitorImpl := NewHostingCalculatorVisitorImpl()
totalCost := 0.00
for _, service := range services {
totalCost += service.Accept(hostingCalculatorVisitorImpl)
}
return totalCost
}
Output
Total cost of hosting is: 636.802400
Example #2: UI Elements
Here is another example of visitor pattern. We are printing some UI elements(dummy) here, to demonstrate how can this pattern be used to easily print UI elements.
UI Element Interface
// ui_element.go
package main
type UIElement interface {
AppendElement(vistor Visitor)
}
// This is not directly relevent to the visitor pattern
// This is used to match certain param type in the implementation
type IWrapperElement interface {
GetText() string
GetWrapper() string
}
Text Element Struct
// text_element.go
package main
type TextElement struct {
text string
}
func NewTextElement(text string) (textElement *TextElement) {
textElement = &TextElement{}
textElement.text = text
return
}
func (textElement *TextElement) GetText() string {
return textElement.text
}
func (textElement *TextElement) AppendElement(visitor Visitor) {
visitor.AppendContent(textElement)
}
Wrap Element Struct
// wrap_element.go
package main
type WrapElement struct {
text string
wrapper string
}
func NewWrapElement(text string, wrapper string) (wrapElement *WrapElement) {
wrapElement = &WrapElement{}
wrapElement.text = text
wrapElement.wrapper = wrapper
return
}
func (wrapElement *WrapElement) GetText() string {
return wrapElement.text
}
func (wrapElement *WrapElement) GetWrapper() string {
return wrapElement.wrapper
}
func (wrapElement *WrapElement) AppendElement(visitor Visitor) {
visitor.AppendContentWithWrapper(wrapElement)
}
Head Element Struct
// head_element.go
package main
type HeadElement struct {
wrapper string
text string
}
func NewHeadElement(text string) (headElement *HeadElement) {
headElement = &HeadElement{}
headElement.wrapper = "h1"
headElement.text = text
return
}
func (headElement *HeadElement) GetText() string {
return headElement.text
}
func (headElement *HeadElement) GetWrapper() string {
return headElement.wrapper
}
func (headElement *HeadElement) AppendElement(visitor Visitor) {
visitor.AppendContentWithWrapper(headElement)
}
List Element Struct
// list_element.go
package main
type ListElement struct {
lines []string
}
func NewListElement(lines []string) (listElement *ListElement) {
listElement = &ListElement{}
listElement.lines = lines
return
}
func (listElement *ListElement) GetListItems() []string {
return listElement.lines
}
func (listElement *ListElement) AppendElement(visitor Visitor) {
visitor.AppendList(listElement)
}
Visitor Interface
// visitor.go
package main
type Visitor interface {
AppendContent(textElement *TextElement)
AppendContentWithWrapper(wrapElement IWrapperElement)
AppendList(listElement *ListElement)
}
Visitor Interface Implementation
// element_visitor
package main
import "fmt"
type ElementVisitor struct {
output string
}
func NewElementVisitor() (elementVsitor *ElementVisitor) {
elementVsitor = &ElementVisitor{}
return
}
func (elementVisitor *ElementVisitor) AppendContent(textElement *TextElement) {
elementVisitor.output += textElement.text
}
func (elementVisitor *ElementVisitor) AppendContentWithWrapper(wrapElement IWrapperElement) {
elementVisitor.output += fmt.Sprintf("[%s]%s[/%s]", wrapElement.GetWrapper(), wrapElement.GetText(), wrapElement.GetWrapper())
}
func (elementVisitor *ElementVisitor) AppendList(listElement *ListElement) {
elementVisitor.output += "[ul]"
for _, item := range listElement.GetListItems() {
elementVisitor.output += fmt.Sprintf("[li]%s[/li]", item)
}
elementVisitor.output += "[/ul]"
}
Demo
// main.go
package main
import "fmt"
func main() {
// set the list of elements we want print
uiElements := []UIElement{
NewHeadElement("My Heading"),
NewTextElement("First line of text"),
NewListElement([]string{"abc", "def", "ghi", "jkl"}),
NewWrapElement("Content wrapped with div", "div"),
NewTextElement("Last line of text"),
}
visitor := NewElementVisitor()
for _, element := range uiElements {
element.AppendElement(visitor)
}
// let"s check the output
fmt.Println(visitor.output)
}
Output
[h1]My Heading[/h1]
First line of text
[ul]
[li]abc[/li]
[li]def[/li]
[li]ghi[/li]
[li]jkl[/li]
[/ul]
[div]Content wrapped with div[/div]
Last line of text
Source Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check Visitor pattern implementation in other programming languages.