案例介绍
在正式介绍访问者之前,我们先看一个需求,用java代码组装一个html, 并能输出html 的字符串表示,html 中有各种标签,比如 <html></html
,<body></body>
,<p></p>
等, 有的标签可以包含其他标签,如 html,body 等,则有的标签不需要包含,如p 标签只包含一些文字。
现在我们来分析需求,根据需求 html 有很多种类的标签,每个标签都有一些共有的属性,如标签名称、开始标记法和结束标记符,每个标签能生成完整的字符表达,如<p>这是一个文字段落</p>
,标签之间还有差异属性,有的标签可以包含其他标签。
需求分析完后,我们可以设计类结构,首先定义一个抽象类HtmlTag
,定义了一些共有的属性和方法,然后定义子类 HtmlParentElement
代表可以包含子标签的标签 和 HtmlElement
代表不包含其他标签的标签。
具体代码如下:
public abstract class HtmlTag {
public abstract String getTagName();
public abstract String getStartTag();
public abstract String getEndTag();
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 abstract void generateHtml();
}
public class HtmlElement extends HtmlTag {
private String tagName;
private String startTag;
private String endTag;
private String tagBody;
public HtmlElement(String tagName, String startTag, String endTag) {
this.tagName = tagName;
this.startTag = startTag;
this.endTag = endTag;
}
@Override
public String getTagName() {
return this.tagName;
}
@Override
public String getStartTag() {
return this.startTag;
}
@Override
public String getEndTag() {
return this.endTag;
}
@Override
public void setTagBody(String tagBody) {
this.tagBody = tagBody;
}
@Override
public void generateHtml() {
System.out.println(startTag + tagBody + endTag);
}
}
public class HtmlParentElement extends HtmlTag {
private String tagName;
private String startTag;
private String endTag;
private List<HtmlTag> childrenTag;
public HtmlParentElement(String tagName, String startTag, String endTag) {
this.tagName = tagName;
this.startTag = startTag;
this.endTag = endTag;
this.childrenTag = new ArrayList<>();
}
@Override
public String getTagName() {
return this.tagName;
}
@Override
public String getStartTag() {
return this.startTag;
}
@Override
public String getEndTag() {
return this.endTag;
}
@Override
public void addChildTag(HtmlTag htmlTag) {
this.childrenTag.add(htmlTag);
}
@Override
public void generateHtml() {
System.out.println(startTag);
for (HtmlTag tag : childrenTag) {
tag.generateHtml();
}
System.out.println(endTag);
}
}
public class HtmlTest {
public static void main(String[] args) {
HtmlTag html = new HtmlParentElement("html", "<html>", "</html>");
HtmlTag body = new HtmlParentElement("body", "<body>", "</body>");
HtmlTag p = new HtmlElement("p", "<p>", "</p>");
p.setTagBody("这是一个段落!");
body.addChildTag(p);
html.addChildTag(body);
html.generateHtml();
}
}
执行main 方法输出如下html 片段:
<html>
<body>
<p>这是一个段落!</p>
</body>
</html>
访问者模式详解
继续上面的案例,这时需求发生变化,需要再标签上添加 class 和 style 属性,根据上面的类结构,我们需要修改抽象类 HtmlTag
和实现类,设想下真实的html 标签有很多,如果这样改动工作量巨大,如果这类需求频繁改动,那维护的工程师苦不堪言。那有没有一种灵活的方案来应对此类需求,答案是有的,该是访问者模式出场的时候了。
访问者模式的目的是在不更改原有类的设计上添加新的操作方法。具体的参与者类图如下:
** Visitor**
为对象结构中的每个类声明一个Visit
操作。如上例中为 HtmlElement 对象添加新增 class 熟悉值,把 HtmlElement 的对象传入该操作,可以改变 HtmlElement 对象内部的数据。
** Element**
声明一个Accept
操作,该方法接收 Visitor 对象
访问者模式代码实现
public interface Visitor {
public void visit(HtmlElement htmlElement);
public void visit(HtmlParentElement htmlParentElement);
}
public interface Element {
public void accept(Visitor visitor);
}
public class CssClassVisitor implements Visitor {
@Override
public void visit(HtmlElement htmlElement) {
htmlElement.setStartTag(htmlElement.getStartTag().replace(">", " class='visitor'>"));
}
@Override
public void visit(HtmlParentElement htmlParentElement) {
htmlParentElement.setStartTag(htmlParentElement.getStartTag().replace(">", " class='visitor>"));
}
}
public class StyleVisitor implements Visitor {
@Override
public void visit(HtmlElement htmlElement) {
htmlElement.setStartTag(htmlElement.getStartTag()
.replace(">", " style='width:100px'>"));
}
@Override
public void visit(HtmlParentElement htmlParentElement) {
htmlParentElement.setStartTag(htmlParentElement.getStartTag()
.replace(">", " style='width:100px'>"));
}
}
public abstract class HtmlTag implements Element {
public abstract String getTagName();
public abstract String getStartTag();
public abstract String getEndTag();
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 abstract void generateHtml();
}
public class HtmlElement extends HtmlTag {
private String tagName;
private String startTag;
private String endTag;
private String tagBody;
public HtmlElement(String tagName, String startTag, String endTag) {
this.tagName = tagName;
this.startTag = startTag;
this.endTag = endTag;
}
@Override
public String getTagName() {
return this.tagName;
}
@Override
public String getStartTag() {
return this.startTag;
}
public void setStartTag(String startTag) {
this.startTag = startTag;
}
@Override
public String getEndTag() {
return this.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);
}
}
public class HtmlParentElement extends HtmlTag {
private String tagName;
private String startTag;
private String endTag;
private List<HtmlTag> childrenTag;
public HtmlParentElement(String tagName, String startTag, String endTag) {
this.tagName = tagName;
this.startTag = startTag;
this.endTag = endTag;
this.childrenTag = new ArrayList<>();
}
@Override
public String getTagName() {
return this.tagName;
}
@Override
public String getStartTag() {
return this.startTag;
}
public void setStartTag(String startTag) {
this.startTag = startTag;
}
@Override
public String getEndTag() {
return this.endTag;
}
@Override
public void addChildTag(HtmlTag htmlTag) {
this.childrenTag.add(htmlTag);
}
@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);
}
}
public class VisitorTest {
public static void main(String[] args) {
Visitor cssVisitor = new CssClassVisitor();
Visitor styleVisitor = new StyleVisitor();
HtmlTag html = new HtmlParentElement("html", "<html>", "</html>");
HtmlTag body = new HtmlParentElement("body", "<body>", "</body>");
HtmlTag p = new HtmlElement("p", "<p>", "</p>");
p.setTagBody("这是一个段落!");
p.accept(cssVisitor);
body.addChildTag(p);
body.accept(cssVisitor);
body.accept(styleVisitor);
html.addChildTag(body);
html.accept(cssVisitor);
html.accept(styleVisitor);
html.generateHtml();
}
}
main 函数输出:
<html class='visitor style='width:100px'>
<body class='visitor style='width:100px'>
<p class='visitor'>这是一个段落!</p>
</body>
</html>
访问者模式的优缺点
优点:
- 方便为一组组合的对象添加新的行为,而不用修改原有的代码
- 添加新的操作方法比较容易
- 新增的方法在Visitor 同一进行管理
缺点:
- 组合对象的封装被打破,Visitor 需要知道组合对象内部的封装逻辑
- 涉及到遍历方法,组合对象的遍历比较复杂
源码github 链接