设计模式:实战访问者模式

访问者模式介绍

访问者要解决的核心事项是,在一个稳定的数据结构下,例如用户信息、雇员信息等,增加易变的业务访说白了访问者模式的核心在于同一个事物不同视角下的访问信息不同,比如一个美女手里拿个冰激凌。小朋友会注意冰激凌,大朋友会找自己喜欢的地方观测敌情。问逻辑。为了增强扩展性,将这两部分的业务解耦的一种设计模式。

案例场景模拟

在本案例中我们模拟校园中的学生和老师对于不同用户的访问视角

这个案例场景我们模拟校园中有学生和老师两种身份的用户,那么对于家长和校长关心的角度来看,他们的视角是不同的。家长更关心孩子的成绩和老师的能力,校长更关心老师所在班级学生的人数和升学率{此处模拟的}。

那么这样学生和老师就是一个固定信息的内容,而想让不同视角的用户获取关心的信息,就比较适合使用观察者模式来实现,从而让实体与业务解耦,增强扩展性。但观察者模式的整体类结构相对复杂,需要梳理清楚再开发

访问者模式实现代码

访问者模式的类结构相对其他设计模式来说比较复杂,但这样的设计模式在我看来更加烧气有魅力,它能阔开你对代码结构的新认知,用这样思维不断的建设出更好的代码架构。

关于这个案例的核心逻辑实现,有以下几点;

  1. 建立用户抽象类和抽象访问方法,再由不同的用户实现;老师和学生。

  1. 建立访问者接口,用于不同人员的访问操作;校长和家长。

  1. 最终是对数据的看板建设,用于实现不同视角的访问结果输出。

1. 工程结构

          java
    │       └── cn.lasse.design
    │           ├── user
    │           │      ├── impl
    │           │      │     ├── Student.java
    │           │      │     └── Teacher.java
    │           │      └── User.java   
    │           ├── visitor
    │           │      ├── impl
    │           │      │     ├── Parent.java
    │           │      │     └── Principal.java
    │           │      └── Visitor.java
    │           └──  DataView.java

访问者模式模型结构

以上是视图展示了代码的核心结构,主要包括不同视角下的不同用户访问模型。

在这里有一个关键的点非常重要,也就是整套设计模式的核心组成部分;visitor.visit(this),这个方法在每一个用户实现类里,包括;Student、Teacher。在以下的实现中可以重点关注。

2. 代码实现

2.1 定义用户抽象类

// 基础用户信息
public abstract class User {

    public String name;      // 姓名
    public String identity;  // 身份;重点班、普通班 | 特级教师、普通教师、实习教师
    public String clazz;     // 班级

    public User(String name, String identity, String clazz) {
        this.name = name;
        this.identity = identity;
        this.clazz = clazz;
    }

    // 核心访问方法
    public abstract void accept(Visitor visitor);
}
  • 基础信息包括;姓名、身份、班级,也可以是一个业务用户属性类。

  • 定义抽象核心方法,abstract void accept(Visitor visitor),这个方法是为了让后续的用户具体实现者都能提供出一个访问方法,共外部使用。

2.2 实现用户信息(老师和学生)

老师类
public class Teacher extends User {

    public Teacher(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    // 升本率
    public double entranceRatio() {
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}
学生类
public class Student extends User {

    public Student(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public int ranking() {
        return (int) (Math.random() * 100);
    }
}
  • 这里实现了老师和学生类,都提供了父类的构造函数。

  • 在accept方法中,提供了本地对象的访问;visitor.visit(this),这块需要加深理解。

  • 老师和学生类又都单独提供了各自的特性方法;升本率(entranceRatio)、排名(ranking),类似这样的方法可以按照业务需求进行扩展。

2.3 定义访问数据接口

public interface Visitor {

    // 访问学生信息
    void visit(Student student);

    // 访问老师信息
    void visit(Teacher teacher);
}
  • 访问的接口比较简单,相同的方法名称,不同的入参用户类型。

  • 让具体的访问者类,在实现时可以关注每一种用户类型的具体访问数据对象,例如;升学率和排名。

2.4 实现访问类型(校长和家长)

访问者;校长
public class Principal implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Principal.class);

    public void visit(Student student) {
        logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);
    }

    public void visit(Teacher teacher) {
        logger.info("老师信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }
}
访问者;家长
public class Parent implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Parent.class);

    public void visit(Student student) {
        logger.info("学生信息 姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    public void visit(Teacher teacher) {
        logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);
    }

}
  • 以上是两个具体的访问者实现类,他们都有自己的视角需求。

  • 校长关注;学生的名称和班级,老师对这个班级的升学率

  • 家长关注;自己家孩子的排名,老师的班级和教学水平

2.5 数据看版

public class DataView {

    List<User> userList = new ArrayList<User>();

    public DataView() {
        userList.add(new Student("谢飞机", "重点班", "一年一班"));
        userList.add(new Student("windy", "重点班", "一年一班"));
        userList.add(new Student("大毛", "普通班", "二年三班"));
        userList.add(new Student("Shing", "普通班", "三年四班"));
        userList.add(new Teacher("BK", "特级教师", "一年一班"));
        userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
        userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
        userList.add(new Teacher("泽东", "实习教师", "三年四班"));
    }

    // 展示
    public void show(Visitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }

}
  • 首先在这个类中初始化了基本的数据,学生和老师的信息。

  • 并提供了一个展示类,通过传入不同的观察者(校长、家长)而差异化的打印信息

3. 测试验证

3.1 编写测试类

@Test
public void test(){
    DataView dataView = new DataView();      

    logger.info("\r\n家长视角访问:");
    dataView.show(new Parent());     // 家长

    logger.info("\r\n校长视角访问:");
    dataView.show(new Principal());  // 校长
}

3.2 测试结果

10:03:01.366 [main] INFO cn.lasse.design.ApiTest - 
家长视角访问:
10:03:01.377 [main] INFO cn.lasse.design.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:84
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 学生信息 姓名:windy 班级:一年一班 排名:23
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 学生信息 姓名:大毛 班级:二年三班 排名:23
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 学生信息 姓名:Shing 班级:三年四班 排名:49
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 老师信息 姓名:BK 班级:一年一班 级别:特级教师
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 老师信息 姓名:娜娜Goddess 班级:一年一班 级别:特级教师
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 老师信息 姓名:dangdang 班级:二年三班 级别:普通教师
10:03:01.381 [main] INFO cn.lasse.design.visitor.impl.Parent - 老师信息 姓名:泽东 班级:三年四班 级别:实习教师
10:03:01.381 [main] INFO cn.lasse.design.ApiTest - 
校长视角访问:
10:03:01.382 [main] INFO cn.lasse.design.visitor.impl.Principal - 学生信息 姓名:谢飞机 班级:一年一班
10:03:01.382 [main] INFO cn.lasse.design.visitor.impl.Principal - 学生信息 姓名:windy 班级:一年一班
10:03:01.382 [main] INFO cn.lasse.design.visitor.impl.Principal - 学生信息 姓名:大毛 班级:二年三班
10:03:01.382 [main] INFO cn.lasse.design.visitor.impl.Principal - 学生信息 姓名:Shing 班级:三年四班
10:03:01.390 [main] INFO cn.lasse.design.visitor.impl.Principal - 老师信息 姓名:BK 班级:一年一班 升学率:69.44
10:03:01.391 [main] INFO cn.lasse.design.visitor.impl.Principal - 老师信息 姓名:娜娜Goddess 班级:一年一班 升学率:10.76
10:03:01.391 [main] INFO cn.lasse.design.visitor.impl.Principal - 老师信息 姓名:dangdang 班级:二年三班 升学率:2.02
10:03:01.392 [main] INFO cn.lasse.design.visitor.impl.Principal - 老师信息 姓名:泽东 班级:三年四班 升学率:11.47
  • 通过测试结果可以看到,家长和校长的访问视角同步,数据也是差异化的。

  • 家长视角看到学生的排名;排名:62、排名:51、排名:16、排名:98。

  • 校长视角看到班级升学率;升学率:70.62、升学率:23.15、升学率:70.98、升学率:90.14。

  • 通过这样的测试结果,可以看到访问者模式的初心和结果,在适合的场景运用合适的模式,非常有利于程序开发。

总结

  • 从以上的业务场景中可以看到,在嵌入访问者模式后,可以让整个工程结构变得容易添加和修改。也就做到了系统服务之间的解耦,不至于为了不同类型信息的访问而增加很多多余的if判断或者类的强制转换。也就是通过这样的设计模式而让代码结构更加清晰。

  • 另外在实现的过程可能你可能也发现了,定义抽象类的时候还需要等待访问者接口的定义,这样的设计首先从实现上会让代码的组织变得有些难度。另外从设计模式原则的角度来看,违背了迪米特原则,也就是最少知道原则。因此在使用上一定要符合场景的运用,以及提取这部分设计思想的精髓。

  • 好的学习方式才好更容易接受知识,学习编程的更需要的不单单是看,而是操作。二十多种设计模式每一种都有自己的设计技巧,也可以说是巧妙之处,这些巧妙的地方往往是解决复杂难题的最佳视角。亲力亲为,才能为所欲为,为了自己的欲望而努力!

项目源码

https://gitee.com/luosuichuan/lasse-design-pattern.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值