{{announcement.body}}
{{announcement.title}}

Go Visitor Pattern

DZone 's Guide to

Go Visitor Pattern

In this article, we discuss how to implement the Visitor Pattern in Go to only override the methods we need in a given subclass.

· Web Dev Zone ·
Free Resource

Summary

One feature that Go does not offer that is really useful in visitor patterns is overriding methods. The basic idea is to write a concrete class that contains all the VisitX methods with empty implementations, and a subclass can choose to only override the methods it cares about, ignoring the rest.

We'll see an example of how to implement this pattern in idiomatic Go code.

Example Using Method Overrides

The code for a visitor pattern would look something like this in pseudo-Java code:

Java
 







Writing a Visitor in Go

Since Go doesn't allow overrides, we need to figure out how we can create CustomersPrinter, bearing in mind the following considerations:

  • Visitor implementations should only have to implement the methods they need.
  • If new visit methods are added, and existing visitor implementations should compile and work without changes.
  • Go does not have generics, and it would be nice if Result() did not return interface{}, but the actual type.

One way to solve this is as follows:

  • Declare one interface per visitor method.
  • Declare an interface that combines all the single method interfaces into one interface of all methods.
  • There is no interface for the result method.
  • One of the single method interfaces is for an init method of no arguments, allowing a visiter instance to be reused any number of times.
  • Declare an adapter struct that takes an instance of the single method init interface, and adapts it to the interface of all methods, using empty implementations for anything not implemented.
  • Visitor implementations just implement the init interface and whatever subset of other single method interfaces are needed.
  • The walker does not provide a result method — the visitor does, so that it can return it as the desired type.
  • The caller does the following:
    • Create a visitor instance that implements a subset of single method interfaces.
    • Use the adapter to adapt the visitor into an implementation of all interfaces.
    • Create a walker with the adapter visitor and use it to walk an instance.

Example of Go Code:

  • AllCustomers is the type to walk.
  • AllCustomersVisitorInit is the init method interface.
  • AllCustomersVisitor is the interface that combines all single method interfaces into one.
  • The remaining *Visitor interfaces are the other single method interfaces.
  • CustomerVisitorAdapter is the adapter.
  • CustomerVisitorAdapter can reuse the same underlying visitor, or you can change the visitor between walks.
  • CustomersPrinter is the visitor that only implements a subset of single method interfaces that needs to be adapted.
  • AllCustomersDepthFirstWalker is the walker that expects a full implementation of all visitor methods.

Run it at Go playground.

Go
 







Maintenance

Over time, new fields will be added to the data structure that we need to walk over.

This results in the following additional changes:

  • New single method interace(s) for each new fields.
  • Addition of above new interfaces to AllCustomersVisitor interface.
  • Addition of new interface methods to NewCustomerVisitorAdapter.

We can create a unit test that uses the AllCustomersDepthFirstWalker to test a particular visitor, such as the CustomersPrinter above.

Since Go uses duck typing for interfaces, if we add new interfaces and forget to put them in the adapter, Go won't complain about the adapter because it has no way of knowing it is supposed to implement all the single method interfaces.

If we pass a constructed instance of our adapter to the walker, the walker expects an instance of AllCustomersVisitor.

This means if we forget to add new interface methods to the adapter, our unit test will fail to compile on the call to NewAllCustomersDepthFirstWalker.

Technically, we could also forget to add the new interfaces to AllCustomersVisitor, and no compile error would occur.

Realistically, as long we addd unit tests to verify the new methods work in a visitor that uses them, the updated test won't pass until all affected types are updated.

Conclusion

The Go version isn't drastically different from the Java version. We just need to maintain an extra struct that adapts a subset of all visitor methods into a full implementation.

Beyond that, the number of lines of code to implement an actual visitor and use it to walk a data structure is similar to Java.

Adding new method(s) will not take noticeably longer than in a language that supports method overrides.

All three goals set out for the Go code have been met:

  • Visitors only have to implement the minimum set of single method interfaces needed to perform an operation.
  • Existing visitor implementations are not affected by the addition of new single method interfaces (only the adapter has to handle them).
  • We don't need generics in Go. We can simply simply not declare any result interface and let each visitor implementation declare its own result method with the correct type.

In addition to the above, we just use ordinary unit testing to ensure the adapter is correctly maintained.

Topics:
go development ,java ,tutorial ,visitor design pattern ,web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}