目录
- 1.访问者模式详解
- 1.1 访问者模式的定义
- 1.1.1 访问者模式在生活中的体现
- 1.1.2 访问者模式的适用场景
- 1.2 访问者模式的通用实现
- 1.3 访问者模式的使用案例之KPI考核
- 1.3.1 类图设计
- 1.3.2 代码实现
- 1.4 访问者模式扩展---分派
- 1.4.1 java中静态分派示例代码
- 1.4.2 java中动态分派
- 1.4.3 访问者模式中伪动态双分派
- 1.5 访问者模式在源码中应用
- 1.5.1 jdk中FileVisitor
- 1.5.2 spring中BeanDefinitionVisitor
- 1.6 访问者模式的使用总结
- 1.6.1 优缺点总结
- 2 观察者模式详解
- 2.1 观察者模式的定义
- 2.1.1 观察者模式在生活场景中的应用
- 2.1.2 观察者模式适用场景
- 2.2 观察者模式应用案例之问答提示角标
- 2.2.1 类图设计
- 2.2.2 代码实现
- 2.3 google开源组件实现观察者模式
- 2.3.1 示例代码
- 2.4 观察者模式应用案例之鼠标事件交互
- 2.4.1 类图设计
- 2.4.2 代码实现
- 2.5 观察者模式在源码中的应用
- 2.5.1 jdk中ServletContextListener
- 2.5.2 spring中ContextLoaderListener
- 2.6 观察者的使用总结
- 2.6.1 优点总结
- 2.6.2 缺点总结
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;
/**
* @ClassName Test
* @Description 测试类
* @Author oldlu
* @Date 2020/6/24 17:44
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
GPer gper = GPer.getInstance();
Teacher tom = new Teacher("tom");
Teacher jerry = new Teacher("Jerry");
gper.addObserver(tom);
gper.addObserver(jerry);
//用户行为
Question question = new Question();
question.setUserName("张三");
question.setContent("观察者模式适用于哪些场景?");
gper.publishQuestion(question);
}
}
2.3 google开源组件实现观察者模式
依赖包:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
2.3.1 示例代码
1.观察者类
package com.oldlu.observer.guava;
import com.google.common.eventbus.Subscribe;
/**
* @ClassName GuavaEvent
* @Description 观察者
* @Author oldlu
* @Date 2020/6/24 18:07
* @Version 1.0
*/
public class GuavaEvent {
//表示观察者回调
@Subscribe
public void observer(String str){
System.out.println("执行observer方法,传参为:"+str);
}
}
2.测试类
package com.oldlu.observer.guava;
import com.google.common.eventbus.EventBus;
/**
* @ClassName Test
* @Description 测试类
* @Author oldlu
* @Date 2020/6/24 18:09
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
GuavaEvent event = new GuavaEvent();
eventBus.register(event);
eventBus.post("tom");
}
}
测试结果:
2.4 观察者模式应用案例之鼠标事件交互
当鼠标发起某个动作【如:单击,移动,滚动。。。】,得到相应的响应。
针对发出动作,需要进行监听【现实中操作系统进行监听】
鼠标:当成被观察者实现。
事件监听器:监听动作【触发一个事件】
业务类:事件类【传递参数,区分不同事件或动作】
事件回调:监听后,需要作出响应,进行回调。充当观察者。
2.4.1 类图设计
2.4.2 代码实现
1.事件监听器
package com.oldlu.observer.mouseclick.core;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName EventListener
* @Description 事件监听器,被观察者的抽象
* @Author oldlu
* @Date 2020/6/24 18:21
* @Version 1.0
*/
public class EventListener {
protected Map<String, Event> events = new HashMap<String,Event>();
public void addListener(String eventType, Object target, Method callback){
events.put(eventType,new Event(target,callback));
}
public void addListener(String eventType, Object target){
try{
this.addListener(eventType,target,target.getClass().getMethod("on"+ toUpperFirstCase(eventType),Event.class));
}catch (Exception e){
e.printStackTrace();
}
}
private String toUpperFirstCase(String eventType) {
char [] chars = eventType.toCharArray();
if(chars[0]> 'a' && chars[0] < 'z'){
chars[0] -= 32;
}
return String.valueOf(chars);
}
private void trigger(Event event){
event.setSource(this);
event.setTime(System.currentTimeMillis());
try{
if(null != event.getCallback()){
//反射调用 回调函数
event.getCallback().invoke(event.getTarget(),event);
}
}catch (Exception e){
e.printStackTrace();
}
}
protected void trigger(String trigger){
if(!this.events.containsKey(trigger)){return;}
//如果已进行注册,回调
trigger(this.events.get(trigger).setTrigger(trigger));
}
}
2.被监听对象,鼠标类
package com.oldlu.observer.mouseclick.event;
import com.oldlu.observer.mouseclick.core.EventListener;
/**
* @ClassName Mouse
* @Description 具体的被观察者
* @Author oldlu
* @Date 2020/6/24 18:21
* @Version 1.0
*/
public class Mouse extends EventListener {
public void click() {
System.out.println("调用单机方法");
this.trigger(MouseEventType.ON_CLICK);
}
}
3.事件回调
package com.oldlu.observer.mouseclick.event;
import com.oldlu.observer.mouseclick.core.Event;
/**
* @ClassName MouseCallback
* @Description 事件响应,回调,观察者
* @Author oldlu
* @Date 2020/6/24 18:22
* @Version 1.0
*/
public class MouseEventCallback {
public void onClick(Event event){
System.out.println("=============触发鼠标单击事件========\n"+event);
}
public void onMove(Event event){
System.out.println("触发鼠标双击事件");
}
}
4.传参bean,事件类
package com.oldlu.observer.mouseclick.core;
import java.lang.reflect.Method;
/**
* @ClassName Event
* @Description 事件抽象,传参对象
* @Author oldlu
* @Date 2020/6/24 18:22
* @Version 1.0
*/
public class Event {
//事件源,如:鼠标,键盘
private Object source;
//事件触发,要通知谁(观察者)
private Object target;
private Method callback;
//事件名称
private String trigger;
//事件触发时间
private Long time;
public Event(Object target, Method callback) {
this.target = target;
this.callback = callback;
}
public Object getSource() {
return source;
}
public Event setSource(Object source) {
this.source = source;
return this;
}
public Object getTarget() {
return target;
}
public Event setTarget(Object target) {
this.target = target;
return this;
}
public Method getCallback() {
return callback;
}
public Event setCallback(Method callback) {
this.callback = callback;
return this;
}
public String getTrigger() {
return trigger;
}
public Event setTrigger(String trigger) {
this.trigger = trigger;
return this;
}
public Long getTime() {
return time;
}
public Event setTime(Long time) {
this.time = time;
return this;
}
@Override
public String toString() {
return "Event{" +
"source=" + source +
", target=" + target +
", callback=" + callback +
", trigger='" + trigger + '\'' +
", time=" + time +
'}';
}
}
5.事件类型常量定义
package com.oldlu.observer.mouseclick.event;
/**
* @ClassName MouseEventType
* @Description 鼠标事件
* @Author oldlu
* @Date 2023/2/19 10:47
* @Version 1.0
*/
public interface MouseEventType {
String ON_CLICK = "click";
}
6.测试类
package com.oldlu.observer.mouseclick;
import com.oldlu.observer.mouseclick.event.Mouse;
import com.oldlu.observer.mouseclick.event.MouseEventCallback;
import com.oldlu.observer.mouseclick.event.MouseEventType;
/**
* @ClassName Test
* @Description 测试类
* @Author oldlu
* @Date 2023/2/19 11:15
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
MouseEventCallback callback = new MouseEventCallback();
Mouse mouse = new Mouse();
mouse.addListener(MouseEventType.ON_CLICK,callback);
mouse.click();
}
}
测试结果如下:
2.5 观察者模式在源码中的应用
2.5.1 jdk中ServletContextListener
实际上,观察者模式的典型提示,*Listener【监听器】
public interface ServletContextListener extends EventListener {
public void contextInitialized ( ServletContextEvent sce );
public void contextDestroyed ( ServletContextEvent sce );
}
2.5.2 spring中ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
2.6 观察者的使用总结
2.6.1 优点总结
1.观察者与被观察者是松耦合的,符合依赖倒置原则。
2.分离表示层【观察者】和数据逻辑层【被观察者】,并且建立了一套触发机制,使得数据的变化可以
响应到多个表示层上。
3.实现一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣
的观察者可以接收到通知。
2.6.2 缺点总结
1.如果观察者数量过多,,则事件通知会耗时较长。
2.事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件。
3.如果观察者和被观察者之间存在循环依赖,则可能造成两者之间循环调用,导致系统崩溃。