模板方法设计模式侧重于代码复用,只需要在父类中定义一套处理流程,将其中的不变部分封装起来,可变的部分交由子类去实现即可。可提升代码的可读性、可维护性、灵活性。
1、背景:
公司的主营业务是港口无人驾驶,我的工作内容是云端部分,工作内容之一就是对接港口的自动化设备厂商,完成自动化作业。
1.1、名词解释:
岸桥:岸边桥式起重机,将集装箱从船上吊起放在无人车上或者反着来
场桥:堆场桥式起重机,将集装箱从无人车上吊起放在堆场或者反着来
码头面:岸桥作业场地
堆场:港口存放集装箱的场地
吊具:岸桥、场桥设备用来吊集装箱的部分设施
1.2、流程:
无人车接到指令去码头面做卸船装箱任务,到指令中指定的岸桥下作业,与岸桥上安装的自动化作业设备进行交互,等待吊具将集装箱放在无人车上。无人车上装好箱子后接到去堆场做卸船卸箱任务,到指令中指定的场桥下作业,与场桥上安装的自动化作业设备进行交互,等待吊具将集装箱从无人车上吊走。
无人车与岸桥场桥的自动化交互流程是一样的,只是各自执行对应流程的条件以及时机不一样,所以此时适合固定一套作业流程的模板,主流程固定,至于岸桥场桥各自触发交互的条件按实际情况去实现。为此定义一个抽象类名为AbstractEcsDataHandler;岸桥流程处理子类定义为ECSQCDataHandler,继承该抽象类;场桥流程处理子类定义为ECSRMGDataHandler,继承该抽象类。
2、代码:
抽象类:AbstractEcsDataHandler:
java
public abstract class AbstractEcsDataHandler {
public void handle(ECSStatusDTO ecsStatusDTO) {
Optional.of(mappingVehicleAndEquipment())// 获取无人车与岸桥场桥设备的绑定关系,一个岸桥或者场桥可能对应多个无人车,key是vehicleId,代表车号,value是equipmentId代表岸桥场桥设备编号
.map(Map::entrySet)
.orElseGet(Collections::emptySet)
.stream()
.filter(entry -> StringUtils.equals(ecsStatusDTO.getCraneCode(), entry.getValue()))
.map(Map.Entry::getKey)
.map(vehicleId -> redisManager.get(RedisKeyConstants.SN_API_TASK_CACHE_EXEC_TASK + vehicleId))// 获取无人车任务指令字符串
.map(cachedExecTaskStr -> JsonUtil.parseObject(cachedExecTaskStr, TaskDto.class))// 反序列化出任务dto
.filter(Objects::nonNull)
.filter(this::isCorrectTaskType) // 任务中有是岸桥任务还是场桥任务属性,交由两个子类去实现
.filter(cachedExecTask -> isCorrectWorkPosition(ecsStatusDTO, cachedExecTask))// 任务中有作业目的地,岸桥场桥对目的地的定义不一致,交由两个子类去实现
.filter(cachedExecTask -> {
// 判断是否收到自动化设备的解锁指令,交由两个子类实现
if (isNotUnLockCommand(ecsStatusDTO)) {
return true;
}
// 省略收到自动化设备解锁方法
log.info("xxxxxxxxx");
return false;
})
.filter(cachedExecTask -> {
if(xxx) {
// 省略此部分方法
return true;
}
log.info("xxxxxxxxx");
return false;
})
.findFirst()
.filter(cachedExecTask -> isNotLocked(cachedExecTask.getTruckId()))
.filter(cachedExecTask -> {
// 判断是否收到自动化设备的锁车指令,交由两个子类实现
if (isNotLockCommand(ecsStatusDTO)) {
return true;
}
// 省略收到自动化设备的锁车处理
return false;
})
.filter(cachedExecTask -> {
// 判断是否对位完成,交由两个子类实现
if (isNotAlignFinish(ecsStatusDTO)) {
return true;
}
// 省略对位完成处理
return false;
})
.filter(cachedExecTask -> {
// 判断无人车是否需要对位,交由两个子类实现
if (vehicleNeedAlign(ecsStatusDTO)) {
return true;
}
// 省略不需要对位的处理
return false;
})
.ifPresent(cachedExecTask -> {
// 获取自动化设备下发的对位值,交由两个子类实现
int currentDistance = acquireAlignDistance(ecsStatusDTO);
// 省略对位值的处理
});
}
/**
* 车辆和要作业的ECS设备的映射关系 key: vehicleId value: qcId or rmgId
*/
protected abstract Map<String, String> mappingVehicleAndEquipment();
protected abstract boolean isCorrectTaskType(TaskDto task);
protected abstract boolean isCorrectWorkPosition(ECSStatusDTO ecsStatusDTO, TaskDto task);
protected abstract boolean isNotUnLockCommand(ECSStatusDTO ecsStatusDTO);
protected abstract boolean isNotLockCommand(ECSStatusDTO ecsStatusDTO);
protected abstract boolean isNotAlignFinish(ECSStatusDTO ecsStatusDTO);
protected abstract boolean vehicleNeedAlign(ECSStatusDTO ecsStatusDTO);
protected abstract int acquireAlignDistance(ECSStatusDTO ecsStatusDTO);
}
两个子类ECSQCDataHandler和ECSRMGDataHandler特别干净,只需要实现父类中的抽象方法即可,调用时直接使用handle()方法处理。
3、总结:
模板方法设计模式在一些固定业务的场景中特别适合,极大的省去了重复代码的编写成本以及维护成本,符合OCP原则,当一个子类中的方法有修改操作,不会影响其它子类对应的场景。在可读性方面,模板方法模式具备天然的易于理解的优势。之后遇到可能出现大量重复代码的时候,考虑模板方法设计模式。