设计模式之观察者模式与访问者模式详解和应用

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之观察者模式与访问者模式详解和应用相关的知识,希望对你有一定的参考价值。

目录


1.访问者模式详解

1.1 访问者模式的定义

定义:

访问者模式【visitor Pattern】,是一种将数据结构与数据操作分离设计模式。是指

封装一些作用于某种数据结构中的各元素的操作。

特征:

可以在不改变数据结构的前提下定义作用于这些元素的新操作。

属于行为型模式。

说明:

访问者模式,被称为最复杂的设计模式。运用并不多。

1.1.1 访问者模式在生活中的体现

1.参与KPI考核的人员

KPI的考核标准,一般是固定不变的,但参与KPI考核的员工会经常变化。

kpi考核打分的人也会经常变化,

2.餐厅就餐人员

餐厅吃饭,餐厅的菜单是基本稳定的,就餐人员基本每天都在变化。就餐人员就是一个访问者。

总结:

访问者,好像就是变化的元素,与不变的结构【标准,规则】的构成关系处理角色。

1.1.2 访问者模式的适用场景

访问者模式很少能用到,一旦需要使用,涉及到的系统往往比较复杂。

1.数据结构稳定,作用于数据结构的操作经常变化的场景。

2.需要数据结构与数据操作分离的场景。

3.需要对不同数据类型(元素) 进行操作,而不使用分支判断具体类型的场景。

1.2 访问者模式的通用实现

1.3 访问者模式的使用案例之KPI考核

1.3.1 类图设计

1.3.2 代码实现

1.元素顶层接口定义

package com.oldlu.visitor.demo.kpi;

import java.util.Random;

/**
 * @ClassName Employee
 * @Description 员工,元素抽象
 * @Author oldlu
 * @Date 2020/6/24 10:38
 * @Version 1.0
 */
public abstract class Employee 
    private String name;
    private int kpi;

    public Employee(String name) 
        this.name = name;
        this.kpi = new Random().nextInt(10);
    

    public abstract void accept(IVisitor visitor);

    public String getName() 
        return name;
    

    public int getKpi() 
        return kpi;
    

2.元素具体实现

package com.oldlu.visitor.demo.kpi;

import java.util.Random;

/**
 * @ClassName Engineer
 * @Description 普通开发人员
 * @Author oldlu
 * @Date 2020/6/24 10:43
 * @Version 1.0
 */
public class Engineer extends Employee
    public Engineer(String name) 
        super(name);
    

    @Override
    public void accept(IVisitor visitor) 
        visitor.visit(this);
    
    //考核:代码量
    public int getCodingLine()
        return new Random().nextInt(100000);
    


package com.oldlu.visitor.demo.kpi;

import java.util.Random;

/**
 * @ClassName Manager
 * @Description 项目经理
 * @Author oldlu
 * @Date 2020/6/24 10:44
 * @Version 1.0
 */
public class Manager extends Employee 
    public Manager(String name) 
        super(name);
    

    @Override
    public void accept(IVisitor visitor) 
        visitor.visit(this);
    
    //考核:每年的新产品研发数量
    public int getProducts()
        return new Random().nextInt(10);
    

3.访问者顶层接口及实现

package com.oldlu.visitor.demo.kpi;

/**
 * @ClassName IVisitor
 * @Description 访问者接口
 * @Author oldlu
 * @Date 2020/6/24 10:41
 * @Version 1.0
 */
public interface IVisitor 
    //传参具体的元素
    void visit(Engineer engineer);

    void visit(Manager manager);

package com.oldlu.visitor.demo.kpi;

/**
 * @ClassName CTOVisitor
 * @Description ceo考核者,只有看kpi打分就行
 * @Author oldlu
 * @Date 2020/6/24 10:49
 * @Version 1.0
 */
public class CEOVisitor implements IVisitor

    @Override
    public void visit(Engineer engineer) 
        System.out.println("工程师:"+engineer.getName()+" ,KPI:"+engineer.getKpi());
    

    @Override
    public void visit(Manager manager) 
        System.out.println("项目经理:"+manager.getName()+" ,KPI:"+manager.getKpi());
    

package com.oldlu.visitor.demo.kpi;

/**
 * @ClassName CTOVisitor
 * @Description cto考核者
 * @Author oldlu
 * @Date 2020/6/24 10:49
 * @Version 1.0
 */
public class CTOVisitor implements IVisitor

    @Override
    public void visit(Engineer engineer) 
        System.out.println("工程师:"+engineer.getName()+" ,编写代码行数:"+engineer.getCodingLine());
    

    @Override
    public void visit(Manager manager) 
        System.out.println("项目经理:"+manager.getName()+" ,产品数量:"+manager.getProducts());
    

4.数据结构定义

package com.oldlu.visitor.demo.kpi;

import java.util.LinkedList;
import java.util.List;

/**
 * @ClassName BusinessReport
 * @Description 业务报表,数据结构
 * @Author oldlu
 * @Date 2020/6/24 10:55
 * @Version 1.0
 */
public class BusinessReport 
    private List<Employee> employeeList = new LinkedList<>();

    public BusinessReport() 
        employeeList.add(new Manager("项目经理A"));
        employeeList.add(new Manager("项目经理B"));
        employeeList.add(new Engineer("程序员A"));
        employeeList.add(new Engineer("程序员B"));
        employeeList.add(new Engineer("程序员C"));
    

    public void showReport(IVisitor visitor)
        for (Employee employee : employeeList) 
            employee.accept(visitor);
        
    

5.测试代码

package com.oldlu.visitor.demo.kpi;

/**
 * @ClassName Test
 * @Description 测试类
 * @Author oldlu
 * @Date 2020/6/24 10:54
 * @Version 1.0
 */
public class Test 
    public static void main(String[] args) 
        BusinessReport report = new BusinessReport();
        System.out.println("===========CEO看报表===============");
        report.showReport(new CEOVisitor());
        System.out.println("===========CTO看报表===============");
        report.showReport(new CTOVisitor());
    

测试结果:

说明:

访问者顶层接口定义时,内部会定义visit重载方法,针对不同的访问元素实现子类进行重载。

为什么不设计成一个方法呢?

因为这里一个方法,把具体元素关联起来。

当系统需要增加元素实现子类时,只需要增加一个实现子类,该接口中增加一个重载方法。

系统方便扩展。

1.4 访问者模式扩展—分派

java中静态分派,和动态分派。还有双分派。

Java中分派,是方法重载的一种特殊形式。即重载方法,方法名相同,参数个数相同,类型不同的形式。

1.4.1 java中静态分派示例代码

package com.oldlu.visitor.dispatch;

/**
 * @ClassName Main
 * @Description 测试静态分派
 * @Author oldlu
 * @Date 2020/6/24 11:20
 * @Version 1.0
 */
public class Main 
    public static void main(String[] args) 
        String str = "1";
        Integer integer = 1;
        Main main = new Main();
        main.test(integer);
        main.test(str);
    
    public void test(String str)
        System.out.println("String "+str);
    
    public void test(Integer integer)
        System.out.println("Integer "+integer);
    

说明:

上面测试代码中,test方法存在两个重载方法,参数个数相同,类型不同,

在编译阶段,就能清楚地知道参数类型,称为静态分派。

相同方法名,不同类型的不同方法,这种形式也称为多分派。

1.4.2 java中动态分派

在程序编译阶段,不能明确是哪种类型,只有在运行时,才能知道是哪个类型,

称为动态分派。

1.定义接口及实现

package com.oldlu.visitor.dispatch.dynamic;

/**
 * @ClassName Person
 * @Description 接口定义
 * @Author oldlu
 * @Date 2020/6/24 11:33
 * @Version 1.0
 */
public interface Person 
    void test();

package com.oldlu.visitor.dispatch.dynamic;

/**
 * @ClassName Women
 * @Description 女人
 * @Author oldlu
 * @Date 2020/6/24 11:35
 * @Version 1.0
 */
public class Women implements Person
    @Override
    public void test() 
        System.out.println("女人");
    

package com.oldlu.visitor.dispatch.dynamic;

/**
 * @ClassName Man
 * @Description 男人
 * @Author oldlu
 * @Date 2020/6/24 11:34
 * @Version 1.0
 */
public class Man implements Person 
    @Override
    public void test() 
        System.out.println("男人");
    

2.测试类

package com.oldlu.visitor.dispatch.dynamic;

/**
 * @ClassName Main
 * @Description 测试类
 * @Author oldlu
 * @Date 2020/6/24 11:35
 * @Version 1.0
 */
public class Main 
    public static void main(String[] args) 
        Person man = new Man();
        Person women = new Women();
        man.test();
        women.test();
    

说明:

当在编译期时,man或women并不知道自己是什么类型,只有在运行期,

通过new创建实例时,才能知道具体的类型,所以,是动态分派。

1.4.3 访问者模式中伪动态双分派

在数据结构中,一般会对集合元素进行遍历处理。如下所示:

可以看到,这里是一次动态分派,调用accept方法,具体的类型要到运行时,才能确定。

而且Employee也是一个抽象接口,类型不能确定。

当进入到一个子类中,如Engineer,accept方法调用visit方法,传参this,也是动态分派。需要到

运行时,才能确定类型。【因为需要在运行时创建this实例】

1.5 访问者模式在源码中应用

1.5.1 jdk中FileVisitor

FileVisitResult visitFile(T file, BasicFileAttributes attrs)
    throws IOException;

FileVisitor接口中定义visitFile方法。传参BasicFileAtributes也是一个接口。

1.5.2 spring中BeanDefinitionVisitor

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());
        
    

访问时,并未改变其中的内容,只是返回相应结果。把数据操作与结构进行分离。

1.6 访问者模式的使用总结

1.6.1 优缺点总结

优点:

1.解耦数据结构与数据操作,使用操作集合可以独立变化

2.扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作

3.元素具体类型并非单一,访问者均可操作

4.各角色职责分离,符合单一职责原则。

缺点:

1.无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,

则访问者类必须增加对应元素的操作,违背开闭原则。

2.具体元素变更困难:具体元素的增加属性,删除属性等操作会导致对应访问者类需要

相应的修改,尤其有大量访问者类时,修改范围太大。

3.违背依赖倒置原则:为了达到”区别对待“,访问者依赖的是具体元素类型,而不是抽象。

2 观察者模式详解

2.1 观察者模式的定义

定义:

观察者模式【Observer Pattern】,又叫发布-订阅【Publish/Subcribe】模式,模型-视图【Model/View】模式、

源监听器【Source/Listener】模式,从属者【Dependents】模式。

定义一种一对多的关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有

依赖它的对象都会得到通知并被自动更新。

属于行为型模式。

2.1.1 观察者模式在生活场景中的应用

1.App角标通知

2.起床闹钟设置

2.1.2 观察者模式适用场景

1.当一个抽象模型包含两个方面内容,其中一个方面依赖于另一个方面。

2.其他一个或多个对象的变化依赖于另一个对象的变化。

3.实现类似广播机制的功能,无需知道具体收听者,只需分发广播,系统中感兴趣

的对象会自动接收该广播。

4.多层嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

2.2 观察者模式应用案例之问答提示角标

在学习社区,我们有疑问,可以发布问题求助。发布问题时,可以邀请某个人【或某个老师】进行

解答。

但是,老师平时会很忙,不会总是去刷新页面。于是,就会做一个通知功能。

一旦有一个问题,向老师提出,通知图标上就会数字加1.

当老师登录到页面时,只要查看通知角标,就能知道是否有人向他提问,就方便回答。

2.2.1 类图设计

2.2.2 代码实现

说明:这里是基于jdk的发布-订阅api实现。

1.被观察者定义

package com.oldlu.observer.demo.gper;

import java.util.Observable;

/**
 * @ClassName GPer
 * @Description 社区生态圈,被观察者
 * @Author oldlu
 * @Date 2020/6/24 17:31
 * @Version 1.0
 */
public class GPer extends Observable 
    private String name = "GPer 生态圈";

    public String getName() 
        return name;
    

    private static final GPer gper = new GPer();

    private GPer() 
    

    public static GPer getInstance()
        return gper;
    

    public void publishQuestion(Question question)
        System.out.println(question.getUserName()+" 在" +this.name +"提交了一个问题");
        //调用jdk api
        setChanged();
        notifyObservers(question);
    

2.数据结构,问题类

package com.oldlu.observer.demo.gper;

/**
 * @ClassName Question
 * @Description 问题
 * @Author oldlu
 * @Date 2020/6/24 17:34
 * @Version 1.0
 */
public class Question 
    //问题发布者
    private String userName;
    //内容
    private String content;

    public void setUserName(String userName) 
        this.userName = userName;
    

    public void setContent(String content) 
        this.content = content;
    

    public String getUserName() 
        return userName;
    

    public String getContent() 
        return content;
    

3.观察者定义

package com.oldlu.observer.demo.gper;

import java.util.Observable;
import java.util.Observer;

/**
 * @ClassName Teacher
 * @Description 观察者
 * @Author oldlu
 * @Date 2020/6/24 17:38
 * @Version 1.0
 */
public class Teacher implements Observer 
    private String name;

    public Teacher(String name) 
        this.name = name;
    

    @Override
    public void update(Observable ob, Object arg) 
        GPer gper = (GPer) ob;
        Question question = (Question) arg;
        System.out.println("===================");
        System.out.println(name+"老师,你好\\n" +
                ",您收到一个来自"+gper.getName()+"的提问,希望你解答,问题内容如下:\\n"+question.getContent()+
                "\\n提问者:"+question.getUserName());
    

4.测试类

package com.oldlu.observer.demo.gper;

import javax.management.Query以上是关于设计模式之观察者模式与访问者模式详解和应用的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式之五大创建型模式(附实例和详解)

Java开发中的23种设计模式详解之三:11种行为型模式

详解Java设计模式之观察者模式(Observer Pattern)

详解Java设计模式之观察者模式(Observer Pattern)

c#面向对象10--简单工厂设计模式

设计模式之观察者模式详解