本文是我们名为“ Java设计模式 ”的学院课程的一部分。
在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看 !
1引言
要了解访客设计模式,让我们重新访问“ 复合设计模式” 。 组合模式使您可以将对象组合成树形结构,以表示部分整体层次结构。
在Composite Pattern示例中,我们创建了一个由不同类型的对象组成的html结构。 现在假设我们需要在html标签中添加一个css
类。 一种方法是在使用setStartTag
方法添加开始标记时添加类。 但是,这种硬编码设置会给我们的代码造成僵化。
另一种方法是在父抽象HtmlTag
类中添加新方法,例如addClass
。 所有子类都将重写此方法,并将提供css
类。 这种方法的一个主要缺点是,如果有许多子类(将在大型html页面中显示),则在所有子类中实施此方法将变得非常昂贵且忙碌。 并假设,稍后我们需要在标签中添加另一个样式元素,我们再次需要执行相同的操作。
访客设计模式为您提供了一种在不更改元素类的情况下,在对象上添加新操作的方法,尤其是当操作经常更改时。
2.什么是访客设计模式
访客设计模式的目的是表示要对对象结构的元素执行的操作。 访问者可让您定义新操作,而无需更改其所操作元素的类。
当跨类层次结构的对象的异构集合设计操作时,Visitor模式很有用。 访客模式允许在不更改集合中任何对象的类的情况下定义操作。 为此,Visitor模式建议在一个单独的类中定义该操作,该类称为visiter类。 这将操作与其所操作的对象集合分开。 对于每个要定义的新操作,都会创建一个新的访问者类。 由于该操作将在一组对象上执行,因此访问者需要一种访问这些对象的公共成员的方法。 可以通过实施以下两个设计思想来满足此要求。
游客
- 为对象结构中每类
ConcreteElement
声明一个Visit
操作。 操作的名称和签名标识了将Visit
请求发送给visitor
。 这使访问者可以确定要访问的元素的具体类别。 然后,访问者可以直接通过其特定界面访问该元素。
具体访客
- 实现
Visitor
声明的每个操作。 每个操作都会实现为结构中相应对象类别定义的算法片段。ConcreteVisitor
提供算法的上下文并存储其本地状态。 这种状态通常会在遍历结构期间累积结果。
元件
- 定义将
visitor
作为参数的Accept
操作。
ConcreteElement
- 实现将
visitor
作为参数的Accept
操作。
对象结构
- 可以枚举其元素。
- 可以提供一个高级界面,以允许
visitor
访问其元素。 - 可以是组合或集合,例如列表或集合。
3.实施访客设计模式
为了实现Visitor设计模式,我们将使用相同的Composite Pattern代码 ,并将为其引入一些新的接口,类和方法。
实现Visitor模式需要两个重要的接口,一个Element
接口,它将包含一个参数为Visitor
类型的accept
方法。 所有需要允许访问者访问的类都将实现此接口。 在我们的例子中, HtmlTag
将实现Element
接口,因为HtmlTag
是所有具体html类的父抽象类,因此具体类将继承并覆盖Element
接口的accept
方法。
另一个重要的界面是Visitor
界面; 此接口将包含带有实现Element
接口的类的参数的visit方法。 还请注意,与Composite Design Pattern课程中显示的示例相反,我们在HtmlTag
类中添加了两个新方法,即getStartTag
和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);
}
下面的代码来自Composite Pattern示例,进行了一些更改。
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();
}
抽象的HtmlTag
类实现Element
接口。 下面的具体类将覆盖Element
接口的accept方法,并调用visit
方法,并将this
运算符作为参数传递。 这将允许visitor
方法获取该对象的所有公共成员,并对其添加新的操作。
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);
}
}
现在,具体的访问者类:我们创建了两个具体的类,一个将为所有html标记添加一个css
类visitor
,另一个将使用html标记的style属性更改标记的宽度。
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;'>"));
}
}
现在,让我们测试上面的示例。
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();
}
}
上面的代码将导致以下输出:
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>
“访问者之前……”之后的输出与“复合模式”课程中的输出相同。 后来,我们创建了两个具体的访问者,然后使用accept
方法将它们添加到具体的html对象中。 输出“ After visitor…”显示给您结果,其中将css
类和样式元素添加到html标记中。
请注意,Visitor Pattern的优点是我们可以在不更改其类的情况下向对象添加新操作。 例如,我们可以添加一些JavaScript函数,例如onclick
或一些angularjs ng标签,而无需修改类。
4.何时使用访客设计模式
在以下情况下使用“访问者”模式:
- 对象结构包含许多具有不同接口的对象类,并且您希望根据这些对象的具体类对这些对象执行操作。
- 需要对对象结构中的对象执行许多不同且不相关的操作,并且您要避免使用这些操作“污染”它们的类。 访客可以通过在一个类中定义相关操作来将它们在一起。 当许多应用程序共享对象结构时,请使用Visitor将操作仅放在需要它们的那些应用程序中。
- 定义对象结构的类很少更改,但是您经常想在该结构上定义新的操作。 更改对象结构类需要重新定义所有访问者的接口,这可能会导致成本高昂。 如果对象结构类经常更改,则最好在这些类中定义操作。
5. JDK中的访问者设计模式
-
javax.lang.model.element.Element
和javax.lang.model.element.ElementVisitor
-
javax.lang.model.type.TypeMirror
和javax.lang.model.type.TypeVisitor
6.下载源代码
这是“访客设计模式”的一课。 您可以在此处下载相关的源代码: VisitorPattern-Project
翻译自: https://www.javacodegeeks.com/2015/09/visitor-design-pattern.html