gpt4 book ai didi

代码精简之路-模板模式

转载 作者:撒哈拉 更新时间:2025-01-09 00:38:57 57 4
gpt4 key购买 nike

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;
    }
}
View Code

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;
}
View Code

格式检查代码封装,实现多种格式的时间检查:

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;
}
View Code

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);
}
View Code

改进订单处理代码,只需填充基类模板空出来的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);
    }
}
View Code

改进运单处理代码,只需填充基类模板空出来的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);
    }
}
View Code

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();
}
View Code

升级后的订单处理类,只填充接口地址方法(模板):

@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";
    }
}
View Code

7. 适合哪些场景 。

模板模式适用的三个特点:

a. 业务流程相似。如案例中校验、初始化、保存三个步骤在每个单证中都有.

b. 业务实现时局部有差异。如案例中订单、运单、支付单各自要检查的字段不同,状态初始值不同,保存数据用的dao实例不同.

c. 业务类型多。如果案例中只有一个订单或运单功能,不需要有抽象基类(继承就是为了代码复用,业务流程只有一个单证时没有区别),可以将流程和实现在业务类中一同实现.

8. 怎么理解模板模式 。

a. 两个关键点是抽象和分层.

b. 总结相同或相似的功能并泛化,用一个更大范围的词语来描述就是抽象.

c. 分层就是将相同或相似的功能放到抽象层,将有差异的部分放到实现层.

d. 比如上班族每天的生活都可以抽象为起床洗漱、早餐、上午工作、午餐、下午工作、回家、晚餐这些步骤,这些泛化的步骤就放抽象层。不同的部分在于不同职业、不同城市的上班族起床洗漱时间地点不同,早餐菜品不同,工作内容不同;这些具体的内容实现代码各不相同,就放到实现层.

最后此篇关于代码精简之路-模板模式的文章就讲到这里了,如果你想了解更多关于代码精简之路-模板模式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

57 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com