投稿    登录
欢迎加入Nice Coder,与众多Coder分享经验,群号:530244901

【原创】设计模式系列(十八)——访问者模式

设计之道 wjx@admin.cc 103浏览 0评论

概念:

      封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改,接受这个操作的数据结构可以保持不变。(维基百科)

理解概念:

      这是一个对关于对数据操作的模式,操作属于算法,元素是数据,我们使用算法操作数据,这个模式中,我们做到了把算法和数据分离。使他们解耦,操作对数据透明,即时操作改变数据还可以保持原来的结构。

应用场景:

      在我们要对一些数据进行操作的时候,而且这些操作和数据不相关,我们不希望这些操作污染数据的时候。就可以使用该模式,想象以下场景:java君的餐厅中有很多员工,还有店长,老板想了解他们的个人信息,这时候就需要java君设计一个系统让老板可以了解他们的信息。java君设计的第一版系统是这样的。

package visitors;

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

/**
 * Created by 17854 on 2016/11/11.
 */
abstract class Staff
{
    private String name;
    private int wage;
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getWage()
    {
        return wage;
    }
    public void setWage(int wage)
    {
        this.wage = wage;
    }
    public final void report()
    {
        System.out.println("姓名:"+name+"  工资:"+wage+"  "+getOtherInfo());
    }
    abstract protected String getOtherInfo();
}
class BasicStaff extends Staff
{
    private String job;
    public void setJob(String job)
    {
        this.job = job;
    }
    protected String getOtherInfo()
    {
        return "工作是:"+job;
    }
}
class Manager extends Staff
{
    private String performance;
    public void setPerformance(String performance)
    {
        this.performance = performance;
    }
    protected String getOtherInfo()
    {
        return "绩效是:"+performance;
    }
}
public class Client
{
    public static void main(String[] args)
    {
        List<Staff> staffs = new ArrayList<>();
        BasicStaff chef = new BasicStaff();
        chef.setName("厨师老王");
        chef.setWage(10);
        chef.setJob("厨子");
        BasicStaff store = new BasicStaff();
        store.setName("仓管小李");
        store.setWage(11);
        store.setJob("仓管");
        Manager manager = new Manager();
        manager.setName("店长贾富贵");
        manager.setWage(100);
        manager.setPerformance("本月营业额100w");
        staffs.add(chef);
        staffs.add(store);
        staffs.add(manager);
        staffs.forEach(Staff::report);
    }
}

输出结果如下:

姓名:厨师老王  工资:10  工作是:厨子
姓名:仓管小李  工资:11  工作是:仓管
姓名:店长贾富贵  工资:100  绩效是:本月营业额100w

看一下这个系统,首先是员工类,抽象员工类中包括员工的基本信息,姓名,工资,还有就是员工的其他信息,不同的员工负责实现不同的其他信息,这个是一个模板方法,在这里就可以看出,模板方法模式的应用十分广泛,到处可见他的身影。然后还有一个report方法,就是作报告,因为老板进行员工的资料检查的时候,直接调用员工的报告方法,员工就把自己的方法报告给老板了。然后他有两个子类,分别是店长和底层,这两个属于两类员工,底层员工的其他信息就是他的工作。然后在获取其他信息中就返回他的工作。店长的其他信息就是他的绩效,在获取其他信息中就是返回他的绩效。然后在场景类中,实例化出厨师老王,仓管小李和店长贾富贵。将他们放入List中,然后遍历他们调用report方法,这里Staff::report是jdk1.8的语法。不懂的可以参考这篇文章。最后的输出结果不出所料,输出很正常。每个人将自己的基本信息和其他信息完整的正确的输出。可是这个程序好吗?答案是不好的。这样写老板看员工们的信息是被写死的。如果老板只看店长的信息,不看基本员工的信息的话,就要直接修改源代码,不符合开闭原则,而且员工类中应该只提供自己的信息,他只是一个提供数据的javabean,作报告不是自己的责任,这又不符合单一职责原则,这时候就要用访问者模式将提供数据的职责和作报告的职责分开了。经过java君改写后的代码如下;

package visitors;

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

/**
 * Created by 17854 on 2016/11/11.
 */
interface VisitorInter
{
    void visit(BasicStaff basicStaff);
    void visit(Manager manager);
}
abstract class Staff
{
    private String name;
    private int wage;
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getWage()
    {
        return wage;
    }
    public void setWage(int wage)
    {
        this.wage = wage;
    }
    public abstract void accept(VisitorInter visitor);
}
class BasicStaff extends Staff
{
    private String job;
    public void setJob(String job)
    {
        this.job = job;
    }
    protected String getJob()
    {
        return job;
    }
    public void accept(VisitorInter visitor)
    {
        visitor.visit(this);
    }
}
class Manager extends Staff
{
    private String performance;
    public void setPerformance(String performance)
    {
        this.performance = performance;
    }
    protected String getPerformance()
    {
        return performance;
    }
    public void accept(VisitorInter visitor)
    {
        visitor.visit(this);
    }
}
class Visitor implements VisitorInter
{
    public void visit(BasicStaff basicStaff)
    {
        System.out.println(this.getBasicStaffInfo(basicStaff));
    }
    public void visit(Manager manager)
    {
        System.out.println(this.getManagerInfo(manager));
    }
    private String getBaseInfo(Staff staff)
    {
        return "姓名:"+staff.getName()+"  工资:"+staff.getWage();
    }
    private String getBasicStaffInfo(BasicStaff basicStaff)
    {
        return this.getBaseInfo(basicStaff)+"  工作:"+basicStaff.getJob();
    }
    private String getManagerInfo(Manager manager)
    {
        return this.getBaseInfo(manager)+"  绩效:"+manager.getPerformance();
    }
}
public class Client
{
    public static void main(String[] args)
    {
        List<Staff> staffs = new ArrayList<>();
        BasicStaff chef = new BasicStaff();
        chef.setName("厨师老王");
        chef.setWage(10);
        chef.setJob("厨子");
        BasicStaff store = new BasicStaff();
        store.setName("仓管小李");
        store.setWage(11);
        store.setJob("仓管");
        Manager manager = new Manager();
        manager.setName("店长贾富贵");
        manager.setWage(100);
        manager.setPerformance("本月营业额100w");
        staffs.add(chef);
        staffs.add(store);
        staffs.add(manager);
        VisitorInter visitorInter = new Visitor();
        staffs.forEach(staff->staff.accept(visitorInter));
    }
}

看下新的代码。 在抽象员工中,删去了报告方法,因为作报告这个职责不是员工的。然后我们添加了一个VisitorInter接口,这个接口中我们定义了重载的visit方法,参数分别是两个层次的具体员工,一个具体的Visitor类实现了该接口,然后重写了这两个方法,我们删除了员工的getOtherInfo方法,因为对于访问者来说,他们的所有信息是访问者知道的。每一个员工类中都有accept方法,接收一个VisitorInter参数,也就是一个访问者,然后调用访问者的访问方法传入自己。我们具体实现的访问者类,是对两类员工,普通员工和店长都可以去访问的所以最后的输出结构中,和之前的一样。如果我们想只访问店长,可以重新写一个具体访问者,然后在里面添加访问规则,让他不如访问非店长的员工,这样,我们就把作报告的职责抽取出来封装成起来,做到了单一职责。如果想添加其他的访问规则只需要重写一个访问者即可。也符合开闭原则。

提炼总结:

      先做出这个模式的一般类图:

访问者模式中一般有以下几种角色:

  •  Element 元素抽象类,这个类定义了元素的一般属性和抽象的accept方法,比如上例中的Staff类。
  •  ConcreteElement 具体元素类,这个类实现了具体的该元素的特殊的属性。和具体的接受访问者的方法。比如上例中的BasicStaff类和Manager类。
  •  Visirot 访问者接口,这个接口定义了所有的具体访问者应该实现的方法,一般是有若干个以具体的元素为参数的visit方法。比如上例中的VisitorInter接口。
  •  ConcreteVisitor 具体访问者类,他对每一类具体的元素实现了该类元素的具体访问方法,这个类应该对他要处理的那个具体元素类知根知底。比如上例中的Visitor类。
  •  ElementContainer 元素容器类,容纳所有元素的容器,可以是List,Set,Map或者是用工厂方法模式封装起来的若干个元素。我们是需要遍历该容器调用每个元素的accept方法传入具体的访问者对象。

访问者模式做到了单一职责原则,把数据和访问数据的算法分离开来,而且也很容易扩展。我们想要改变访问的策略只要重新定义一个访问者即可,灵活性极高。不过他也有缺点,我们可以看到,在很多地方我们是直接依赖具体的,比如visitor的方法定义中,这违背了依赖倒置原则。而且我们要求访问者对他要访问的元素知根知底,所以这也暴露了具体元素的细节。而且修改具体的元素比较困难,具体的元素修改,访问者类必须要修改。我们在实际的应用中,经常会有多个访问者一块出现,通过不同的算法来多次访问数据,每次访问数据产生不同的目的,经过多次访问达到最终目的。这是该模式的一个扩展。

下一篇预告:桥梁模式

转载请注明:王镜鑫的个人博客 » 【原创】设计模式系列(十八)——访问者模式

喜欢 (2)or分享 (0)

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请狠狠点击下面的