Tech Tradeoff: Visitor pattern
Tags: software
Refactoring guru: Visitor Pattern
Params
We would weigh the following things.
- How much code change is required?
- What if we want another method to export to JSON? What if we want another method to count population?
- What if we want to add another type of geographical object? Let’s say we want to add a Mansion?
- How easy it is to use?
- a) If you have a list of geographical nodes and a list of operations(Export to JSON, Export to CSV, Count population) you want to perform on them.
Approaches
⚖️ Approach 1: Add a method to the Base class
Add a method exportToXML
to the Base class.
Cost:
- Have to make changes to already working, class code, which may break existing functionality. (This may happen because the method would have access to private member variables and may change them or call other methods of the class which may cause side effects)
- Base class can become bloated. Can make it confusing to work with the code.
- For some programming languages, each method would need a recompile for all the classes
- If you need to make another method to export to JSON, then we would need to again make changes to the existing class.
Benefit:
- Adding more types of geographical objects like a Mansion is easy. We won’t need to change the underlying algorithm or the class code.
⚖️ Approach 2: Make a use-case specific function
Here our use-case is to exportToXML
and so we would make such a function. The function would take a root node which has all the children nested inside it.
Cost:
- If you need to make another method to export to JSON, then we would need to make a new function again.
- For making sure the function can access the private variables we might have to break encapsulation for the class. This might also open up the private variables to other functions. Not a problem with Approach 1.
- Adding new objects is hard. For each new object we have to change the method. Let’s say we have three functions
exportToXML
, exportToJSON
, exportToCSV
. Each of them would need to change.
Benefit:
- Adding more functions is easy.
- No need to change class code.
⚖️ Approach 3: Apply visitor pattern
The visitor pattern is almost similar to approach 2 but has some costs and benefits.
Cost:
- Extra configuration needed when adding a new type of object than Approach 2.
Benefit:
- More consistent API than approach 2. Let’s say we have a list of objects(
[City, Town]
) we want to perform a list of operations on it ([ExportToXMLVisitor, ExportToCSVVisitor]
). We can easily iterate through the list of objects and call the .accept(Visitor)
on them by passing in visitors from the operations list. This may not be possible if we had a list of functions [exportToXML, exportObjectsCSV]
. Eg. the parameter that each function would take may differ. exportToXML
and exportToCSv
might have a signature as
exportToXML(object: geographicalNode)
exportToCSV(string: separator, geographicalNode)
This might happen because to export to CSV we might want a different separator than usual which is a comma ,
. Hence there would be incosistency in the API which we would need to handle while iterating. This isn’t a problem with Visitor since we can pass separator to the ExportToCSVVisitor
in the constructor.
Conclusion
- There is a tradeoff decision. If the number of objects are more and we add more objects frequently then favor approach 1. Else favor approach 2 and 3 appropriately.