- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
1. 前言 。
程序员怕重复CRUD,总是做一些简单繁琐的事情。“不要重复造轮子”,“把基础功能提炼出来封装成工具类” 我喜欢把这些话挂在嘴边,写起来常不知从何下手。 下面拆解一个项目中的功能。记录从复制粘贴到对业务抽象、实现功能分层的详细过程。如何着手提升代码重构优化能力,拿到项目需求用自己的思维实现一遍,再到维护发现其中的不足,再模仿优化。自己踩坑发现问题再自己解决是最有效的方式.
。
2. 需求 。
XX申报系统,接受用户申报数据,系统对申报数据做格式检查,再对单证中的一些字段(如状态、单证号、创建时间等)赋初始值,再保存入库。单证类型有:订单、运单、支付单、清单、申报单.
3. 原始代码的不足 。
a. 流水代码,比如数据格式检查中大量用到if else的判断.
b. 时间等格式检查代码在不同单证中重复出现。(因为用户上传的excel申报数据中时间格式多样,甚至有中文年月字样,时间字段才用的字符串类型。) 。
c. 结构混乱,数据校验、赋初始值、保存等功能交叉在一起.
原订单处理代码:
@Service @RequiredArgsConstructor public class OrderHandler { private final CurrencyService currencyService; private final EbcService ebcService; private final OrderDao orderDao; public Result start(Order order) { Result result = new Result(); result.setSuccess(true); if (Strings.isNullOrEmpty(order.getAgentName())) { result.setSuccess(false); result.getErrors().add("代理人为空"); } if (Strings.isNullOrEmpty(order.getCurrency())) { result.setSuccess(false); result.getErrors().add("币制编码为空"); } else { // 赋初始值混在数据检查中 order.setCurrencyName(currencyService.getName(order.getCurrency())); } if (Strings.isNullOrEmpty(order.getEbcCode())) { result.setSuccess(false); result.getErrors().add("电商为空"); } else { // 赋初始值混在数据检查中 order.setEbcName(ebcService.getName(order.getEbcCode())); } if (Strings.isNullOrEmpty(order.getConsignee())) { result.setSuccess(false); result.getErrors().add("收货人为空"); } if (Strings.isNullOrEmpty(order.getConsigneeTelephone())) { result.setSuccess(false); result.getErrors().add("收货人电话为空"); } if (Strings.isNullOrEmpty(order.getOrderDate())) { result.setSuccess(false); result.getErrors().add("订单时间为空"); } else { // 检查时间格式 boolean timeValid = false; try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalTime.parse(order.getOrderDate(), formatter); timeValid = true; } catch (DateTimeParseException e) { e.printStackTrace(); } // 多种时间格式 if (!timeValid) { try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"); LocalTime.parse(order.getOrderDate(), formatter); timeValid = true; } catch (DateTimeParseException e) { e.printStackTrace(); } } if (!timeValid) { result.setSuccess(false); result.getErrors().add("订单时间格式错误"); } } // todo: 其它格式检查代码 if (result.isSuccess()) { order.setStatus(OrderStatus.APPLY.getCode()); order.setCreateTime(LocalDateTime.now()); orderDao.save(order); } return result; } }
4. 改进一一初步封装 。
a. 实现对数据检查的功能封装,将基础功能与业务解耦。这里的基础功能是指格式检查,业务是指对不单证中的字段值赋初始值和保存。解耦的好处:有利于将来在别的项目或功能模块中复用基础功能。同时本系统中业务功能调整也不需要改动基础功能部分的代码.
改进后的订单处理代码:
public Result start(Order order) { Result result = new Result(); result.setSuccess(true); // todo:其它格式检查 // 封装时间格式检查功能 boolean timeValid = validator.checkTime(order.getOrderDate()); if (!timeValid) { result.setSuccess(false); result.getErrors().add("订单时间格式错误"); } // todo:赋初始值 // todo:保存 return result; }
格式检查代码封装,实现多种格式的时间检查:
public boolean checkTime(String time) { List<String> formatters = new ArrayList<>(); formatters.add("yyyy-MM-dd HH:mm:ss"); formatters.add("yyyy年MM月dd日 HH时mm分ss秒"); formatters.add("yyyyMMddHHmmss"); formatters.add("yyyy/MM/dd HH:mm:ss"); boolean pass = formatters.stream().anyMatch(format -> { try { LocalTime parse = LocalTime.parse(time, DateTimeFormatter.ofPattern(format)); return true; } catch (DateTimeParseException e) { return false; } }); return pass; }
5. 改进一一模板模式 。
a. 实现业务抽象,建立对检验、初始化、保存的标准流程.
b. 实现代码分层,抽象基类负责定义标准流程,实现类负责各业务功能具体实现。上层(抽象基类)负责制定标准,下层负责执行标准.
基类代码:
/** * 抽象基类,定义处理流程 * * @param <T> */ public abstract class BaseHandler<T> { /** * 接收申报处理入口:校验、赋初值、保存都在这里实现了,下层类不需要写流程处理的重复代码 * * @param doc * @return */ public Result start(T doc) { if (doc == null) { return new Result(false); } // step1 Result result = check(doc); // 如果数据校验不通过直接返回 if (!result.isSuccess()) { return result; } try { // step2,3 init(doc); save(doc); result.setSuccess(true); } catch (Exception e) { e.printStackTrace(); result.setSuccess(false); result.getErrors().add(e.getMessage()); } return result; } /** * 数据校验方法,业务类分别实现 * * @param doc * @return */ protected abstract Result check(T doc); /** * 数据初始化 * * @param doc */ protected abstract void init(T doc); /** * 保存 * * @param doc */ protected abstract void save(T doc); }
改进订单处理代码,只需填充基类模板空出来的3个方法:
@Component @RequiredArgsConstructor public class OrderHandlerV3 extends BaseHandler<Order> { private final CurrencyService currencyService; private final EbcService ebcService; private final OrderDao orderDao; private final Validator validator; @Override protected Result check(Order doc) { Result result = new Result(); result.setSuccess(true); // 时间检查 boolean timeValid = validator.checkTime(doc.getOrderDate()); if (!timeValid) { result.setSuccess(false); result.getErrors().add("订单时间格式错误"); } if (StringUtils.isBlank(doc.getAgentName())) { result.setSuccess(false); result.getErrors().add("代理人为空"); } // todo:其它格式检查 ... return result; } @Override protected void init(Order doc) { doc.setCurrencyName(currencyService.getName(doc.getCurrency())); doc.setEbcName(ebcService.getName(doc.getEbcCode())); doc.setStatus(OrderStatus.APPLY.getCode()); doc.setCreateTime(LocalDateTime.now()); // todo:其它字段初始化 ... } @Override protected void save(Order doc) { orderDao.save(doc); } }
改进运单处理代码,只需填充基类模板空出来的3个方法:
@Component @RequiredArgsConstructor public class WaybillHandlerV3 extends BaseHandler<Waybill> { private final CountryService countryService; private final CurrencyService currencyService; private final WaybillDao waybillDao; private final Validator validator; @Override protected Result check(Waybill doc) { Result result = new Result(); result.setSuccess(true); // 检查发货时间 boolean timeValid = validator.checkTime(doc.getDeliveryDate()); if (!timeValid) { result.setSuccess(false); result.getErrors().add("发货时间格式错误"); } // todo:其它格式检查 ... return result; } @Override protected void init(Waybill doc) { doc.setCurrencyName(currencyService.getName(doc.getCurrency())); doc.setConsigneeCountryName(countryService.getName(doc.getConsigneeCountry())); doc.setStatus(WaybillStatus.APPLY.getCode()); doc.setCreateTime(LocalDateTime.now()); // todo:其它字段初始化 ... } @Override protected void save(Waybill doc) { waybillDao.save(doc); } }
6. 优劣对比 。
a. 有利于阅读代码、维护功能.
原始代码中3个步骤(校验、赋初始值、保存)的功能在混合交叉在一起,在一个方法中实现,阅读维护非常耗时。将来如果需求变动,如字段长度变化/必填字段变化要修改数据检查部分代码;状态字段值变化(申报由1表示改为由A表示)而修改赋初始值部分代码;ORM框架变化修改dao的实例。这时就只能到这一个方法中寻找对应部分,要从头到尾阅读代码.
b. 有利于功能升级。现在的功能只有3步,假如将来功能拓展,如对接别的系统平台(把合规的数据转为json格式推送给目标系统的接口).
c. 功能升级举例,流程处理中增加推送功能:
升级后的基类,只增加了4行代码:
public abstract class BaseHandler<T> { @Autowired private ApiClient apiClient; /** * 接收申报处理入口:校验、赋初值、保存都在这里实现了,下层类不需要写流程处理的重复代码 * * @param doc * @return */ public Result start(T doc) { if (doc == null) { return new Result(false); } // step1 Result result = check(doc); // 如果数据校验不通过直接返回 if (!result.isSuccess()) { return result; } try { // step2,3 init(doc); save(doc); result.setSuccess(true); } catch (Exception e) { e.printStackTrace(); result.setSuccess(false); result.getErrors().add(e.getMessage()); } // 升级功能,申报成功以后推送数据到别的平台 String json = JSONObject.toJSONString(doc); boolean send = apiClient.send(json, getApi()); if (!send) { // todo:记录推送失败日志 } // todo: 记录推送记录等 return result; } /** * 数据校验方法,业务类分别实现 * * @param doc * @return */ protected abstract Result check(T doc); /** * 数据初始化 * * @param doc */ protected abstract void init(T doc); /** * 保存 * * @param doc */ protected abstract void save(T doc); /** * 新增功能,获取推送接口 * * @return */ protected abstract String getApi(); }
升级后的订单处理类,只填充接口地址方法(模板):
@Component @RequiredArgsConstructor public class OrderHandlerV3 extends BaseHandler<Order> { private final CurrencyService currencyService; private final EbcService ebcService; private final OrderDao orderDao; private final Validator validator; @Override protected Result check(Order doc) { Result result = new Result(); result.setSuccess(true); // 时间检查 boolean timeValid = validator.checkTime(doc.getOrderDate()); if (!timeValid) { result.setSuccess(false); result.getErrors().add("订单时间格式错误"); } if (StringUtils.isBlank(doc.getAgentName())) { result.setSuccess(false); result.getErrors().add("代理人为空"); } // todo:其它格式检查 ... return result; } @Override protected void init(Order doc) { doc.setCurrencyName(currencyService.getName(doc.getCurrency())); doc.setEbcName(ebcService.getName(doc.getEbcCode())); doc.setStatus(OrderStatus.APPLY.getCode()); doc.setCreateTime(LocalDateTime.now()); // todo:其它字段初始化 ... } @Override protected void save(Order doc) { orderDao.save(doc); } /** * 设置推送接口 * * @return */ @Override protected String getApi() { return "http://host:port/api/order"; } }
7. 适合哪些场景 。
模板模式适用的三个特点:
a. 业务流程相似。如案例中校验、初始化、保存三个步骤在每个单证中都有.
b. 业务实现时局部有差异。如案例中订单、运单、支付单各自要检查的字段不同,状态初始值不同,保存数据用的dao实例不同.
c. 业务类型多。如果案例中只有一个订单或运单功能,不需要有抽象基类(继承就是为了代码复用,业务流程只有一个单证时没有区别),可以将流程和实现在业务类中一同实现.
8. 怎么理解模板模式 。
a. 两个关键点是抽象和分层.
b. 总结相同或相似的功能并泛化,用一个更大范围的词语来描述就是抽象.
c. 分层就是将相同或相似的功能放到抽象层,将有差异的部分放到实现层.
d. 比如上班族每天的生活都可以抽象为起床洗漱、早餐、上午工作、午餐、下午工作、回家、晚餐这些步骤,这些泛化的步骤就放抽象层。不同的部分在于不同职业、不同城市的上班族起床洗漱时间地点不同,早餐菜品不同,工作内容不同;这些具体的内容实现代码各不相同,就放到实现层.
最后此篇关于代码精简之路-模板模式的文章就讲到这里了,如果你想了解更多关于代码精简之路-模板模式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
粗略地说,单向数据绑定(bind)只是与 ng-model 绑定(bind)。当涉及 Controller 时,在页面内和 2-way 内。有人可以向我解释这个概念,以便我真正了解如何看待它吗?还有什
我想知道是否有任何替代 2 向 SSL 的方法。 2 向 SSL 是确保客户端和服务器可信通信的唯一选择吗?我有一个自签名证书供我的客户使用,我能否将自签名证书重新用于 2 种 SSL 方式,还是应该
如果是这样,你如何设置认证证书,你需要什么文件?是 .pfx 吗?您将如何在浏览器中安装它?一直试图通过浏览器测试 2 路 ssl。我有一个网络服务,尝试连接时总是返回认证身份验证失败。 最佳答案 扩
我希望能够对 XHTML 文档进行三向合并: 从文档的一些原始副本开始 一个用户编辑原始文档的副本 另一个用户编辑原始文档的单独副本 需要一个工具来合并(自动和/或可视化)两个用户所做的更改。 注意:
我有 4 张 table : ad (id, ...) website (id, title, URL, ...) space (id, website_id, ...) ad_space_count
我在 java 中有一个无状态服务,部署在 tomcat 网络服务器中,我还配置了 2 路 ssl 验证。到目前为止,一切正常。当我有一个新客户端时,我只需要将新客户端证书放入我的 trustore
我已经创建了一个带有证书的信任库和带有私钥的 keystore 。我已经放置了以下代码,加载了 trsustore 管理器和 keystore 管理器,然后创建了 SSL 上下文的实例。 每当我向网络
如果我在仅服务器身份验证中正确理解 SSL/TLS,握手后,服务器会向客户端发送它的公钥和由 CA 签名的数字签名证书。如果客户端有这个 CA 的公钥,它就可以解密证书并与服务器建立信任。如果它不信任
我有 Nginx,它使用双向 TLS 代理从客户端到 IBM DataPower 的请求。 从 Nginx 向 IBM DP 发送消息时出现错误:sll server (SERVER) ssl pee
我刚刚开始了一个项目,让我的雇主成为一个管理软件。我有一个琐碎但可能很简单的查询,我似乎找不到任何相关信息。 在对象之间建立“具有”关系的两种方式是否谨慎/良好做法。例如,Client 对象“有一个”
我在设置双向 SSL 身份验证时遇到问题。 我需要从 wso2 企业集成商访问 HTTPS 端点。 服务提供商给了我一个 pfx keystore ,其中包含我必须提供给服务器的证书和私钥。 我在我的
我正在为小型 PoC 构建 AWS Lambda 服务。 PoC 中的流程是: 通过 POST 获取(文本)输入, 执行小字符串操作 + 将操纵值存储到 DynamoDB 中,然后 通过 HTTP P
我的任务是在 Java 上下文中实现双向 TLS。我找到了一个示例 ( https://www.opencodez.com/java/implement-2-way-authentication-us
我正在尝试测试一个非常简单的双向 IM 应用程序。客户端在 android 上,服务器在我的 PC(java)上。我已经在 PC 到 PC 之间用 java 测试了这个应用程序,它工作正常。 但是在我
我有 java web 服务支持2-way ssl auth。所以我有客户端 keystore (client.p12),服务器证书在受信任的存储区中,服务器 keystore 中的客户端证书在受信任
通过 HTTPS 使用 Web 服务 我们有一个我们正在使用的网络服务。 Webservice 可以在 HTTP 和 HTTPS 协议(protocol)上运行。使用 HTTP 没问题,但如何使用 H
我在 Node.js 上有一个后端服务器,我正在尝试在 Nginx 和这个后端服务器之间设置 2 路 SSL。 但是我得到一个错误:2015/11/02 06:51:02 [错误] 12840#128
我一直在尝试连接到启用了 2 路 SSL 的服务端点。我正在使用 Spring resttemplate。我已将证书添加到 keystore 中,但出现以下错误: >org.springframewo
从 CherryPy 3.0 开始,只需指向服务器证书和私钥即可启用单向 SSL,如下所示: import cherrypy class HelloWorld(object): def ind
这个问题来自:MySQL Number of Days inside a DateRange, inside a month (Booking Table) 我有一个包含以下数据的表: CREATE
我是一名优秀的程序员,十分优秀!