重学设计模式(三设计模式-访问者模式)

Posted 穆瑾轩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学设计模式(三设计模式-访问者模式)相关的知识,希望对你有一定的参考价值。

1、访问者模式

    访问者模式的关键在于访问二字,那么什么是访问?其实在学习迭代器模式的时候,遍历就是访问的一般形式。

    假如有这样一个需求:

    1)一开始,是小黄的诉求——想要知道某个文件夹下所有的文件和文件夹的名字;

    2)接着,是小红的诉求——想要知道某个目录中文件和文件夹的个数;

    3)然后,是小蓝的诉求——想要删除某个目录中的某个文件;

    对于以上诉求,我们的很容易实现。对于小黄的诉求,我们通常只要遍历文件树就可以打印出来;对于小红的诉求,我们很快就能想到加个计数器来统计,这也是我们最常见的做法;对于小蓝的诉求,我们在遍历的时候,判断获取到的文件是小蓝需要删除的,则执行删除方法。

    对于上面的 ,我们都有一个相同的操作,即遍历文件树。我们最常见,也是最容易想到的做法1:分三个方法实现;做法2:共用一个方法,并且加判断。如果此时,还有人继续提出需求呢?又要修改原有代码。

    在现实生活中,其实也有很多这样的例子,比如对于同一部电影来说,不同的观众对他们的评价也不同。

    这些被处理的数据相对稳定而访问者的操作是多样化的,使用访问者模式来处理就比较方便了。

1.1、什么是访问者模式

  • 定义

    访问者模式是一种行为设计模式,在GoF的《Design Pattern》中的定义是:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

    简单的说,访问者模式就是把作用于元素(数据结构/算法)的操作分离出来封装成独立的类,使得操作集可以相对自由的实现,新增操作时不违背开闭原则。

访问者模式的结构:

    1)抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,它的参数就是被访问者的具体元素;

    2)具体访问者(ConcreteVisitor)角色:它需要给出对每一个元素类访问时所产生的具体行为;

    3)抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数;

    4)具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作;

    5)对象结构(Object Structure)角色:对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素并且可以迭代这些元素,供访问者访问。

    访问者模式的意图在于:将数据结构与作用于结构上的操作进行解耦,使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

3.22.2、访问者模式的优缺点

  • 优点

    1)扩展性好,可以在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的行为;

    2)符合单一职责原则,可以把同一行为的多种操作分离到不同的类中;

    3)灵活性好,访问者模式将数据结构与作用于结构上的操作解耦,易于扩展。

  • 缺点

    访问者模式中具体元素的操作细节给了访问者,这破坏了对象的封装性。

3.22.3、创建方式

    我们以购物为例,普通用户,加入购物车后得到商品的总价;VIP会员,加入购物车后得到商品的总价。

1)抽象访问者(Visitor)角色

//抽象访问者,定义访问者可以做的事情
public interface CustomerVisitor 

	String browseProducts(Goods goods); //浏览商品
	

2)抽象元素(Element)角色

//抽象元素,包含接受操作,被访问者作为accept
public abstract class Goods 

	private String name; //商品名称
	
	private Integer price; //商品价格
	
	abstract Integer accept(CustomerVisitor visitor); //接收访问者
	
	public Goods()
		
	
	
	public Goods(String name,Integer price)
		this.name = name;
		this.price = price;
	
	/**
	 * @return the name
	 */
	public String getName() 
		return name;
	

	/**
	 * @param name the name to set
	 */
	public void setName(String name) 
		this.name = name;
	

	/**
	 * @return the price
	 */
	public Integer getPrice() 
		return price;
	

	/**
	 * @param price the price to set
	 */
	public void setPrice(Integer price) 
		this.price = price;
	

	@Override
	public String toString() 
		return this.name+"价格:"+this.price;
	
	

3)具体访问者(ConcreteVisitor)角色

//具体访问者-普通会员
public class OrdinaryVisitor implements CustomerVisitor

	@Override
	public String browseProducts(Goods goods) 
		String goodsMessage = goods.toString();
		return goodsMessage;
	



//具体访问者-VIP会员
public class VipVisitor implements CustomerVisitor

	@Override
	public String browseProducts(Goods goods) 
		goods.setPrice(goods.getPrice()-5);
		String goodsMessage = goods.toString();
		return goodsMessage;
	

4)具体元素(ConcreteElement)角色

public class Book extends Goods

	public Book(String name,Integer price)
		super(name, price);
	
	
	/**
	 * 接受访问者,并且把自已(商品)传给访问者
	 */
	@Override
	Integer accept(CustomerVisitor visitor) 
		String bp = visitor.browseProducts(this); //把一些处理逻辑(计算费用)的  【操作权限】  给访问者
		System.out.println(bp); //打印浏览价格
		return this.getPrice();
	

5)对象结构(Object Structure)角色

//对象结构角色
public class ShoppingCartStructure 

	private List<Goods> list = new ArrayList<Goods>();
	
	/**
	 * 计算总价
	 */
	public String accept(CustomerVisitor visitor)
		Iterator<Goods> i = list.iterator();
        Integer price = 0;
        while (i.hasNext()) 
        	price += ((Goods) i.next()).accept(visitor);
        
		return "商品总价"+price;
		
	
	
	/**
	 * 添加商品
	 */
	public void add(Goods goods) 
        list.add(goods);
    
    public void remove(Goods goods) 
        list.remove(goods);
    

6)客户端

public class Client 

	public static void main(String[] args) 
		Goods hlm = new Book("红楼梦",120);
		Goods sgyy = new Book("三国演义",105);
		Goods xyj = new Book("西游记",80);
		
		//创建访问者-普通会员
		OrdinaryVisitor ov = new OrdinaryVisitor();
		//创建结构对象-购物车
		ShoppingCartStructure scs = new ShoppingCartStructure();
		System.out.println("普通会员浏览商品");
		scs.add(hlm); //添加商品
		scs.add(sgyy); //添加商品
		scs.add(xyj); //添加商品
		System.out.println(scs.accept(ov));
		
		//创建访问者-VIP会员
		VipVisitor vip = new VipVisitor();
		//创建结构对象-购物车
		ShoppingCartStructure vscs = new ShoppingCartStructure();
		System.out.println("VIP会员浏览商品");
		vscs.add(hlm);
		vscs.add(sgyy);
		vscs.add(xyj);
		System.out.println(vscs.accept(vip));
		
	

案例效果:

    为了更有效的理解访问者模式,我们再看看JDK中的实现。其实,在JDK1.7中引进了一个叫FileVisitor的接口,看名字就猜到了一半,它使用了访问者模式。

//抽象访问者(Visitor)角色
public interface FileVisitor<T> 
    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException;
    FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException;
    FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException;
    FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException;

//具体访问者(ConcreteVisitor)角色,在SimpleFileVisitor中只是对FileVisitor的简单实现,并没有什么特别的控制
public class SimpleFileVisitor<T> implements FileVisitor<T> 
    @Override
    public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException
    
        Objects.requireNonNull(dir);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    
    @Override
    public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException
    
        Objects.requireNonNull(file);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    
    @Override
    public FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException
    
        Objects.requireNonNull(file);
        throw exc;
    
    @Override
    public FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException
    
        Objects.requireNonNull(dir);
        if (exc != null)
            throw exc;
        return FileVisitResult.CONTINUE;
    


//在java.nio.file中的Files类中有个walkFileTree方法,通过FileTreeWalker方法实现
public static Path walkFileTree(Path start,
                                    Set<FileVisitOption> options,
                                    int maxDepth,
                                    FileVisitor<? super Path> visitor)
        throws IOException
    
        if (maxDepth < 0)
            throw new IllegalArgumentException("'maxDepth' is negative");
        new FileTreeWalker(options, visitor, maxDepth).walk(start);
        return start;
    

//在walkFileTree中实现了文件树的遍历,且遍历到文件的时候,仍然调用了visitor.visitFile(file, attrs)方法,由遍历者决定是否要继续遍历或做一些其他的操作。
//省略其他部分...
// unable to get attributes of file
if (exc != null) 
    return visitor.visitFileFailed(file, exc);


// at maximum depth or file is not a directory
if (depth >= maxDepth || !attrs.isDirectory()) 
    return visitor.visitFile(file, attrs);

1.4、总结及建议

    是不是觉得访问者模式有点像模板方法模式,都是封装了固定不变的东西,开放了可变的东西。模板方法模式为可变部分预留扩展点,而访问者模式,将可变点分离出来由访问者来决定做一些处理,但是它们还是有一点区别的,访问者模式在变化(访问者)与固定(被访问者)之间,是组合关系,而模板方法模式的变化与固定之间是继承关系。

    访问者模式在迭代器模式下做了进一步的分离,它是对迭代器模式的扩充,可以遍历不同的对象,也可以在遍历的同时执行一些其他的操作。

应用场景:

    1)如果需要对复杂对象结构或对象结构比较稳定,但需要对所有元素执行操作时,可以使用访问者模式;

    2)可以将所有其他行为提取到一组访问者类中,使您的应用程序的主要类更加专注于它们的主要工作;

    3)可以充当拦截器角色。

JDK中访问者模式的应用:

    javax.lang.model.element.AnnotationValue-AnnotationValueVisitor

    javax.lang.model.element.Element-ElementVisitor

    java.nio.file.FileVisitor-SimpleFileVisitor

    javax.faces.component.visit.VisitContext-VisitCallback

CSDN 社区图书馆,开张营业! 深读计划,写书评领图书福利~

以上是关于重学设计模式(三设计模式-访问者模式)的主要内容,如果未能解决你的问题,请参考以下文章

重学设计模式(三设计模式-组合模式)

重学设计模式(三设计模式-原型模式)

购物车的程序设计

重学设计模式(三设计模式-单例模式)

重学设计模式(三设计模式-迭代器模式)

重学设计模式(三设计模式-迭代器模式)