访问者设计模式

访问者设计模式


1. 简单介绍

访问者模式(Visitor)是一种行为型设计模式,表示一个作用于对象结构中的各元素的操作。它可以使你在不改变各元素类型的前提下定义作用于这些元素的新操作。值得注意的是,当对象结构中需要新增元素时,访问者这一角色,都需要添加对新增元素的操作行为。因此大部分场景下,我们不太会用到该设计模式,因为元素类型总是会变化,此时采用访问者模式不满足开闭原则。

2. 使用场景
  • 对象结构中聚合的元素对象类型很少改变,但经常需要在此对象结构上定义新的动作
  • 需要对一个对象结构中的元素对象进行很多不同且不相关的操作时,不希望增加新操作时修改元素对象
3. 场景举例

使用javacjava源代码编译成字节码这个过程,使用到了访问者模式。编译过程可以描述为词法分析器、语法分析器、语义分析器和代码生成器多次遍历语法树,而每次遍历这个语法树。不同的分析器会采用不同的处理动作,达到对这棵语法树进行进一步的处理 。这实际上就是采用了访问者模式设计的,每次遍历都是一次访问者的执行过程。

4. UML类图

访问者设计模式_第1张图片

5. 具体实现

描述

  • 背景:京东商城中的不同商品(具体元素:书本、食品),针对不同等级的会员(具体观察者:普通会员、Plus会员),打折力度不同
  • JDMall:京东商城,充当ObjectStruct角色,聚合了不同的商品,如:书本、食品
  • Element:京东商城销售的商品的抽象接口,充当Element角色
  • BookElement:京东商城销售的书本,充当具体元素ConcreteElement
  • FoodElement:京东商城销售的食品,充当具体元素ConcreteElement
  • Visitor:观察者抽象接口
  • OrdinaryMember:京东普通会员,充当具体访问者角色ConcreteVisitor
  • PlusMember:京东Plus会员,充当具体访问者角色ConcreteVisitor
  • Client:客户端

代码实现

JDMall.java

/**
 * 京东商城,充当ObjectStruct角色
 */
public class JDMall {
     

    /**
     * 聚合元素对象
     */
    List<Element> commodities = new ArrayList<>();

    public void addElement(Element element) {
     
        commodities.add(element);
    }

    public void accept(Visitor visitor) {
     
        for (Element commodity : commodities) {
     
            commodity.accept(visitor);
        }
    }
}

Element.java

/**
 * 京东商城销售的商品的抽象接口,充当Element角色
 */
public interface Element {
     
    void accept(Visitor visitor);
}

BookElement.java

/**
 * 图书商品:充当具体元素ConcreteElement
 */
public class BookElement implements Element {
     

    /**
     * 书本原价69元
     */
    private Double originalPrice = 69D;

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

    Double getOriginalPrice() {
     
        return originalPrice;
    }
}

FoodElement.java

/**
 * 东商城销售的食品,充当具体元素ConcreteElement
 */
public class FoodElement implements Element {
     

    /**
     * 食品原价50元
     */
    private Double originalPrice = 50D;

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


    Double getOriginalPrice() {
     
        return originalPrice;
    }
}

Visitor.java

/**
 * 观察者抽象接口
 */
public interface Visitor {
     
    /**
     * 针对具体元素书本的访问行为
     */
    void visitBook(BookElement book);

    /**
     * 针对具体元素食品的访问行为
     */
    void visitFood(FoodElement food);
}

OrdinaryMember.java

/**
 * 普通会员:充当具体访问者角色ConcreteVisitor
 */
public class OrdinaryMember implements Visitor {
     

    /**
     * 京东普通会员购买图书打9折
     */
    @Override
    public void visitBook(BookElement book) {
     
        System.out.println("京东普通会员购买图书打9折,售价为:" + book.getOriginalPrice() * 0.9 + "元");
    }

    /**
     * 京东普通会员购买美食打8折
     */
    @Override
    public void visitFood(FoodElement food) {
     
        System.out.println("京东普通会员购买美食打8折,售价为:" + food.getOriginalPrice() * 0.8 + "元");
    }
}

PlusMember.java

/**
 * 京东Plus会员:充当具体访问者角色ConcreteVisitor
 */  
public class PlusMember implements Visitor {
     

    /**
     * 京东Plus会员购买图书打8折
     */
    @Override
    public void visitBook(BookElement book) {
     
        System.out.println("京东Plus会员购买图书打8折,售价为:" + book.getOriginalPrice() * 0.8 + "元");
    }

    /**
     * 京东Plus会员购买美食打7.5折
     */
    @Override
    public void visitFood(FoodElement food) {
     
        System.out.println("京东Plus会员购买美食打7.5折,售价为:" + food.getOriginalPrice() * 0.75 + "元");
    }
}

Client.java

/**
 * Client: 客户端
 */
public class Client {
     

    public static void main(String[] args) {
     

        // 1. 创建观察者:普通会员和Plus会员
        Visitor ordinaryMember = new OrdinaryMember();
        Visitor plusMember = new PlusMember();

        // 2. 创建具体元素:书本和食品
        Element book = new BookElement();
        Element food = new FoodElement();

        // 3. 聚合具体的元素对象
        JDMall jdMall = new JDMall();
        jdMall.addElement(book);
        jdMall.addElement(food);

        // 4. 不同的商品accept不同的观察者
        //     输出:京东普通会员购买图书打9折,售价为:62.1元
        //          京东普通会员购买美食打8折,售价为:40.0元
        //          京东Plus会员购买图书打8折,售价为:55.2元
        //          京东Plus会员购买美食打7.5折,售价为:37.5元
        jdMall.accept(ordinaryMember);
        jdMall.accept(plusMember);
    }
}
7. 源码展示

Spring中的BeanDefinition有许多属性,其中就包括parentName、beanClassName、scope等属性。Spring在解析这些属性时,采用的便是访问者模式。BeanDefinition可以看成访问者模式中的ObjectStruct角色,聚合了许多属性(如上述列出的)。这些属性对应模式中的ConcreteElement角色。属性解析者是BeanDefinitionVisitor,即观察者。BeanDefinitionVisitor根据BeanDefinition属性的不同,进行不同的解析。其中源码如下:

BeanDefinition.java

/**
 * BeanDefinition,对应访问者模式中的ObjectStruct,聚合了属性对象
 */
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
     
    /**
     * BeanDefinition中的属性,对应访问者模式中的ConcreteElement
     */
    String getParentName();
    String getBeanClassName();
    String getBeanClassName();
    String getFactoryBeanName();
    String getScope();
}

BeanDefinitionVisitor.java

public class BeanDefinitionVisitor {
     
    /**
     * 针对BeanDefinition中不同的属性,采用不同的visit方法
     */
    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());
        }
    } 
}

你可能感兴趣的:(设计模式,访问者模式,访问者模式源码实现,Spring中的访问者模式,访问者模式UML类图,访问者模式使用场景)