首页 > 编程语言 >Java设计模式

Java设计模式

时间:2024-06-10 23:58:32浏览次数:18  
标签:订阅 Java chain void 观察者 class 设计模式 public

行为设计模式

观察者模式

定义

观察者是一种行为设计模式,观察者模式通常由两个对象组成:观察者(事件和发布器)和被观察者(监听器)。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”。

特点

优点:

降耦:观察者和被观察者之间不需要知道对方的具体实现,只需要知道对方实现的接口,降低了耦合度

增加灵活度:程序的运行中可以动态的进行添加或者删除观察者对象,增加了灵活度

符合开闭原则:对新增开放,而不需要修改原来被观察的代码

缺点:

  • 当观察者未被正确移除时候,会导致内存泄漏的问题
  • 会产生循环依赖的问题
组成
  • 抽象被观察者(Subject):定义了一个接口,包含了注册观察者、删除观察者、通知观察者等方法。
  • 具体被观察者(ConcreteSubject):实现了抽象被观察者接口,维护了一个观察者列表,并在状态发生改变时通知所有注册的观察者。
  • 抽象观察者(Observer):定义了一个接口,包含了更新状态的方法。
  • 具体观察者(ConcreteObserver):实现了抽象观察者接口,存储了需要观察的被观察者对象,并在被观察者状态发生改变时进行相应的处理。
应用场景
报纸订阅例子

被观察者接口

/**
 * @author Created by njy on 2023/6/1
 * 报纸接口,即被观察者接口
 */
public interface Newspaper {
    /**
     * 添加订阅者
     * @param subscriber
     */
    void addSubscriber(Subscriber subscriber);
 
    /**
     * 移除订阅者
     * @param subscriber
     */
    void removeSubscriber(Subscriber subscriber);
 
    /**
     * 通知订阅者
     * @param message
     */
    void notifySubscribers(String message);
}

观察者接口

/**
 * @author Created by njy on 2023/6/1
 * 订阅者(即观察者)接口
 */
public interface Subscriber {
    void update(String message);
}

被观察者实现

import java.util.ArrayList;
import java.util.List;
/**
 * @author Created by njy on 2023/6/1
 * 报纸实现类
 */
public class NewspaperImpl implements Newspaper{
    //订阅者集合
    List<Subscriber> subscribers = new ArrayList<>();
    //添加订阅者
    @Override
    public void addSubscriber(Subscriber subscriber) {
        subscribers.add(subscriber);
    }
    //移除订阅者
    @Override
    public void removeSubscriber(Subscriber subscriber) {
        subscribers.remove(subscriber);
    }
    //通知订阅者
    @Override
    public void notifySubscribers(String message) {
        for (Subscriber s : subscribers) {
            s.update(message);
        }
    }
}

观察者实现

import lombok.AllArgsConstructor;
import lombok.Data;
 
/**
 * @author Created by njy on 2023/6/1
 * 具体订阅者
 */
@Data
@AllArgsConstructor
public class SubscriberImpl implements Subscriber{
    private String name;
 
    @Override
    public void update(String message) {
        System.out.println(name + "---接到消息: " + message);
    }
}
Spring中的事件编程

Spring中的事件编程模型就是观察者模式的实现,具体通过一个类:ApplicationEvent(事件),和两个接口进行实现:ApplicationListener(监听器), ApplicationEventPublisherAware(发布器)。Application其实就是ApplicationContext,ApplicationContext内置了几个事件,其中比较容易理解的是:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent,从名称上来看,就知道这几个事件是什么时候被触发的了。

底层实现:

事件publish发布以后调用**multicastEvent方法查找所有Listener以后过滤出对应的监听器,然后调用监听器的doInvokeListener方法,方法中执行onApplicationEvent**方法执行监听到的逻辑。

自定义事件

// 自定义的事件
public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

自定义监听器

@Component //需要注册到spring容器中去
public class MyListener implements ApplicationListener<MyEvent> {
    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("我订阅的事件已经到达");
    }
}

事件发布者

发布者,需要实现ApplicationEventPublisherAware 接口,重写publish方法,方法的参数obj就是用来存放发布事件数据。setApplicationEventPublisher是Spring内部主动调用的,可以简单的理解为初始化发布者。

@Component
public class MyEventPublish implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void publish(Object obj) {
        this.publisher.publishEvent(obj);
    }
}

测试

@SpringBootTest
class MyApplicationTests {

    @Autowired
    MyEventPublish publish;
    
    @Test
    void contextLoads() {
        publish.publish(new MyEvent(this));
    }

}

发布订阅模式

定义

发布-订阅模式(Publish-Subscribe Pattern)是一种行为型设计模式,用于解耦生产者(发布者)和消费者(订阅者)之间的关系。在这种模式中,发布者负责发布消息,而订阅者则可以选择订阅他们感兴趣的消息类型。当有新消息发布时,订阅者将收到通知并执行相应的操作。

特点

优点:

  1. 解耦性:实现了消费者和生产者之间的解耦,双方通过消息队列进行通信,彼此不直接依赖或知晓对方的存在
  2. 灵活性:可以任意的增添发布者或者订阅者,并且支持多对多的通信。发布订阅者可以根据需求动态的添加,删除或修改。
  3. 扩展性:发布订阅者之间的解耦,系统可以动态的进行扩展。
  4. 异步通信:发布订阅是通过消息队列或者总线的形式进行,所以支持异步通信

缺点:

  1. 消息的传递顺序难以保证:消息是异步发送的,所以顺序不能保证
  2. 消息处理延迟:因为是异步的,所以消息的传递和处理存在一定的延迟
组成
  • 发布者(Publisher):负责发布消息的组件。它们通常不知道谁会接收到消息,只是将消息发送给与之连接的消息队列或主题。
  • 订阅者(Subscriber):订阅特定类型的消息,并在该类型的消息被发布时接收到通知。订阅者可以根据自己的需求选择订阅的消息类型。
  • 消息(Message):由发布者发布并由订阅者接收的信息单元。消息可以是任何形式的数据,例如文本、JSON、XML等。
  • 主题(Topic):定义消息类型的逻辑通道或分类。发布者将消息发布到特定的主题,而订阅者则根据需要订阅特定的主题。
  • 消息队列(Message Queue):用于在发布者和订阅者之间传递消息的中介服务。它可以确保消息的异步传输,并提供缓冲和路由消息的功能。
  • 事件总线(Event Bus):类似于消息队列,用于在组件之间传递消息,但通常更为轻量级,通常在单个应用程序内部使用。
应用场景
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 定义事件类
class Event {
    private String message;

    public Event(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

// 定义发布者类
class Publisher {
    private Map<String, List<Subscriber>> subscribers = new HashMap<>();

    // 订阅
    public void subscribe(String eventType, Subscriber subscriber) {
        subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(subscriber);
    }

    // 发布消息
    public void publish(String eventType, Event event) {
        List<Subscriber> subscribersList = subscribers.getOrDefault(eventType, new ArrayList<>());
        for (Subscriber subscriber : subscribersList) {
            subscriber.notify(event);
        }
    }
}

// 定义订阅者接口
interface Subscriber {
    void notify(Event event);
}

// 定义具体的订阅者类
class ConcreteSubscriber implements Subscriber {
    private String name;

    public ConcreteSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void notify(Event event) {
        System.out.println(name + " received message: " + event.getMessage());
    }
}

public class PublishSubscribeExample {
    public static void main(String[] args) {
        Publisher publisher = new Publisher();

        // 创建两个订阅者
        Subscriber subscriber1 = new ConcreteSubscriber("Subscriber 1");
        Subscriber subscriber2 = new ConcreteSubscriber("Subscriber 2");

        // 订阅事件类型为 "news"
        publisher.subscribe("news", subscriber1);
        publisher.subscribe("news", subscriber2);

        // 发布事件类型为 "news" 的消息
        publisher.publish("news", new Event("Breaking news: COVID restrictions lifted!"));
    }
}

常见面试题

发布-订阅模式的基本概念是什么?

答案:发布-订阅模式是一种软件架构模式,用于解耦生产者(发布者)和消费者(订阅者)之间的关系。在这种模式中,发布者负责发布消息,而订阅者则可以选择订阅他们感兴趣的消息类型。

发布-订阅模式与观察者模式有何区别?

答案:发布-订阅模式和观察者模式都用于处理对象之间的通信,但它们之间有一些区别。观察者模式中,主题(被观察者)维护了一组观察者对象,并在状态发生变化时通知它们。而在发布-订阅模式中,发布者和订阅者之间没有直接的关联,发布者将消息发布到特定的主题,而订阅者可以选择订阅他们感兴趣的主题,从而解耦了生产者和消费者。

责任链模式

定义

是一种行为设计模式,通常使用一条链来处理请求,该请求沿着链的顺序传递,直到有对象处理该请求为止,从而达到解耦请求发送者和请求处理者的目的。

image-20240407113804192
特点

优点:

解耦:将请求发送者和处理者解耦

灵活度:可以灵活的调整顺序

简化连接:责任链中,只需要保持一个后继引用,不需要大量的if_else

缺点:

可能死循环:如果两个处理类之间有循环引用的问题会造成死循环

组成

1. BaseHandler

首先我们创建一个BaseHandler类,定义了一个大致的框架,包括了下一个处理器的指针,本处理器的执行逻辑

package com.example.proxy.chain;

/**
 * BaseHandler class
 *
 * @author TransientBa
 * @date 2018/3/10
 */
public abstract class BaseHandler {
    /**
     * 用来存储下一个Handler
     */
    private BaseHandler nextHandler;

    public BaseHandler getNextHandler() {
        return nextHandler;
    }

    public void setNextHandler(BaseHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    /** 如果nextHandler不为空  则执行下一个Handler**/
    public void execute(){
        handlerProcess();
        if( nextHandler != null){
            nextHandler.execute();
        }
    }

    /**
     * Handler
     * @return void
     */
    protected abstract void handlerProcess();
}

2. Client测试

在这个类中我们首先创建三个Handler来继承上面的BaseHandler类,重写其中的handlerProcess方法
,然后在实例化三个雷,并将它们按照顺序赋值给各自的NextHandler,然后我们执行一下

package com.example.proxy.chain;

/**
 * Client class
 *
 * @author TransientBa
 * @date 2018/3/12
 */
public class Client {
    /**创建ABC三个Handler来继承BaseHandler**/
    static class HandlerA extends BaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handle by a");
        }
    }
    static class HandlerB extends BaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handle by b");
        }
    }
    static class HandlerC extends BaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handle by c");
        }
    }

    public static void main(String[] args) {
        BaseHandler handlerA = new HandlerA();
        BaseHandler handlerB = new HandlerB();
        BaseHandler handlerC = new HandlerC();
        /**
         * 将下个Handler赋值给当前Handler中的NextHandler属性
         * 有点像HashMap中的Node  每个Node存储着下一个Node
         */
        handlerA.setNextHandler(handlerB);
        handlerB.setNextHandler(handlerC);
        handlerA.execute();

    }


}

应用场景

aop的责任链模式

aop的责任链模式是在上面的基础上进行的优化部分,让我们创建的时候可以不用挨个添加责任链

底层原理

第一步:AOP的责任链是先通过**CglibAopProxy类中的intercept**方法拦截

第二步:**CglibAopProxy中的内部类CglibMethodInvocationReflectiveMethodInvocation类的子类,然后调用父类的proceed**方法来开始链式的执行

第三步:按照**interceptorsAndDynamicMethodMatchers**集合中保存的链式处理方法进行处理

1.Chain

这里创建一个chain类,这个类用来做什么呢?我们将上面的BaseHandler类拆分为ChainBaseHandler类和Chain类两个类来实现链式调用

package com.example.proxy.chain;

import java.util.List;

/**
 * Chain class
 *
 * @author TransientBa
 * @date 2018/3/12
 */
public class Chain {
    private List<ChainBaseHandler> handlers;
    /**
     * 用来表示handlers的游标
     */
    private int index = -1;

    public  Chain(List<ChainBaseHandler> handlers){
        this.handlers = handlers;
    }

    /**
     * 通过index自增 调用excute实现递归  遍历整个handlers
     */
    public void proceed(){
        if(index == handlers.size() - 1){
            return;
        }
        handlers.get(++index).execute(this);
    }
}

在chain中我们取消了NextHandler属性,而是改用了一个List,类型是ChainBaseHandler,
还多了一个index,初始值为-1,
然后是一个构造,在实例化时要传入一个List进来,并赋值给该类的handlers属性,
最后是proceed方法,在这个方法中我们首先判断了index是否超过List的size,如果超过了则返回,如果没操作,就得到当索引的Handler,然后执行ChainBaseHandler中的execute方法

2. ChainBaseHandler

在这个类中我们只有两个方法,execute和handlerProcess,其中handlerProcess依然用来被继承后写执行过程。
execute方法有一个参数Chain,这个方法首先进来会执行当前Handler的HandlerProcess执行过程,然后会调用传入Chain的proceed方法,这样调用就又回到了proceed中,继续判断index并开始下一次循环

package com.example.proxy.chain;

/**
 * ChainBaseHandler class
 *
 * @author TransientBa
 * @date 2018/3/12
 */
public abstract class ChainBaseHandler {
    /**同样先执行自己的动作 再调用chain的proceed去遍历下一个Handler**/
    public void execute(Chain chain){
        handlerProcess();
        chain.proceed();
    };
    protected abstract void handlerProcess();
}

3. 测试

package com.example.proxy.chain;

import java.util.Arrays;
import java.util.List;

/**
 * ChainClient class
 *
 * @author TransientBa
 * @date 2018/3/12
 */
public class ChainClient {
    static class ChainHandlerA extends ChainBaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handler by chain a");
        }
    }

    static class ChainHandlerB extends ChainBaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handler by chain b");
        }
    }

    static class ChainHandlerC extends ChainBaseHandler{
        @Override
        protected void handlerProcess() {
            System.out.println("handler by chain c");
        }
    }

    public static void main(String[] args) {
        /**
         * 声明HandlerList关系链 通过数组顺序排序
         * 不再像之前那样手动给当前Handler中的NextHandler设置值
         * 同样每个chain中间没有相互的依赖 实现解耦
         */
        List<ChainBaseHandler> handlerList = Arrays.asList(
                new ChainHandlerA(),
                new ChainHandlerB(),
                new ChainHandlerC()
        );

        Chain chain = new Chain(handlerList);
        chain.proceed();

    }
}

责任链模式和观察者模式的区别

Java8新特性

函数式编程

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”

面向对象思想强调“必须通过对象的形式来做事情”

函数式思想强调则金量忽略面向对象的复杂语句:“强调做什么,而不是以什么形式去做”

而我们要学习的Lambda表达式就是函数式思想的体现

函数式接口详细文章

标签:订阅,Java,chain,void,观察者,class,设计模式,public
From: https://blog.csdn.net/asdfasaa/article/details/139584522

相关文章

  • C#.NET与JAVA互通之AES加密解密V2024
    C#.NET与JAVA互通之AES加密解密V2024 视频:   注意点:1.KEY和IV从字符串转byte数组时,双方要约定好编码,一般是UTF8。2.明文从字符串转byte数组时,双方要约定好编码,一般是UTF8,也可以GB2312,但不能Encoding.Default。3.加密后的结果,从byte数组转字符串时,双方要约定好编......
  • 第五章: 梳理Java 中各种运算符(Operator)的使用
    1.运算符的分类:运算符是一种特殊的符号,用以表示数据的运算,赋值和比较运算符按照功能可以分为:算术运算符比较(关系)运算符逻辑运算符赋值运算符位运算符三元运算符Lambda运算符(以后再说)分类运算符算术运算符(7个)+,-,*,/,%,++,–赋值运算符(12个)=,......
  • 第四章: 全面梳理Java 标识符变量的声明,基本数据类型,String类型以及相互之间的类型
    1.关键字和保留字关键字(keyword)是指被Java语言赋予了特殊含义,用做专门用途的字符串(单词)其特点就是关键字中所有字母都为小写官方地址:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html保留字(reservedword)是当前Java版本尚未使用,但以......
  • 23种设计模式之代理模式
    代理模式1、概念代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问代理模式是常用的结构型设计模式之一,在JavaRMI、WebService、SpringAOP等技术和框架中都使用了代理模式2、代理模式结构Subject(抽象主题角色):它声明了真实主题和代理主题的......
  • java发送get请求带参数
    在Java中,你可以使用java.net.HttpURLConnection或者org.apache.http.client.HttpClient来发送GET请求。以下是使用java.net.HttpURLConnection发送带参数的GET请求的示例代码:importjava.io.BufferedReader;importjava.io.InputStreamReader;importjava.net.HttpURLConnecti......
  • Java 垃圾回收
    在C和C++中,许多对象要求程序员声明他们后为其分配资源,然后才能安全地使用对象。使用完后,则需要程序员将这些资源释放到自由内存池。如果资源得不到释放,则认为代码泄露内存。然而,如果过早地释放,又可能发生数据丢失、Null指针等问题。Java和C#都有单独的管理应用程序管理对......
  • Java之数据库连接桥梁JDBC学习笔记
    JDBC调用Java与数据库的连接桥梁是JDBC(JavaDatabaseConnectivity)。JDBC是Java编程语言中用于连接和执行数据库操作的API(应用程序编程接口)。它提供了一种标准的方法,允许Java程序与各种数据库(如MySQL、PostgreSQL、Oracle、SQLServer等)进行交互。JDBC主要包括以下几个核......
  • web前端期末大作业 简单的学生网页作业源码 基于html css javascript学生宿舍管理系统
    ......
  • web课程设计网页规划与设计:鲜花网站设计——基于HTML+CSS+JavaScript制作网上鲜花网页
    ......
  • Web前端大作业制作个人网页(html+css+javascript)
    ......