This article is our title " Java Design Patterns ” part of the Academy curriculum.
In this course, you will delve into a large number of design patterns and learn how to implement and utilize them in Java. You'll learn why patterns are so important, and learn when and how to apply each of them. Check it out here !
1 Introduction
To learn about visitor design patterns, let's revisit" "Composite Design Patterns" . Composition mode allows you to group objects into a tree structure to represent parts of a whole hierarchy.
In the Composite Pattern example, we have created an html structure composed of different types of objects. Now suppose we need to add a css class in the html tag. One way is to add the class when adding the start tag using the setStartTag method. However, this hard-coded setup creates rigidity in our code.
Another way is to add a new method like addClass in the parent abstract HtmlTag class. All subclasses will override this method and will provide the css class. A major disadvantage of this approach is that if there are many subclasses (that will be displayed in large html pages), implementing this method in all subclasses becomes very expensive and hectic. And suppose, later on we need to add another style element to the label, we need to do the same again.
The Visitor design pattern gives you a way to add new actions on objects without changing the element class, especially when actions change frequently.
2. What is the visitor design pattern
The purpose of the visitor design pattern is to express actions to be performed on elements of an object structure. Visitors let you define new actions without changing the class of the element they operate on.
The Visitor pattern is useful when designing operations across heterogeneous collections of objects in a class hierarchy. The visitor pattern allows operations to be defined without changing the classes of any objects in the collection. For this, the Visitor pattern recommends defining the operation in a separate class called the visitor class. This separates the operation from the collection of objects it operates on. For each new operation to be defined, a new visitor class is created. Since the operation will be performed on a set of objects, the visitor needs a way to access the public members of those objects. This requirement can be met by implementing the following two design ideas.
tourists
- Declare a Visit operation for each type of ConcreteElement in the object structure. The name and signature of the action identify the Visit request sent to the visitor. This allows the visitor to determine the specific class of elements to visit. The visitor can then access the element directly through its specific interface.
specific visitor
- Implement each operation declared by the Visitor. Each operation is implemented as a fragment of the algorithm defined for the corresponding object class in the structure. ConcreteVisitor provides the context of the algorithm and stores its local state. This state typically accumulates results during traversal of the structure.
element
- Defines the Accept action that takes the visitor as a parameter.
ConcreteElement
- Implements the Accept operation that takes the visitor as a parameter.
object structure
- Its elements can be enumerated.
- A high-level interface can be provided to allow visitor s to access its elements.
- Can be a combination or set, such as a list or set.
3. Implement the Visitor Design Pattern
To implement the Visitor design pattern, we will use the same Composite Pattern Code , and will introduce some new interfaces, classes and methods to it.
Implementing the Visitor mode requires two important interfaces, an Element interface, which will contain an accept method with a parameter of type Visitor. All classes that need to allow visitor access will implement this interface. In our case, HtmlTag will implement the Element interface, because HtmlTag is the parent abstract class of all concrete html classes, so the concrete classes will inherit and override the accept method of the Element interface.
Another important interface is the Visitor interface; this interface will contain a visit method with parameters for a class that implements the Element interface. Note also that, contrary to the example shown in the Composite Design Pattern course, we have added two new methods to the HtmlTag class, getStartTag and getEndTag.
package com.javacodegeeks.patterns.visitorpattern; public interface Element { public void accept(Visitor visitor); } package com.javacodegeeks.patterns.visitorpattern; public interface Visitor { public void visit(HtmlElement element); public void visit(HtmlParentElement parentElement); }
The code below is from the Composite Pattern example with some changes.
package com.javacodegeeks.patterns.visitorpattern; import java.util.List; public abstract class HtmlTag implements Element{ public abstract String getTagName(); public abstract void setStartTag(String tag); public abstract String getStartTag(); public abstract String getEndTag(); public abstract void setEndTag(String tag); public void setTagBody(String tagBody){ throw new UnsupportedOperationException("Current operation is not support for this object"); } public void addChildTag(HtmlTag htmlTag){ throw new UnsupportedOperationException("Current operation is not support for this object"); } public void removeChildTag(HtmlTag htmlTag){ throw new UnsupportedOperationException("Current operation is not support for this object"); } public List<HtmlTag>getChildren(){ throw new UnsupportedOperationException("Current operation is not support for this object"); } public abstract void generateHtml(); }
The abstract HtmlTag class implements the Element interface. The concrete class below will override the accept method of the Element interface and call the visit method, passing the this operator as a parameter. This will allow the visitor method to take all the public members of the object and add new operations to them.
package com.javacodegeeks.patterns.visitorpattern; import java.util.ArrayList; import java.util.List; public class HtmlParentElement extends HtmlTag { private String tagName; private String startTag; private String endTag; private List<HtmlTag>childrenTag; public HtmlParentElement(String tagName){ this.tagName = tagName; this.startTag = ""; this.endTag = ""; this.childrenTag = new ArrayList<>(); } @Override public String getTagName() { return tagName; } @Override public void setStartTag(String tag) { this.startTag = tag; } @Override public void setEndTag(String tag) { this.endTag = tag; } @Override public String getStartTag() { return startTag; } @Override public String getEndTag() { return endTag; } @Override public void addChildTag(HtmlTag htmlTag){ childrenTag.add(htmlTag); } @Override public void removeChildTag(HtmlTag htmlTag){ childrenTag.remove(htmlTag); } @Override public List<HtmlTag>getChildren(){ return childrenTag; } @Override public void generateHtml() { System.out.println(startTag); for(HtmlTag tag : childrenTag){ tag.generateHtml(); } System.out.println(endTag); } @Override public void accept(Visitor visitor) { visitor.visit(this); } }
package com.javacodegeeks.patterns.visitorpattern; public class HtmlElement extends HtmlTag{ private String tagName; private String startTag; private String endTag; private String tagBody; public HtmlElement(String tagName){ this.tagName = tagName; this.tagBody = ""; this.startTag = ""; this.endTag = ""; } @Override public String getTagName() { return tagName; } @Override public void setStartTag(String tag) { this.startTag = tag; } @Override public void setEndTag(String tag) { this.endTag = tag; } @Override public String getStartTag() { return startTag; } @Override public String getEndTag() { return endTag; } @Override public void setTagBody(String tagBody){ this.tagBody = tagBody; } @Override public void generateHtml() { System.out.println(startTag+""+tagBody+""+endTag); } @Override public void accept(Visitor visitor) { visitor.visit(this); } }
Now, concrete visitor classes: we have created two concrete classes, one will add a css class visitor to all html tags and the other will change the width of the tags using the style attribute of the html tags.
package com.javacodegeeks.patterns.visitorpattern; public class CssClassVisitor implements Visitor{ @Override public void visit(HtmlElement element) { element.setStartTag(element.getStartTag().replace(">", " class='visitor'>")); } @Override public void visit(HtmlParentElement parentElement) { parentElement.setStartTag(parentElement.getStartTag().replace(">", " class='visitor'>")); } }
package com.javacodegeeks.patterns.visitorpattern; public class StyleVisitor implements Visitor { @Override public void visit(HtmlElement element) { element.setStartTag(element.getStartTag().replace(">", " style='width:46px;'>")); } @Override public void visit(HtmlParentElement parentElement) { parentElement.setStartTag(parentElement.getStartTag().replace(">", " style='width:58px;'>")); } }
Now, let's test the above example.
package com.javacodegeeks.patterns.visitorpattern; public class TestVisitorPattern { public static void main(String[] args) { System.out.println("Before visitor......... \\n"); HtmlTag parentTag = new HtmlParentElement("<html>"); parentTag.setStartTag("<html>"); parentTag.setEndTag("</html>"); HtmlTag p1 = new HtmlParentElement("<body>"); p1.setStartTag("<body>"); p1.setEndTag("</body>"); parentTag.addChildTag(p1); HtmlTag child1 = new HtmlElement("<p>"); child1.setStartTag("<p>"); child1.setEndTag("</p>"); child1.setTagBody("Testing html tag library"); p1.addChildTag(child1); child1 = new HtmlElement("<p>"); child1.setStartTag("<p>"); child1.setEndTag("</p>"); child1.setTagBody("Paragraph 2"); p1.addChildTag(child1); parentTag.generateHtml(); System.out.println("\\nAfter visitor....... \\n"); Visitor cssClass = new CssClassVisitor(); Visitor style = new StyleVisitor(); parentTag = new HtmlParentElement("<html>"); parentTag.setStartTag("<html>"); parentTag.setEndTag("</html>"); parentTag.accept(style); parentTag.accept(cssClass); p1 = new HtmlParentElement("<body>"); p1.setStartTag("<body>"); p1.setEndTag("</body>"); p1.accept(style); p1.accept(cssClass); parentTag.addChildTag(p1); child1 = new HtmlElement("<p>"); child1.setStartTag("<p>"); child1.setEndTag("</p>"); child1.setTagBody("Testing html tag library"); child1.accept(style); child1.accept(cssClass); p1.addChildTag(child1); child1 = new HtmlElement("<p>"); child1.setStartTag("<p>"); child1.setEndTag("</p>"); child1.setTagBody("Paragraph 2"); child1.accept(style); child1.accept(cssClass); p1.addChildTag(child1); parentTag.generateHtml(); } }
The above code will result in the following output:
Before visitor......... <html> <body> <p>Testing html tag library</p> <p>Paragraph 2</p> </body> </html> After visitor....... <html style='width:58px;' class='visitor'> <body style='width:58px;' class='visitor'> <p style='width:46px;' class='visitor'>Testing html tag library</p> <p style='width:46px;' class='visitor'>Paragraph 2</p> </body> </html>
The output after "Before the visitor..." is the same as in the "Compound Pattern" class. Later, we created two concrete visitors and then added them to concrete html objects using the accept method. The output "After visitor..." shows you the result, where the css classes and style elements are added to the html markup.
Note that the advantage of the Visitor Pattern is that we can add new operations to an object without changing its class. For example, we can add some JavaScript functions like onclick or some angularjs ng tags without modifying the class.
4. When to use the visitor design pattern
Use the Visitor pattern when:
- An object structure contains many object classes with different interfaces, and you want to perform operations on these objects based on their concrete classes.
- There are many different and unrelated operations that need to be performed on the objects in the object structure, and you want to avoid "polluting" their classes with those operations. Visitors can group related operations together by defining related operations in a class. When many applications share object structures, use Visitor to place operations only in those applications that need them.
- The class that defines the structure of an object rarely changes, but you often want to define new operations on that structure. Changing the object structure class requires redefining the interface of all visitors, which can be costly. If the object structure classes change frequently, it is better to define operations in these classes.
5. Visitor Design Pattern in JDK
- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
6. Download the source code
This is a lesson in the "Visitor Design Pattern". You can download the relevant source code here: VisitorPattern-Project
Translated from: https://www.javacodegeeks.com/2015/09/visitor-design-pattern.html