【Java】重构之善用多态解耦,记录一次模板方法实践

这里写目录标题

  • 项目场景:
  • 重构方案
    • [1. 公司枚举类](#1. 公司枚举类)
    • [2. 推送处理器抽象类(定义模板)](#2. 推送处理器抽象类(定义模板))
    • [3. 默认实现与特殊实现](#3. 默认实现与特殊实现)
    • [4. 工厂类创建实例](#4. 工厂类创建实例)
    • [5. 主程序](#5. 主程序)
    • [6. 执行结果](#6. 执行结果)
  • 总结:

项目场景:

前段时间维护了一个报警推送的桌面程序,支持几家供热公司的实时报警消息推送。每次启动时,通过配置指定处理其中某一家公司的报警推送。但是后面有一家客户的需求,是要实现实时报警次数统计的功能,于是引入Redis作为计数器。

最初代码采用条件分支实现,在主推送逻辑区域插入if判断,主流程被各种业务细节污染,违反了软件设计的基本开闭原则。现在趁着时间充裕,回过头来重构自己的代码。由于原有项目并不是一个Spring工程,思来想去,我决定用OOP中的多态来重构它,主要使用到了模板模式和工厂模式。

本文章作为一次代码重构的记录,希望能帮助到有相同困扰的小伙伴。


重构方案

前面发现主流程被业务细节所污染了,所以重构的核心是将实时报警次数统计这一行为差异,封装到该公司独有的实现类中,其他公司采用通用的实现类。这样一来就能保证在主流程只调用统一方法。

下面贴上精简版本的代码实现。

1. 公司枚举类

java 复制代码
package org.araby.model;

public enum Company {
    COMPANY_A,COMPANY_B,COMPANY_C,COMPANY_D
}

2. 推送处理器抽象类(定义模板)

AlarmPushHandler是一个抽象类,通过抽象类实现了一个模板,定义了一个统一的push方法,保证每个实现类都默认带推送的功能,支持重写。

并且模仿AOP的思想,在核心方法中预留三个钩子方法(beforePush,withPush,afterPush),让各自的推送实现类插入自定义的逻辑,这是抽象模板类的允许功能扩展的关键。

java 复制代码
package org.araby.handler;

import org.araby.model.Company;
import java.util.ArrayList;
import java.util.List;

public abstract class AlarmPushHandler {
    protected final Company company;

    public AlarmPushHandler(Company company) {
        this.company = company;
    }

    // 统一入口,保证所有子类支持推送行为,支持重写
    public void push(){
        List<String> alarmMessages = getAlarmMessages();
        List<String> recipients = getRecipients();
        for (String alarmMessage : alarmMessages) {
            for (String recipient : recipients) {
                sendAlarmMessage(alarmMessage, recipient);
                withPush(alarmMessage, recipient);
            }
        }
    }

    protected void sendAlarmMessage(String alarmMessage, String recipient){
        System.out.println("推送消息:" + alarmMessage + ",给:" + recipient);
    }

    /**
     * 推送消息前处理逻辑
     * @param alarmMessage
     * @param recipient
     */
    protected void beforePush(String alarmMessage, String recipient){
    }

    /**
     * 推送消息中处理逻辑
     * @param alarmMessage
     * @param recipient
     */
    protected void withPush(String alarmMessage, String recipient){
    }

    /**
     * 推送消息后处理逻辑
     * @param alarmMessage
     * @param recipient
     */
    protected void afterPush(String alarmMessage, String recipient){
    }



    private List<String> getAlarmMessages(){
        List<String> alarmMessages = new ArrayList<>();
        alarmMessages.add("xx换热站二次压力过低");
        alarmMessages.add("xx换热站阀门开度过大");
        return alarmMessages;
    }

    private List<String> getRecipients(){
        List<String> recipients = new ArrayList<>();
        recipients.add("user1");
        recipients.add("user2");
        return recipients;
    }
}

3. 默认实现与特殊实现

默认报警推送处理器的实现,通过super关键字调用父类抽象类模板方法的构造器。

java 复制代码
package org.araby.impl.common;

import org.araby.handler.AlarmPushHandler;
import org.araby.model.Company;

public class DefaultAlarmPushHandler extends  AlarmPushHandler {
    public DefaultAlarmPushHandler(Company company) {
        super(company);
    }
}

特殊实现,引入计数器功能,通过重写我们预定义的withPush钩子函数,插入自定义逻辑incrementAlarmCounts。

java 复制代码
package org.araby.impl.companyA;

import org.araby.impl.common.DefaultAlarmPushHandler;
import org.araby.model.Company;

public class CompanyAPushHandler extends DefaultAlarmPushHandler {
    public CompanyAPushHandler(Company company) {
        super(company);
    }


    @Override
    protected void withPush(String alarmMessage, String recipient){
        incrementAlarmCounts(recipient);
    }

    private void incrementAlarmCounts(String recipient){
        System.out.println(recipient + "推送次数+1");
    }
}

4. 工厂类创建实例

定义一个工厂类,根据公司枚举类型,初始化AlarmPushHandler的指定实现类。

java 复制代码
package org.araby.factory;

import org.araby.handler.AlarmPushHandler;
import org.araby.impl.common.DefaultAlarmPushHandler;
import org.araby.impl.companyA.CompanyAPushHandler;
import org.araby.model.Company;

public class PushHandlerFactory {
    public static AlarmPushHandler create(Company company){
        switch (company){
            case COMPANY_A:
                return new CompanyAPushHandler(company);
            case COMPANY_B:
            case COMPANY_C:
            case COMPANY_D:
                return new DefaultAlarmPushHandler(company);
            default:
                return null;
        }
    }
}

5. 主程序

在主程序中,我们并不关心具体实现类的逻辑处理。每一次调用AlarmPushHandler,运行时动态绑定到不同实现。

java 复制代码
package org.araby;

import org.araby.factory.PushHandlerFactory;
import org.araby.handler.AlarmPushHandler;
import org.araby.model.Company;

public class AlarmService {
    public static void main(String[] args) {
        Company target = Company.COMPANY_A; // 从配置读取公司
        // 创建推送处理器
        AlarmPushHandler alarmPushHandler = PushHandlerFactory.create(target);
        // 推送
        alarmPushHandler.push();
    }
}

6. 执行结果

公司A特殊处理

bash 复制代码
推送消息:xx换热站二次压力过低,给:user1
user1推送次数+1
推送消息:xx换热站二次压力过低,给:user2
user2推送次数+1
推送消息:xx换热站阀门开度过大,给:user1
user1推送次数+1
推送消息:xx换热站阀门开度过大,给:user2
user2推送次数+1

通用逻辑

bash 复制代码
推送消息:xx换热站二次压力过低,给:user1
推送消息:xx换热站二次压力过低,给:user2
推送消息:xx换热站阀门开度过大,给:user1
推送消息:xx换热站阀门开度过大,给:user2

总结:

本次重写实际上把OOP中的封装继承和多态都使用上了。

  • 封装:每家公司的差异化逻辑被严格封装在各自子类内部。外部不需要关心细节,只需要能通过 push() 统一触发。
  • 继承:子类通过继承抽象类获得通用能力,并且同时可选择性地扩展或覆盖行为
  • 多态:子类继承父类这种关系,赋予我们实现类与抽象类解耦,所以对于主类的alarmPushHandler.push()方法,编译期只知道其类型是AlarmPushHandler,运行期自动调用对应子类逻辑。

除了OOP的思想,我们还运用了模板模式和工厂模式,前者创建了一个可扩展的模板定义了公共的行为契约,后者解耦创建实现类的逻辑。这也是软件设计中的开闭原则体现,对扩展开放,对修改关闭。比如以后如果某家公司也有新增的特殊逻辑,完全可以创建一个独有的实现类。然后在工厂方法中创建对应的实现类,系统将在不破坏现有功能的前提下获得新能力。

相关推荐
元Y亨H5 小时前
Nacos - 服务发现
java·微服务
微露清风5 小时前
系统性学习C++-第十八讲-封装红黑树实现myset与mymap
java·c++·学习
dasi02275 小时前
Java趣闻
java
阿波罗尼亚6 小时前
Tcp SSE Utils
android·java·tcp/ip
susu10830189116 小时前
springboot3.5.8整合minio8.5.9
java·springboot
不知道累,只知道类6 小时前
深入理解 Java 虚拟线程 (Project Loom)
java·开发语言
myzshare6 小时前
实战分享:我是如何用SSM框架开发出一个完整项目的
java·mysql·spring cloud·微信小程序
Chan166 小时前
【 Java八股文面试 | JavaSE篇 】
java·jvm·spring boot·面试·java-ee·八股
wen__xvn7 小时前
代码随想录算法训练营DAY10第五章 栈与队列part01
java·前端·算法
独自破碎E7 小时前
解释一下NIO、BIO、AIO
java·开发语言·nio