设计模式系列-访问者模式-Visitor

案例介绍

在正式介绍访问者之前,我们先看一个需求,用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 链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值