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