JAVA设计模式之访问者模式

本文介绍了访问者模式的基本概念和应用场景,通过示例说明了如何利用访问者模式在不修改对象结构的情况下为对象添加新功能。此外,还提供了一个具体的案例,展示了如何运用访问者模式进行HTML页面按钮的权限处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

         1.访问者模式的介绍

         2.访问者模式的简单例子 

         访问者模式的介绍,本人本来打算用自己组织的语言写的,但在网上能找到一堆,有些人写得非常详细了,感觉比自己的表达还要好,故就不重新自己敲键盘了。下面关于访问者模式的介绍,主要来源http://www.cnblogs.com/java-my-life/archive/2012/06/14/2545381.html(这家伙的来源又是《JAVA与模式》,呵呵)。本文为什么不属于转载呢?呵呵,因为后面还有一个比较有实际意义的一个简单应用例子:Html页面按钮的权限处理。

1.访问者模式的介绍

访问者模式的结构

     访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。访问者模式的简略图如下所示:

        数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:

访问者模式涉及到的角色如下:

  ●  抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。

  ●  具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。

  ●  抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参数。

  ●  具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。

  ●  结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。

 

抽象节点角色

<span style="font-size:18px;">package cn.visitor;

/**
 * 抽象节点角色
 *
 */
public abstract class AbstractNode {
	/**
	 * 接收访问者
	 * 允许访问者访问
	 * @param visitor
	 */
     public abstract void accept(Visitor visitor);
}
</span>


具体节点角色A

<span style="font-size:18px;">package cn.visitor;

/**
 * 具体节点角色
 *
 */
public class NodeA extends AbstractNode{

	/**
	 * 接收访问者
	 */
	@Override
	public void accept(Visitor visitor) {
	  visitor.visit(this);
	}
    
	public String say(){
		return "我是nodeA";
	}
}
</span>


具体节点角色B

<span style="font-size:18px;">package cn.visitor;

/**
 * 具体节点角色
 *
 */
public class NodeB extends AbstractNode{

	/**
	 * 接收访问者
	 */
	@Override
	public void accept(Visitor visitor) {
	  visitor.visit(this);
	}
    
	public String say(){
		return "我是nodeB";
	}

}
</span>


访问者角色接口

<span style="font-size:18px;">package cn.visitor;

/**
 * 访问者角色接口
 *
 */
public interface Visitor {

	/**
	 * 访问节点A
	 * @param node
	 */
	public void visit(NodeA node);
	
	/**
	 * 访问节点A
	 * @param node
	 */
	public void visit(NodeB node);
	
	/**
	 * 刚开始看到这时,不明白为什么要重载visit方法,NodeA,NodeB都是AbstractNode的子类
	 * 那只写一个visit(AbstractNode node)方法不就OK了?
	 * 再回头看一下访问者模式的定义,是需要对不同的节点元素新增一些额外的操作,
	 * 如果这里只写visit(AbstractNode node),那么在具体访问者中就需要判断node是属于nodeA呢还是NodeB呢?
	 * 如果AbstractNode的具体节点有很多,那么为了判断node类型,就需要很多if instanceof else等等判断了
	 * 代码耦合性太强,不利于维护 
	 */
}
</span>


访问者A

<span style="font-size:18px;">package cn.visitor;

/**
 * A访问者
 *
 */
public class VisitorA implements Visitor{

	@Override
	public void visit(NodeA node) {
		// do something....
		System.out.println(node.say());
	}

	@Override
	public void visit(NodeB node) {
		// do something
		System.out.println(node.say());
	}

}
</span>


访问者B

<span style="font-size:18px;">package cn.visitor;

/**
 * 
 *访问者B
 */
public class VisitorB implements Visitor {

	@Override
	public void visit(NodeA node) {
		// do something....
		System.out.println(node.say());
	}

	@Override
	public void visit(NodeB node) {
		// do something
		System.out.println(node.say());
	}

}
</span>


结构对象

<span style="font-size:18px;">package cn.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 *结构对象(ObjectStructure)角色:
 *有如下的责任,可以遍历结构中的所有元素;
 *如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;
 *如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。
 */
public class ObjectStructure {
	private List<AbstractNode> nodeList=new ArrayList<AbstractNode>();
	/**
	 * 提供接口,共外部添加访问者可以访问哪些节点
	 * @param node
	 */
	public void addNode(AbstractNode node){
		nodeList.add(node);
	}
	
	/**
	 * 遍历节点
	 * @param visitor
	 */
	public void action(Visitor visitor){
		for(AbstractNode node:nodeList){
			node.accept(visitor);
		}
	}
}
</span>


测试客户端

<span style="font-size:18px;">package cn.visitor;

public class VisitorPatternTest {
	public static void main(String[] args) {
		//创建结构对象
		ObjectStructure os=new ObjectStructure();
		//结构对象增加节点
		os.addNode(new NodeA());
		os.addNode(new NodeB());
		//创建访问者
		Visitor visitor=new VisitorA();
		os.action(visitor);
	}
}
</span>


        虽然在这个示意性的实现里并没有出现一个复杂的具有多个树枝节点的对象树结构,但是,在实际系统中访问者模式通常是用来处理复杂的对象树结构的,而且访问者模式可以用来处理跨越多个等级结构的树结构问题。这正是访问者模式的功能强大之处。

访问者模式的优点

      ●  好的扩展性

  能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  ●  好的复用性

  可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  ●  分离无关行为

  可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

访问者模式的缺点

     ●  对象结构变化很困难

  不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。

  ●  破坏封装

  访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

2.访问者模式的应用例子(按钮级别的权限判断)

     初步接触设计模式最痛苦的就是一些教程对设计模式的概念长篇大论,然后随便贴个简单例子的源码出来(例如上面,呵呵),没有举一些实际开发中哪些场景会使用到(对于初学者来说,更希望看到一些有意义的场景例子)。

 

      需求:html页面的按钮需要根据用户的权限进行判断,是否显示或者隐藏或者删除(总之就是用户如果没有操作那个按钮的权限,页面上就不要让用户看见)。

     

            html其实也是一颗树,拥有不同的节点。为了解析html页面,借用了htmlparser工具。这个可以在网上下载。通过htmlparser可以很方便的获得html的节点,然后自己自定义一个NodeVisitor,访问节点,并对特殊的节点(被标识,需要判断权限的按钮等节点)进行加工,比如delete或者display等操作。

    

      假如有这样一个页面,上面有4个按钮,查询,添加,修改,删除,其中添加,修改,删除按钮都需根据用户的权限进行动态显示。页面初始源码及效果图如下(需权限判断的按钮,使用sys_opt="xxx"表示,xxx表示按钮的功能,如果remove)

 

      原始的html页面源码及效果图,如下。

 

UserPurview.java模拟用户拥有权限并判断

<span style="font-size:18px;">package cn.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 *用户拥有的权限范围(简单点)
 */
public class UserPurview {

	private String name;
	private List<String> listPurview=new ArrayList<String>();//实际应用中,这里可以用Map<String,List<String>>表示哪个模块下拥有那些权限等
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void addPurview(String purview){
		this.listPurview.add(purview);
	}
	public List<String> getPurview(){
		return this.listPurview;
	}
	/**
	 * 检查用户是否拥有某种操作的权限
	 * @param operation
	 * @return
	 */
	public boolean isHave(String operation){
		for(String o:listPurview){
			if(o.equalsIgnoreCase(operation)) return true;
		}
		return false;
	}
}
</span>


ButtonVisitor.java模拟访问者角色,由于使用了htmlParser工具,所以访问者必须都继承NodeVisitor类

<span style="font-size:18px;">package cn.visitor;

import org.apache.commons.lang.StringUtils;
import org.htmlparser.Tag;
import org.htmlparser.visitors.NodeVisitor;



public class ButtonVisitor extends NodeVisitor{

	private final String TAG_SYS_OPT="sys_opt";//对含有属性sys_opt的节点做处理
	private UserPurview user;
	public ButtonVisitor(UserPurview user){
		this.user=user;
	}
	
	@Override
	public void visitTag(Tag tag){
		String operation=this.getOperation(tag);//获得指定标签的操作属性,如remove,update等
		if(operation==null) return;
		if(!user.isHave(operation)) this.doTagFilter(tag);
		this.removeTagOptAttr(tag);//删除过滤标签的指定属性
	}
	/**
	 * 获取指定标签的操作属性
	 * 如果sys_opt="remove"
	 * 则返回remove
	 * @param tag
	 * @return
	 */
	private String getOperation(Tag tag){
		String operation=tag.getAttribute(TAG_SYS_OPT);
		if (operation == null) return null;
        return StringUtils.trimToEmpty(operation);
	}
	/**
	 * 对标签进行过滤,使用户不可操作
	 * @param tag
	 */
	private void doTagFilter(Tag tag){
		 String attr = tag.getAttribute("disabled");
		 //至于是不可见,还是可见但不可操作,这里用户可以自行选择
//		 if (attr == null) {
//	            tag.setAttribute("style", "\"display:none;\"");
//	        } else {
//	            tag.setAttribute("style", attr + ";display:none;");
//	        }
		 
	        if (attr == null) {
	            tag.setAttribute("disabled", "\"disabled\"");
	        } else {
	            tag.setAttribute("disabled", "disabled");
	        }
	}
	/**
     * 删除过滤标签的操作标志属性
     * @param tag
     */
    protected void removeTagOptAttr(Tag tag)
    {
        if (tag == null) return;
        tag.removeAttribute(TAG_SYS_OPT);
    }
}
</span>


HtmlStructure.java类模拟构造对象角色

<span style="font-size:18px;">package cn.visitor;

import org.apache.commons.lang.StringUtils;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;

public class HtmlStructure {

	private String htmlContent="";
	private UserPurview user;
	
	public HtmlStructure(String htmlContent,UserPurview user){
		this.htmlContent=htmlContent;
		this.user=user;
	}
	
	/**
	 * 使用visitor来遍历body区域内的节点
	 * 呵呵,到这一步,如果没看过CompositeTag.accept(NodeVisitor visitor)这方法的源码的话,
	 * 估计你会有疑问,在哪里遍历了?呵呵,后面我贴出源码
	 */
	public String visitAndParse(){
		try {
			Node body=this.getBodyNode();
			if(body==null) return htmlContent;
			ButtonVisitor visitor=new ButtonVisitor(user);
			body.accept(visitor);//accept方法里面将会对body区域内的所有节点进行遍历
			return this.getFinalString(body);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 获取html中的body节点
	 * @return
	 */
	private Node getBodyNode()throws ParserException{
		if(StringUtils.isBlank(htmlContent)) return null;
		Parser parser=Parser.createParser(htmlContent, "UTF-8");
		NodeList list=parser.parse(new TagNameFilter("body"));
		if(list==null || list.size()<1) return null;
		return list.elementAt(0);
	}
	
	 /**
     * 获取最终过滤和解析后的字符串.
     * @param body
     * @return
     */
    private String getFinalString(Node body){
    	 Node root = body;
         while (root.getParent() != null) {
             root = root.getParent();
         }
         return root.toHtml();
    }
}
</span>


   对于节点角色,htmlParser已经帮我们封装好了,不需要我们自己写代码了。

下面是客户端测试类。

<span style="font-size:18px;">package cn.visitor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;

public class HtmlFilterTest {
	public static void main(String[] args) {
		//初始的html文件
		String initHtml="F:/test.html";
		//初始html内容
		String initHtmlContent=getHtmlContent(initHtml);
		//创建一个用户,并授以权限
		UserPurview u=new UserPurview();
		u.setName("张三");
		u.addPurview("add");
		//创建一个htmlStructure
		HtmlStructure htmlStructure=new HtmlStructure(initHtmlContent,u);
		//获取处理后的html内容
		String afterDealHtml=htmlStructure.visitAndParse();
		//写入文件
		outputFile("F:/test01.html",afterDealHtml);
	}
	
	/**
	 * 如果是web系统,可以使用filter截取到html内容
	 * @param fileName
	 * @return
	 */
	private static String getHtmlContent(String fileName){
		
		try {
			File file=new File(fileName);
			InputStream in=new FileInputStream(file);
			byte[] b=new byte[(int)file.length()];
			in.read(b);
			in.close();
			return new String(b);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
	
	private static void outputFile(String fileName,String content){
		try {
			FileWriter writer=new FileWriter(new File(fileName));
			writer.write(content);
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
}
</span>


     

      看看处理好的html源码及效果图。

          遍历body区域内的所有节点,被封装在org.htmlparser.tags.CompositeTag.accept(NodeVisitor visitor)中。下面看看它的源码

<span style="font-size:18px;">/**
     * Tag visiting code.
     * Invokes <code>accept()</code> on the start tag and then
     * walks the child list invoking <code>accept()</code> on each
     * of the children, finishing up with an <code>accept()</code>
     * call on the end tag. If <code>shouldRecurseSelf()</code>
     * returns true it then asks the visitor to visit itself.
     * @param visitor The <code>NodeVisitor</code> object to be signalled
     * for each child and possibly this tag.
     */
    public void accept (NodeVisitor visitor)
    {
        SimpleNodeIterator children;
        Node child;

        if (visitor.shouldRecurseSelf ())
            visitor.visitTag (this);
        if (visitor.shouldRecurseChildren ())
        {
            if (null != getChildren ())
            {
                children = children ();
                while (children.hasMoreNodes ())
                {
                    child = children.nextNode ();
                    child.accept (visitor);
                }
            }
            if ((null != getEndTag ()) && (this != getEndTag ())) // 2nd guard handles <tag/>
                getEndTag ().accept (visitor);
        }
    }</span>


 

 
 
 
 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值