小白设计模式:访问者模式

定义

可作用于对象结构中各个元素,在不改变各元素类的前提下,定义作用于这些元素新操作方法的一种行为型设计模式。

主要组成

抽象访问者(Visitor): 声明出对对象结构中每一个具体元素的访问方法visit,传入Concrete Element对象作为参数

具体访问者(Concrete Visitor): 实现各种visit方法,调用具体元素对象完成对应的各种操作

元素(Element): 定义出抽象accept方法,用于接收Visitor对象参数

具体元素(Concrete Element): 实现accept操作,该操作一般用于操作执行Visitor.visit方法,将自身作为参数传入

对象结构(Object structure): 各个元素构成的一个整体,提供能够让访问者访问所有元素的接口。可以是集合(比如List),或者是复合的类对象。

UML图

 

 

 

框架代码

抽象访问者: 需要依赖于具体的元素类,而不是抽象元素类,以避免if-else之类的嵌套和类型判断

public interface Visitor {
	
	void visit(ConcreteElementA elementA);
	void visit(ConcreteElementB elementB);
}
复制代码

具体访问者:

public class ConcreteVisitorA implements Visitor{

	@Override
	public void visit(ConcreteElementA elementA) {
		// use elementA to do something
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		// use elementB to do something
		
	}
}

public class ConcreteVisitorB implements Visitor{

	@Override
	public void visit(ConcreteElementA elementA) {
		// use elementA to do something
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		// use elementB to do something
		
	}
}
复制代码

抽象元素(可能接口或者抽象类): 由各个具体元素类实现accept接口,用于使得Visitor依赖于具体元素类

public interface Element {
	
	void accept(Visitor visitor);
}
复制代码

具体元素:

public class ConcreteElementA implements Element{

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

}

public class ConcreteElementB implements Element{

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

}
复制代码

Client中简单调用:

    List<Element> elements = new ArrayList<>();
    elements.add(new ConcreteElementA());
    elements.add(new ConcreteElementB());
    elements.add(new ConcreteElementA());
    
    ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
    ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
    for (Element element : elements) {
    	element.accept(concreteVisitorA);
    	element.accept(concreteVisitorB);
    }
复制代码

具体例子

公司对员工进行考核,员工存在工程师和产品经理,考核的评审有CEO和CTO,CTO只关注工程师的代码量和产品经理的新产品数量。而CEO则关注工程师的KPI和产品经理的KPI及新产品数量。从这边可以看出CEO和CTO对员工考核的关注点不一样,这就需要对员工类型进行处理,就可以用到访问者模式(员工分类结构也是很稳定不变的)。(来自<<Android源码设计模式解析与实践>>)

UML

 

 

 

代码

员工Staff:

public abstract class Staff {
	String name;
	public Staff(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public abstract int getKPI();
	public abstract void accept(Visitor visitor);
}
复制代码

工程师Engineer:

public class Engineer extends Staff{

	public Engineer(String name) {
		super(name);
		
	}

	@Override
	public int getKPI() {
		return 9;
	}
	
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	
	public int getCodeLines() {
		return 9999;
	}

}
复制代码

产品经理Manager:

public class Manager extends Staff{

	public Manager(String name) {
		super(name);
		
	}

	@Override
	public int getKPI() {
		return 10;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	
	public int getProductCount() {
		return 5;
	}
}
复制代码

访问者Visitor:

public interface Visitor {
	
	void visit(Engineer engineer);
	void visit(Manager manager);
}
复制代码

CEO:

public class CEOVisitor implements Visitor{

	@Override
	public void visit(Engineer engineer) {
		System.out.println("CEOVisitor 考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
		
	}

	@Override
	public void visit(Manager manager) {
		// TODO Auto-generated method stub
		System.out.println("CEOVisitor 考核产品经理"+ manager.getName() + ",KPI:" + manager.getKPI() + ",产品数:" + manager.getProductCount());
	}

}
复制代码

CTO:

public class CTOVisitor implements Visitor{
	
	@Override
	public void visit(Engineer engineer) {
		System.out.println("CTOVisitor 考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
		
	}

	@Override
	public void visit(Manager manager) {
		// TODO Auto-generated method stub
		System.out.println("CTOVisitor 考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
	}
}
复制代码

BussinessReport(对应Object structure):

public class BussinessReport {
	List<Staff> staffs = new ArrayList<>();

	public BussinessReport() {
		staffs.add(new Engineer("小张"));
		staffs.add(new Manager("小王"));
		
	}
	
	public void showReport(Visitor visitor) {
		for (Staff staff : staffs) {
			staff.accept(visitor);
		}
	}
}
复制代码

Client调用:

//构建报表
BussinessReport bussinessReport = new BussinessReport();
//给CEO看
bussinessReport.showReport(new CEOVisitor());
//给CTO看
bussinessReport.showReport(new CTOVisitor());
复制代码

假设不使用访问者模式

如果不使用访问者模式的话,也就不存在Visitor继承体系,以上述具体例子为例则需要对Staff的各种类型进行判断: 假设在ReportUtils处理:

public class ReportUtils {
    public static void visitCEO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
            System.out.println("考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
        }
    }
    
    public static void visitCTO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
    		System.out.println("考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
        }
    }
}
复制代码

这就会导致出现一大堆的if -else嵌套和类型转换,当类型较多的时候就会难以扩展和维护,并且当关注层面不同,重复的判断会很多,难以维护后续持续扩展新的操作(visitXX,visitXXX...)。而使用访问者Visitor模式,能够通过accept-visit方法的配套使用使得不同关注面的操作可以分离,并且去除一大堆嵌套if-else和类型转换的判断,灵活性更好,可扩展性高,更易于维护。

总结

优点

角色职责分离,符合单一职责原则;

允许对组合结构中的各个元素加入新的操作,而不需要修改结构本身,增加新操作相对容易;

集中管理访问者所进行操作的代码;

缺点

具体元素元素对访问者公布细节,违反了迪米特原则(一个类对自己需要耦合或调用的类知道的最少);

增加删减具体元素时修改成本过大,需要修改Visitor继承体系;

为了达到"区别对待"而依赖具体类而不是抽象,违反了"依赖倒置"原则(高层模块不应该依赖低层模块,两者都应该依赖抽象);

应用场景

1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。 注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

微信公众号

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值