学习设计模式不光要学习设计模式的思想,还要去深入理解,为什么要用这个设计模式。
如何深入理解?读优秀的框架代码,看别人代码,了解它们的使用场景。 - - - 博主老师(感谢他)
本文介绍了访问者模式的概念,实现并对spring中访问者模式实现做了介绍。
访问者模式
1、概念
访问者模式是一种将数据操作和数据结构分离的设计模式。访问者模式的基本想法是:软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象的访问。访问者是一个接口,拥有visit方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每个元素都实施accept方法,在每个元素的accept方法中调用访问者的visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
场景:
- 对象结构比较稳定
- 需要对一个对象结构中的对象进行很多不同且不相关的操作(不希望这些操作污染对象,也不希望在添加新操作的时候修改对象)。
2、实现
公司对员工进行业绩考核,不同领域的管理人员对员工的评定标准不一样。我们把员工分为工程师和经理,评定员工的分别是CTO和CEO。假定CTO关注工程师的代码量、经理的新产品数量,而CEO关注工程师的KPI和产品的KPI及新产品数量。
CTO和CEO对不同员工关注点不一样,需要对不同的员工类进行不同的处理。
员工基类
public abstract class Staff {
public String name;
public int kpi;
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
public abstract void accept(Visitor visitor);
}
工程师
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getCodeLines() {
return new Random().nextInt(1000000);
}
}
经理
public class Manager extends Staff {
private int products;
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
public int getProducts() {
return products;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
访问者接口
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("工程师:" + engineer.name + ", KPI:" + engineer.kpi);
}
@Override
public void visit(Manager manager) {
System.out.println("经理:" + manager.name + ", KPI:" + manager.kpi + ", 产品数量:" + manager.getProducts());
}
}
CTO访问者
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("工程师:" + engineer.name + ", 代码行数:" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("经理:" + manager.name + ", 产品数量:" + manager.getProducts());
}
}
报表类
public class BusinessReport {
List<Staff> mStaffs = new ArrayList<>();
public BusinessReport() {
mStaffs.add(new Manager("王经理"));
mStaffs.add(new Engineer("工程师-h"));
mStaffs.add(new Engineer("工程师-i"));
mStaffs.add(new Engineer("工程师-x"));
}
public void showReport(Visitor visitor) {
mStaffs.forEach(s -> s.accept(visitor));
}
}
测试
public class Test {
public static void main(String[] args) {
BusinessReport report = new BusinessReport();
report.showReport(new CEOVisitor());
report.showReport(new CTOVisitor());
}
}
输出
经理:王经理, KPI:4, 产品数量:1
工程师:工程师-h, KPI:2
工程师:工程师-i, KPI:1
工程师:工程师-x, KPI:7
经理:王经理, 产品数量:1
工程师:工程师-h, 代码行数:835396
工程师:工程师-i, 代码行数:916640
工程师:工程师-x, 代码行数:68679
spring中的访问者模式
PropertySourcesPlaceholderConfigurer 允许我们用 Properties 文件中的属性来定义应用上下文(配置文件或者注解)
我们在 XML 配置文件(或者其他方式,如注解方式)中使用占位符的方式来定义一些资源,并将这些占位符所代表的资源配置到 Properties 中,这样只需要对 Properties 文件进行修改即可
就是支持${jdbc.url}这种变量
<bean id="dataSourceDefault" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
PropertySourcesPlaceholderConfigurer要做到替换,至少需要先获取bean的属性并解析,BeanDefinitionVisitor就是对bean的属性配置信息做解析用的。BeanDefinitionVisitor用到了访问者模式。
在PropertySourcesPlaceholderConfigurer的postProcessBeanFactory(生命周期方法)方法中,调用了doProcessProperties
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
...
//本方法在Bean对象实例化之前执行,通过beanFactory可以获取bean的定义信息,并可以修改bean的定义信息。
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
...
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
...
}
//访问给定bean工厂中的每个bean定义,并尝试用给定属性中的值替换${…}属性占位符。
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
...
doProcessProperties(beanFactoryToProcess, valueResolver);
}
}
我们doProcessProperties是在PropertySourcesPlaceholderConfigurer的父类PlaceholderConfigurerSupport中实现的
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer
implements BeanNameAware, BeanFactoryAware {
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 对bean进行访问
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
...
}
}
BeanDefinitionVisitor:对Spring IoC容器内的BeanDefinition属性配置信息做解析
public class BeanDefinitionVisitor {
@Nullable
private StringValueResolver valueResolver;
public BeanDefinitionVisitor(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
this.valueResolver = valueResolver;
}
// 遍历给定的BeanDefinition对象以及其中包含的可变PropertyValue和ConstructorArgumentValue。
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}
}
分析下这里怎么体现出访问者模式了:
BeanDefinition 为 Spring Bean 的定义信息,在 Spring 解析完配置后,会生成 BeanDefinition 并且记录下来。下次通过 getBean 获取 Bean 的时候,会通过 BeanDefinition 来实例化具体的 Bean 对象。
Spring 的 BeanDefinitionVisitor 用来访问 BeanDefinition。
抽象元素为 BeanDefinition。对 Bean 的定义信息,比如属性值、构造方法参数或者更具体的实现。
具体元素有 RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition 等等。
因为没有对访问者进行扩展,所以只有一个具体访问者 BeanDefinitionVisitor
访问的具体调用就是visitor.visitBeanDefinition(bd);
再回顾下访问者的定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
参考
[1]何红辉 关爱民 · Android源码设计模式解析与实战 · 人民邮电出版社
[2]https://blog.csdn.net/firefile/article/details/90314409