一个利用观察者模式和策略模式对代码进行解耦优化的例子

本文介绍如何使用Spring的ApplicationEvent和ApplicationListener实现推送平台与主业务的解耦,遵循开闭原则,支持策略模式下的多平台推送。

在我的博客阅读本文

#2019年12月30日更新,Spring自带的时间编程是同步的,如果你有异步的需求需要自己配置一下:

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster =
          new SimpleApplicationEventMulticaster();
         
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

参考

1. 前言

观察者模式(Observer Pattern):当一个对象被修改时,则会自动通知它的依赖对象。属于行为模式。(一对多模式适用)

策略模式(Strategy Pattern):一个类的行为或其算法可以在运行时更改。属于行为模式。

关于这两种设计模式详细的介绍,网上一搜一大把,不复赘述。

本文基于这两种设计模式,提供一个具体的业务场景作为实例,表述一种模块间解耦和优化的思路。

本文相关代码Github链接

2. 业务需求

在我们日常开发中,肯定会遇到需要做多平台分发推送的时候,你是做SMS的,那你肯定每天都在做消息质检的传递推送工作,你要是做电商的,你肯定得向用户推送物料情况诸如此类等等。

对于我们做MES系统的,管理层编派任务给工人,工人要能够实时收到推送。

推送这一块我们是使用的第三方平台,这一块我一开始直接写了个工具类,集成推送平台提供的SDK,在主业务里面直接引用对应SDK工具类就可以了。

后来老大给了我一些建议,说是要考虑将来平台的拓展,我目前的代码,将来如果要增加新的平台很可能就得在主业务逻辑里修改业务代码,不符合开闭原则,即使通过工具类进行解耦,将来有新的平台引入,修改推送工具类,本质上推送工具类在这里也不符合开闭原则,因此需要一个方案,能够将推送解耦,同时让系统更严格符合开闭原则

在这里,正如标题所说,利用设计模式,我们进行解耦。

3. 基于Spring提供的类的实现

3.1. 简介

springframework中提供了叫做ApplicationEventApplicationListener的类,这两个类用来实现bean与bean质检的通信机制,其实也就是spring提供的观察者模式实现。
ApplicationEvent有点类似被观察者,我们看下这个类的方法:

比较简单,我们着重关注一下这个类的构造方法,参数为source,文档中的解释大概意思是说这是一个event被初始化的地方或者关联的对象,不用纠结,其实就是在调用的地方用this

ApplicationListener 有点类似观察者,我们看下这个类的方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3aAcGqI-1571032169240)(/Users/sq.ma/Library/Application Support/typora-user-images/image-20191012171644504.png)]

这个类有且只有一个抽象方法,所以是一个函数式接口(functionInterface)。

下面我们用具体的代码来看一下这两个类怎么用。

3.2. 开始码代码

3.2.1. 创建一个枚举类PushPlatform
/**
 * 推送平台枚举
 *
 * @author sq.ma
 * @date 2019/10/12 下午5:50
 */
public enum PushPlatform {
    /**
     * 全平台
     */
    ALL_PLATFORM
}

这个枚举类用来标志推送到哪个平台(目前就一个,全平台)

3.2.2. 创建一个推送谓词接口(推送条件)PushPredicate
/**
 * 推送谓词(推送条件)
 * 
 * @author sq.ma
 * @date 2019/10/12 下午6:09
 */
@FunctionalInterface
public interface PushPredicate {

    /**
     * @return 推送平台
     */
    PushPlatform doPush();
}

这是一个函数式接口,后续可以通过lambda表达式简化代码。

后面推送的代码中,我们可以根据这个方法具体的回调实现不同的条件不同处理,也就是我们的策略模式

3.2.3. 创建一个ApplicationEvent的子类PushEvent
/**
 * 推送Event
 *
 * @author sq.ma
 * @date 2019/10/12 下午5:39
 */
@Getter
public class PushEvent extends ApplicationEvent {

    private PushPlatform platform;

    public PushEvent(Object source, PushPredicate predicate) {
        super(source);
        this.platform = predicate.doPush();
    }

}
3.2.4. 创建一个ApplicationListener的实现类PushListener
/**
 * 推送Listener
 *
 * @author sq.ma
 * @date 2019/10/12 下午6:14
 */
@Component
public class PushListener implements ApplicationListener<PushEvent> {

    @Override
    public void onApplicationEvent(PushEvent pushEvent) {
        PushPlatform platform = pushEvent.getPlatform();
        if (PushPlatform.ALL_PLATFORM.equals(platform)) {
            System.err.println("进行全平台推送");
        }
    }

}

这里如果想避免线程阻塞的话可以在方法或类上标志@Async注解

3.2.5. 主业务中使用
/**
 * 主业务
 *
 * @author sq.ma
 * @date 2019/10/14 上午11:27
 */
@Component
public class MainServiceA {

    /**
     * ApplicationContext be supported too
     * ApplicationEventPublisher recommended
     * because ApplicationEventPublisher is smaller range then ApplicationContext
     */
    @Resource
    ApplicationEventPublisher applicationEventPublisher;

    public void doMainService() {
        System.err.println("主业务执行中");
        applicationEventPublisher.publishEvent(new PushEvent(this, () -> PushPlatform.ALL_PLATFORM));
        System.err.println("主业务执行和完毕");
    }
}

借助于lambda表达式,我们可以更优雅的用一行解决问题。

这里要注意的是,ApplicationEventPublisher这个类也可以用他的子类ApplicationContext来代替,但是ApplicationContext比``ApplicationEventPublisher多更多功能,本着刚好服务于所需求功能,减少资源调度和增加安全的原则,更推荐用ApplicationEventPublisher`

3.2.6. 测试是否成功
@RunWith(SpringRunner.class)
@SpringBootTest
public class ObserverTrategyDemoApplicationTests {

    @Resource
    MainServiceA mainServiceA;

    @Test
    public void contextLoads() {
       	/**
         * Spring提供的方式
         */
        mainServiceA.doMainService();
        mainServiceA.doMainService();
    }

}

测试结果如下:

程序正确执行。

3.2.7 设计小结

利用Spring提供的ApplicationEventApplicationListener类我们实现了多种推送平台(例如极光推送,阿里推送等)和主业务的解耦,后续如果有新的平台引入,只需要写新的ApplicationListener的实现类就可以实现,而不需要改变原有的主业务逻辑代码和原有的推送平台代码,符合开闭原则(对修改关闭,对扩展开放),这一点基于我们的观察者模式。

通过PushPredicate接口,我们实现了不同目标推送平台(例如安卓,iOS等)条件不同的推送代码。其实这里就是将原有的if语句中的判断条件抽象出去,这样模块上更独立。这样的一个优势是后续如果有其他同事要使用这个模块只需要实现抽象方法,而对于本模块的维护者而言,他不需要关心主业务逻辑,只需要根据这个接口的结果进行不同目标推送平台处理就行,实现了与主业务之间的解耦。

PushPlatform这个枚举类主要是约束用户的数据格式,避免所谓的魔法值的出现。

这里其实还有个问题,调用推送的模块必须是交给Spring进行注入管理的bean,我们可以看到上面的主业务代码我们用了@Componet让交托给Spring进行创建管理,在测试类中我们使用的是Spring的依赖注入获得主业务bean。如果你尝试直接通过new一个实例的方式,你会得到一个空指针异常,原因是主业务中的this为null(没有上下文信息,姑且这么理解)。

4. 备选方案

  • 基于JDK自带的Observer相关类进行实现(JDK9+已过时)基于JDK自带的Observer相关类进行实现。需要说明的是,这个方法已经过时,jdk9以后更推荐用java.util.concurrent.Flow进行实现。

  • 基于JDK9以后的java.util.concurrent.Flow进行实现

<think>嗯,用户想了解基于FreeRTOS内核逻辑驱动的混合模式架构的嵌入式软件分层解耦。首先,我需要明确几个关键点:FreeRTOS的基本结构、逻辑驱动架构的特点,以及如何将它们结合实现分层解耦。 首先,FreeRTOS是一个实时操作系统,主要用于任务调度、内存管理中断处理等。分层解耦在嵌入式系统中很重要,因为它能提高代码的可维护性可扩展性。逻辑驱动架构通常指的是将业务逻辑与底层硬件分离,可能涉及状态机或事件驱动模型。 混合模式架构可能是指结合了时间触发事件触发的系统。需要将系统分为不同的层次,比如硬件抽象层、驱动层、操作系统层、逻辑控制层应用层。每层之间通过接口通信,减少耦合。 接下来,可能需要详细说明每一层的职责。例如,硬件抽象层封装硬件细节,驱动层提供统一接口,FreeRTOS管理任务资源,逻辑控制层处理业务逻辑,应用层实现具体功能。同时,要说明如何利用FreeRTOS的特性,如任务、队列、信号量等来实现层间通信解耦。 还要考虑数据流事件传递机制,比如使用消息队列传递事件,确保各层独立运作。此外,模块化设计是关键,每个模块有明确接口,便于测试替换。可能还需要提到设计模式,如观察者模式或发布-订阅模式,来支持事件驱动。 需要验证分层后的系统是否满足实时性要求,如何调试优化。可能存在的挑战包括层间通信的延迟、资源竞争,以及如何保持各层的独立性。最后,举一个实际例子,比如智能家居中的传感器数据采集处理,说明各层如何协作。 用户可能是嵌入式开发者,希望优化现有架构或学习最佳实践。深层需求可能是提高代码重用性,降低维护成本,适应多硬件平台。因此,回答要突出分层带来的好处,如模块化、可测试性、可扩展性,以及如何结合FreeRTOS实现这些目标。</think>以下是基于FreeRTOS内核逻辑驱动的混合模式架构的嵌入式软件分层解耦详细说明: --- ### 一、架构核心设计思想 1. **分层解耦原则** $$系统=硬件抽象层(HAL)+驱动框架层+RTOS内核层+逻辑控制层+应用层$$ 2. **混合模式特征** - 时间触发(RTOS任务调度) - 事件驱动(逻辑状态机触发) - 数据流驱动(消息队列传递) --- ### 二、分层架构实现方案 #### 1. 硬件抽象层(HAL) ```c /* 示例:GPIO统一接口 */ typedef struct { void (*Init)(void); void (*Write)(uint8_t state); uint8_t (*Read)(void); } GPIO_Driver_t; ``` - 封装芯片寄存器操作 - 提供统一硬件接口 - 实现硬件无关性 #### 2. 驱动框架层 ```c /* 示例:SPI设备驱动注册 */ void SPI_Device_Register(SPI_Dev_t *dev) { xQueueSend(spi_driver_queue, &dev, portMAX_DELAY); } ``` - 设备驱动管理 - 中断服务程序(ISR)封装 - 提供设备操作API #### 3. FreeRTOS内核层 $$任务优先级= f(实时性要求, 执行频率)$$ - 任务调度器配置 - 资源管理: - 堆内存分配策略 - 信号量/互斥量使用 - 软件定时器配置 #### 4. 逻辑控制层 ```mermaid stateDiagram-v2 [*] --> Idle Idle --> DataProcessing: 收到传感器数据 DataProcessing --> DecisionMaking: 数据校验通过 DecisionMaking --> ActionExec: 执行控制命令 ``` - 状态机实现业务逻辑 - 事件处理器设计 - 数据预处理模块 #### 5. 应用层 ```c void AppTask(void *pvParameters) { while(1) { Event_t event; if(xQueueReceive(app_event_queue, &event, pdMS_TO_TICKS(100))) { HandleAppEvent(event); } } } ``` - 具体业务实现 - 人机交互接口 - 系统监控功能 --- ### 三、关键解耦技术 1. **接口抽象化** - 定义`.h`头文件规范 - 使用函数指针表实现动态绑定 2. **消息传递机制** ```c typedef struct { uint8_t msg_type; void *payload; size_t payload_size; } SystemMessage_t; ``` - 跨层通信使用消息队列 - 数据传递采用零拷贝技术 3. **资源隔离策略** - 为每个任务分配独立栈空间 - 使用内存保护单元(MPU) - 关键资源互斥访问设计 --- ### 四、实时性保障措施 1. **任务优先级矩阵** | 任务类型 | 优先级范围 | 响应时间要求 | |----------------|------------|--------------| | 紧急中断处理 | 31-24 | <1μs | | 实时控制任务 | 23-16 | <1ms | | 普通业务任务 | 15-8 | <10ms | | 后台维护任务 | 7-0 | <100ms | 2. **中断嵌套控制** ```c void vApplicationIRQHandler(uint32_t ulICCIAR) { portDISABLE_INTERRUPTS(); /* 中断处理 */ portENABLE_INTERRUPTS(); } ``` --- ### 五、典型数据流 1. **传感器数据采集路径** ``` 传感器 → HAL层 → 驱动层 → DMA缓冲区 → 消息队列 → 逻辑控制层 → 应用层 ``` 2. **控制指令下发路径** ``` 应用层 → 逻辑决策 → RTOS任务 → 驱动接口 → HAL层 → 执行器 ``` --- ### 六、调试与优化 1. **Tracealyzer监控** ```python # 示例:任务执行时间分析 trace.filter_events("TASK_SWITCHED_IN") trace.statistics_duration() ``` 2. **内存使用优化** - 使用`heap_4.c`内存管理方案 - 定期检查任务栈水位 --- ### 七、应用场景示例 **智能家居控制系统:** 1. WiFi模块通过AT指令驱动层通信 2. 环境传感器数据通过HAL层采集 3. 业务逻辑层实现自动化场景 4. FreeRTOS确保多任务实时响应 --- 这种架构通过严格的分层设计混合驱动模式,在保证实时性的同时实现了高度模块化,特别适合需要长期维护迭代的复杂嵌入式系统。实际项目中可根据具体需求调整各层实现细节。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值