大话设计模式十二:访问者模式

一.模式定义

访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
Visitor Pattern: Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

 

二.模式要素

Vistor: 抽象访问者
ConcreteVisitor: 具体访问者
Element: 抽象元素
ConcreteElement: 具体元素 
ObjectStructure: 对象结构

 

三.举例说明

1.场景带入一

        假设你开了一家水果店,自己就是销售者。店里常年销售着西瓜、苹果两种水果。你每天都向顾客报出两种水果的价格。

        那你可能会这样组织代码。

IFruit.java  水果接口

package example;

/**
 * @program: Test
 * @description: 水果接口
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public interface IFruit {
    /**
     * 得到名称
     *
     * @return
     */
    String obtainName();

    /**
     * 返回日常的价格
     *
     * @return
     */
    int obtainCommonPrice();
}

Watermelon.java  西瓜

package example;

/**
 * @program: Test
 * @description: 西瓜
 * @author: Lei Dong
 * @create: 2019-03-03 10:30
 **/
public class Watermelon implements IFruit {
    private static final String NAME = "西瓜";

    @Override
    public String obtainName() {
        return NAME;
    }

    /**
     * 返回西瓜日常的价格
     *
     * @return
     */
    @Override
    public int obtainCommonPrice() {
        return 2;
    }
}

Apple.java  苹果

package example;

/**
 * @program: Test
 * @description: 苹果
 * @author: Lei Dong
 * @create: 2019-03-03 10:39
 **/
public class Apple implements IFruit {
    private static final String NAME = "苹果";

    @Override
    public String obtainName() {
        return NAME;
    }

    @Override
    public int obtainCommonPrice() {
        return 3;
    }
}

Seller.java  销售者

package example;

import java.util.List;

/**
 * @program: Test
 * @description: 销售者
 * @author: Lei Dong
 * @create: 2019-03-03 10:31
 **/
class Seller {
    List<IFruit> fruitList;

    Seller(List<IFruit> fruitList) {
        this.fruitList = fruitList;
    }

    /**
     * 展示水果的日常价格
     */
    void showCommonDetail() {
        System.out.println("日常价格:");
        for (IFruit fruit : fruitList) {
            System.out.println(fruit.obtainName() + ":" +  fruit.obtainCommonPrice() + "元/斤");
        }
    }
}

Main.java  

package example;

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

/**
 * @program: Test
 * @description:
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public class Main {
    public static void main(String[] args) {
        List<IFruit> fruitList = new ArrayList<>();
        fruitList.add(new Apple());
        fruitList.add(new Watermelon());

        Seller seller = new Seller(fruitList);
        seller.showCommonDetail();
    }
}

运行结果:

        你已经完美解决了现在的问题。

2.场景带入二

        但是你转念一想,好像不够完善。因为这两种水果的价钱并非一成不变,逢年过节的肯定要调整价格。这个其实也不难,你会这样写代码。

IFruit.java  水果接口(增加其他节日价格的相关返回方法

package example;

/**
 * @program: Test
 * @description: 水果接口
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public interface IFruit {
    /**
     * 得到名称
     *
     * @return
     */
    String obtainName();

    /**
     * 返回日常的价格
     *
     * @return
     */
    int obtainCommonPrice();

    /**
     * 返回春节的价格
     *
     * @return
     */
    int obtainSpringFestivalPrice();

    /**
     * 返回情人节的价格
     *
     * @return
     */
    int obtainValentinesDayPrice();
}

Watermelon.java  西瓜(增加实现新的接口方法

package example;

/**
 * @program: Test
 * @description: 西瓜
 * @author: Lei Dong
 * @create: 2019-03-03 10:30
 **/
public class Watermelon implements IFruit {
    private static final String NAME = "西瓜";

    @Override
    public String obtainName() {
        return NAME;
    }

    @Override
    public int obtainCommonPrice() {
        return 2;
    }

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

    @Override
    public int obtainValentinesDayPrice() {
        return 5;
    }
}

Apple.java  苹果(增加实现新的接口方法

package example;

/**
 * @program: Test
 * @description: 苹果
 * @author: Lei Dong
 * @create: 2019-03-03 10:39
 **/
public class Apple implements IFruit {
    private static final String NAME = "苹果";

    @Override
    public String obtainName() {
        return NAME;
    }

    @Override
    public int obtainCommonPrice() {
        return 3;
    }

    @Override
    public int obtainSpringFestivalPrice() {
        return 12;
    }

    @Override
    public int obtainValentinesDayPrice() {
        return 30;
    }
}

Seller.java  销售者(添加展示春节水果价格和情人节水果价格的方法

package example;

import java.util.List;

/**
 * @program: Test
 * @description: 销售者
 * @author: Lei Dong
 * @create: 2019-03-03 10:31
 **/
class Seller {
    List<IFruit> fruitList;

    Seller(List<IFruit> fruitList) {
        this.fruitList = fruitList;
    }

    /**
     * 展示水果的日常价格
     */
    void showCommonDetail() {
        System.out.println("日常价格:");
        for (IFruit fruit : fruitList) {
            System.out.println(fruit.obtainName() + ":" +  fruit.obtainCommonPrice() + "元/斤");
        }
    }

    /**
     * 展示水果的春节价格
     */
    void showSpringFestivalDetail() {
        System.out.println("春节价格:");
        for (IFruit fruit : fruitList) {
            System.out.println(fruit.obtainName() + ":" +  fruit.obtainSpringFestivalPrice() + "元/斤");
        }
    }

    /**
     * 展示水果的情人节价格
     */
    void showValentinesDayPrice() {
        System.out.println("情人节价格:");
        for (IFruit fruit : fruitList) {
            System.out.println(fruit.obtainName() + ":" + fruit.obtainValentinesDayPrice() + "元/斤");
        }
    }
}

Main.java

package example;

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

/**
 * @program: Test
 * @description:
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public class Main {
    public static void main(String[] args) {
        List<IFruit> fruitList = new ArrayList<>();
        fruitList.add(new Apple());
        fruitList.add(new Watermelon());

        Seller seller = new Seller(fruitList);
        seller.showCommonDetail();

        seller.showSpringFestivalDetail();

        seller.showValentinesDayPrice();
    }
}

运行结果:

        这种实现的变化就是,每增加一个节日,就需要在水果接口IFruit中添加一个返回该节日水果价格的接口,具体的水果类再去实现新增的接口。然后需要在Seller类中新建新的方法去实现对该节日水果价格的展示。动IFruit接口和在具体的水果类中实现接口方法还能忍,但是频繁改动Seller类就比较难受了。

        有没有办法说不改变Seller类也能实现这样的功能呢?有的,那就是访问者模式的写法。

        访问者模式的UML图如下:

        

四.代码实现

IFruit.java  水果接口

package example;

/**
 * @program: Test
 * @description: 水果接口
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public interface IFruit {
    /**
     * 得到名称
     *
     * @return
     */
    String obtainName();

    /**
     * 接受访问者
     *
     * @param visitor
     */
    void accept(IVisitor visitor);

    /**
     * 返回日常的价格
     *
     * @return
     */
    int obtainCommonPrice();

    /**
     * 返回春节的价格
     *
     * @return
     */
    int obtainSpringFestivalPrice();

    /**
     * 返回情人节的价格
     *
     * @return
     */
    int obtainValentinesDayPrice();
}

Watermelon.java  西瓜

package example;

/**
 * @program: Test
 * @description: 西瓜
 * @author: Lei Dong
 * @create: 2019-03-03 10:30
 **/
public class Watermelon implements IFruit {
    private static final String NAME = "西瓜";

    @Override
    public String obtainName() {
        return NAME;
    }

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

    @Override
    public int obtainCommonPrice() {
        return 2;
    }

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

    @Override
    public int obtainValentinesDayPrice() {
        return 5;
    }
}

Apple.java  苹果

package example;

/**
 * @program: Test
 * @description: 苹果
 * @author: Lei Dong
 * @create: 2019-03-03 10:39
 **/
public class Apple implements IFruit {
    private static final String NAME = "苹果";

    @Override
    public String obtainName() {
        return NAME;
    }

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

    @Override
    public int obtainCommonPrice() {
        return 3;
    }

    @Override
    public int obtainSpringFestivalPrice() {
        return 12;
    }

    @Override
    public int obtainValentinesDayPrice() {
        return 30;
    }
}

Seller.java  销售者

package example;

import java.util.List;

/**
 * @program: Test
 * @description: 销售者
 * @author: Lei Dong
 * @create: 2019-03-03 10:31
 **/
class Seller {
    List<IFruit> fruitList;

    Seller(List<IFruit> fruitList) {
        this.fruitList = fruitList;
    }

    void showPrice(IVisitor visitor) {
        for (IFruit fruit : fruitList) {
            fruit.accept(visitor);
        }
    }
}

IVisitor.java  访问者接口

package example;

/**
 * @program: Test
 * @description: 访问者接口
 * @author: Lei Dong
 * @create: 2019-03-03 11:38
 **/
public interface IVisitor {
    /**
     * 访问西瓜
     *
     * @param watermelon
     */
    void visit(Watermelon watermelon);

    /**
     * 访问苹果
     *
     * @param apple
     */
    void visit(Apple apple);
}

CommonVisitor.java  日常访问者

package example;

/**
 * @program: Test
 * @description: 日常访问者
 * @author: Lei Dong
 * @create: 2019-03-03 11:41
 **/
public class CommonVisitor implements IVisitor {
    @Override
    public void visit(Watermelon watermelon) {
        System.out.println(watermelon.obtainName() + "的日常价格是:" + watermelon.obtainCommonPrice() + "元/斤");
    }

    @Override
    public void visit(Apple apple) {
        System.out.println(apple.obtainName() + "的日常价格是:" + apple.obtainCommonPrice() + "元/斤");
    }
}

SpringFestivalVisitor.java  春节访问者

package example;

/**
 * @program: Test
 * @description: 春节访问者
 * @author: Lei Dong
 * @create: 2019-03-03 11:44
 **/
public class SpringFestivalVisitor implements IVisitor {
    @Override
    public void visit(Watermelon watermelon) {
        System.out.println(watermelon.obtainName() + "春节的价格是:" + watermelon.obtainSpringFestivalPrice() + "元/斤");
    }

    @Override
    public void visit(Apple apple) {
        System.out.println(apple.obtainName() + "春节的价格是:" + apple.obtainSpringFestivalPrice() + "元/斤");
    }
}

ValentinesDayVisitor.java  情人节访问者

package example;

/**
 * @program: Test
 * @description: 情人节访问者
 * @author: Lei Dong
 * @create: 2019-03-03 11:48
 **/
public class ValentinesDayVisitor implements IVisitor {
    @Override
    public void visit(Watermelon watermelon) {
        System.out.println(watermelon.obtainName() + "情人节的价格是:" + watermelon.obtainValentinesDayPrice() + "元/斤");
    }

    @Override
    public void visit(Apple apple) {
        System.out.println(apple.obtainName() + "情人节的价格是:" + apple.obtainValentinesDayPrice() + "元/斤");
    }
}

Main.java

package example;

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

/**
 * @program: Test
 * @description:
 * @author: Lei Dong
 * @create: 2019-03-03 10:27
 **/
public class Main {
    public static void main(String[] args) {
        List<IFruit> fruitList = new ArrayList<>();
        fruitList.add(new Apple());
        fruitList.add(new Watermelon());
        Seller seller = new Seller(fruitList);

        IVisitor commonVisitor = new CommonVisitor();
        seller.showPrice(commonVisitor);

        IVisitor springFestivalVisitor = new SpringFestivalVisitor();
        seller.showPrice(springFestivalVisitor);

        IVisitor valentinesDayVisitor = new ValentinesDayVisitor();
        seller.showPrice(valentinesDayVisitor);
    }
}

运行结果:

        结合UML图来看。

        Visitor指代:IVisitor

        ConcreteVisitor指代:CommonVisitor、SpringFestivalVisitor、ValentinesDayVisitor

        Element指代:IFruit

        ConcreteElement指代:Watermelon、Apple

        Object Structure指代:Seller

        Client指代:Main

        

五.总结

1.模式优点

(1)使得增加新的访问操作变得很容易。
(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。 
(3)可以跨过类的等级结构访问属于不同的等级结构的元素类。
(4)让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作。

2.模式缺点

(1)增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求。
(2)破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

3.模式适用场景

(1)一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
(2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
(3)对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值