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

相关推荐
wanghowie2 小时前
01.03 Java基础篇|面向对象核心与设计实践
java·开发语言
vortex52 小时前
ORM是什么?如何理解ORM?ORM的优缺点?
java·数据库·sql·mysql·oracle·orm
Algebraaaaa2 小时前
为什么线程阻塞要用.join而不是.wait
java·c++·python
是苏浙2 小时前
零基础入门Java之设计图书管理系统
java·开发语言
墨雪不会编程2 小时前
C++内存管理深度剖析
java·开发语言·c++
BBB努力学习程序设计2 小时前
Java Scanner完全指南:让程序与用户对话
java
BBB努力学习程序设计2 小时前
Java面向对象编程:封装、继承与多态深度解析
java
Lucky_Turtle2 小时前
【Springboot】解决PageHelper在实体转Vo下出现total数据问题
java·spring boot·后端
Mr.朱鹏2 小时前
大模型入门学习路径(Java开发者版)下
java·python·学习·微服务·langchain·大模型·llm