TechBits | 工作中代码如何抽象

2023年 10月 11日 43.5k 0

前言:

不知道,大家编写代码时,是否有使用抽象思维,它是一项非常有趣的技能。有时候,我觉得它离我好远,但也有时候觉得离我好近。最近,我开始尝试对某个功能进行抽象,结果让我感到惊喜。一旦完成抽象,代码将变得更整洁,更有力量。这就像是给代码增添了一些魔力,让它焕发生机。

那么长话短说,我们开始吧我们的简单抽象之旅吧!

要求:

最近,你接到了一个任务,需要开发一个数据导出功能,具体说就是将数据导出到Excel文件。目前,有四个相似的数据集需要从数据库中提取,并写入到Excel文件中,这包括物理机相关的数据、云主机相关的数据、磁盘相关的数据以及交换机相关的数据。此任务未来可能需要进行扩展和添加更多数据类型。

简而言之,你的一开始没有仔细思考的设计思路如下:

  • 编写4个任务的方法

    • 例如

          public void hostDataExcel(){
              //具体的方法
              //从数据库查询
              //设置excel结构
              //写入到excel
              //...
          }
      
          public void machineDataExcel(){
              //具体的方法
              //从数据库查询
              //设置excel结构
              //写入到excel
              //...
          }
          //...
      
  • 再编写controller层进行调用,前端传递需要的excel数据调用对应接口

    • 例如

      @RequestMapping(value = "downloadEexcelFile/{type}")
          public void exportExcel(@PathVariable String type, HttpServletResponse response) throws Exception {
              logger.info("开始获取数据数据");
              // 创建ExcelWriter
              ExcelWriter writer = ExcelUtil.getWriter();
      
              //枚举类型,根据前端传递的type来获取对应的方法
              MetricsFileNameType metricsFileNameType ;
      
              try {
                  // 尝试从字符串类型的type获取枚举值
                  metricsFileNameType = MetricsFileNameType.valueOf(type);
              } catch (IllegalArgumentException e) {
                  response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // 设置响应状态码为400 Bad Request
                  response.setCharacterEncoding("UTF-8"); // 设置字符编码为UTF-8
                  response.setContentType("text/plain;"); // 设置响应内容类型为纯文本,并指定字符编码
                  response.getWriter().write("无效的type值: " + type); // 返回错误消息给前端
                  return ; // 例如,可以返回错误响应或执行其他操作
              }
      
              if(type.equals(metricsFileNameType.HOST)){
                  //调用具体的处理方法
                  hostDataExcel();
              }else if(metricsFileNameType.MACHINE){
                   //调用具体的处理方法
                  machineDataExcel();
              }else{
                  //...
              }
              // 设置响应头 + 文件名称
              response.setHeader("Content-Disposition", "attachment;filename="+ metricsFileNameType.getFileName());
              response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
              // 将Excel数据写入响应流
              writer.flush(response.getOutputStream(), true);
              // 关闭ExcelWriter
              writer.close();
              logger.info("结束获取数据数据");
          }
      

不过,这样的代码看起来确实简单清晰,并且也是可以完成任务的。但是,如果随着数据类型的增加与扩展,方法就将变得多,会越来越混乱。未来维护的代价似乎有点高。

那么经过我们仔细思考,发现这些代码有很多相同的地方。我们是不是可以进行抽象?那么我们先思考一下,这个四个代码方法都有相同的操作:

  • 从数据库获取数据 --- getResource()
  • 由于数据不一样,excel导出的表头肯定也不一样,表头的自定义 --- setHeader()
  • 最后就是获取最后的excel数据 ---- getTemplateExce()

...等等,还有很多,暂时,我们先思考到这里。

4个数据的操作大致都需要经过这四个过程并且他们具体的实现有稍微有些不同。

在此具体抽象之前,我们需要先创建一下这些数据的实体对象:

物理机的实体对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Host {

    private String resourcePoolId;
    private Float cpuutil;
    private Float ramutil;
    private Float ramused;

    private Float cpuUsedPeak;
    private Float ramUsedPeak;


}

云主机的实体对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Machine {

    private String resourcePoolId;
    private Float vCpuUtil;

    private Float memoryUsed;

    private Float totalMemory;

}

这样的实体对象创建完了。但是,似乎有点怪怪的。这些对象中好像有相同的字段。resourcePoolId那么,我们是不是可以通过抽象一个父类呢?当然抽象父类的作用不单单是这个。这个后面再说,让我们继续完善一下。

//abstract empty class for inheritance
public  abstract class AbstractMachineInfo {

}

物理机的实体对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Host extends AbstractMachineInfo{

    private String resourcePoolId;
    private Float cpuutil;
    private Float ramutil;
    private Float ramused;

    private Float cpuUsedPeak;
    private Float ramUsedPeak;


}

云主机的实体对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Machine extends AbstractMachineInfo{


    private String resourcePoolId;
    private Float vCpuUtil;

    private Float memoryUsed;

    private Float totalMemory;



}

有了它们,接下来就来抽象!!!,我们可以将上面的方法抽象成下面的这个对象:


public abstract class AbstractCollectionService {


    /**
     * 封装接口,并且隐藏底层
     *
     * @param writer
     * @return
     */
    public ExcelWriter getExcelWriter(ExcelWriter writer) throws Exception {
        writer = this.setExcelHeader(writer);
        List rows = this.getTemplateWriter();
        writer.write(rows, true);
        return writer;
    }

    /**
     * 获取excel模板数据
     *
     * @return
     */
    abstract List getTemplateWriter() throws Exception;

    /**
     * 设置excel表头
     *
     * @param writer
     * @return
     */
    abstract ExcelWriter setExcelHeader(ExcelWriter writer);

    /**
     * 获取资源数据
     *
     * @return
     */
    abstract List getResourceData();


}

因为这4个方法中大致都需要执行这些方法。那么,就意味着我们可以通过抽象来讲这些方法抽离出来。

那么我们就可以编写具体的对象来完成任务了。

物理机的:


public class HostMetricsExcelService extends AbstractCollectionService {

    public HostMetricsExcelService() {
    }

    @Override
    protected List getTemplateWriter() throws IOException {
        List resourceData = this.getResourceData();
        return resourceData;
    }

    //从数据库获取数据,我们简单模拟
    @Override
    protected List getResourceData() {
        Host Host = new Host();
        Host.setCpuutil(0.1f);
        Host.setNicreceiverate(0.2f);
        Host.setRamutil(0.6f);
        Host.setResourcePoolId("xxxx8748324");
        List rows = CollUtil.newArrayList(Host);
        return rows;
    }

    @Override
    protected ExcelWriter setExcelHeader(ExcelWriter writer) {

        // 创建标题样式
        StyleSet style = writer.getStyleSet();
        // 获取标题行样式
        CellStyle headCellStyle = style.getHeadCellStyle();
        headCellStyle.setAlignment(HorizontalAlignment.CENTER); // 设置标题居中
        Font font = writer.createFont();
        font.setBold(true); // 设置字体加粗
        headCellStyle.setFont(font);

        // 设置标题行样式
        writer.setStyleSet(style);

        // 自动适配单元格宽度
        writer.autoSizeColumnAll();

        writer.addHeaderAlias("resourcePoolId", "xxx");
        writer.addHeaderAlias("cpuutil", "xxx");
        writer.addHeaderAlias("ramutil", "xxx");
        writer.addHeaderAlias("ramused", "xxx");

        writer.addHeaderAlias("cpuUsedPeak", "xxx");
        writer.addHeaderAlias("ramUsedPeak", "xxx");

        return writer;
    }
}

云主机的:


public class VirtualMachineMetricsExcelService extends AbstractCollectionService {

    public VirtualMachineMetricsExcelService() {
    }

    @Override
    protected List getTemplateWriter() throws IOException {
        List resourceData = this.getResourceData();
        return resourceData;
    }

    //TODO 底层查询数据的方法,后续需要变更
    @Override
    protected List getResourceData() {
        Machine vmMetrics = new Machine();
        vmMetrics.setResourcePoolId("SX00112345678911");
        vmMetrics.setVCpuUtil(0.1f);
        vmMetrics.setMemoryUsed(0.2f);
        vmMetrics.setTotalMemory(0.3f);
        vmMetrics.setVRamUtil(0.4f);
        vmMetrics.setReadIops(1);
        vmMetrics.setWriteIops(2);
        vmMetrics.setReadRate(0.5f);
        vmMetrics.setWriteRate(0.6f);

        List rows = CollUtil.newArrayList(vmMetrics);
        return rows;
    }

    @Override
    protected ExcelWriter setExcelHeader(ExcelWriter writer) {

        // 创建标题样式
        StyleSet style = writer.getStyleSet();
        // 获取标题行样式
        CellStyle headCellStyle = style.getHeadCellStyle();
        headCellStyle.setAlignment(HorizontalAlignment.CENTER); // 设置标题居中
        Font font = writer.createFont();
        font.setBold(true); // 设置字体加粗
        headCellStyle.setFont(font);

        // 设置标题行样式
        writer.setStyleSet(style);

        // 自动适配单元格宽度
        writer.autoSizeColumnAll();

        writer.addHeaderAlias("resourcePoolId", "xx");
        writer.addHeaderAlias("vCpuUtil", "xx");
        writer.addHeaderAlias("memoryUsed", "xx");
        writer.addHeaderAlias("totalMemory", "xx");
        writer.addHeaderAlias("vRamUtil", "xx");
        writer.addHeaderAlias("readIops", "xx");



        return writer;
    }

}

等等等等...

到这里我们就完成了简单的抽象。其实,简单的业务逻辑是否要抽象还得要看是否需要扩展。如果不存在扩展抽象似乎作用不是很大... 这个就得仁者见仁智者见智了,可能大家会有不一样的看法吧...🤔

编写完这些代码之后,我们需要考虑的时如何选择我们想要的对象返回想要的数据。我可以通过简单工厂+枚举对象来帮助我们完成这个任务:

@Service
public class CollectExcelServiceFactory {

    public AbstractCollectionService getCollectExcelService(MetricsFileNameType type){

        AbstractCollectionService service = null;
        switch (type){
            case HOST_PERFORMANCE:
                service = new HostMetricsExcelService();
                break;
            case VIRTUAL_PERFORMANCE:
                service = new VirtualMachineMetricsExcelService();
                break;
            case HOST_RESOURCE:
                service = new HostResourceExcelService();
                break;
            case VIRTUAL_RESOURCE:
                service = new VirtualMachineResourceExcelService();
                break;
        }
        return service;
    }


}

这里是不是还有更好的方式?如果未来添加新的类型,这边也需要跟着变更,有点麻烦!我们可以通过反射来完成,我们传递对象的对象名,再通过反射创建对象,这样就可以实现动态创建我们想要的对象了。

接着就是优化我们的controller,它现在真的变的很干净了

    @RequestMapping(value = "downloadEexcelFile/{type}")
    public void exportExcel(@PathVariable String type, HttpServletResponse response) throws Exception {
        logger.info("开始获取数据数据");
        // 创建ExcelWriter
        ExcelWriter writer = ExcelUtil.getWriter();

        MetricsFileNameType metricsFileNameType ;

        try {
            // 尝试从字符串类型的type获取枚举值
            metricsFileNameType = MetricsFileNameType.valueOf(type);
        } catch (IllegalArgumentException e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // 设置响应状态码为400 Bad Request
            response.setCharacterEncoding("UTF-8"); // 设置字符编码为UTF-8
            response.setContentType("text/plain;charset=UTF-8"); // 设置响应内容类型为纯文本,并指定字符编码
            response.getWriter().write("无效的type值: " + type); // 返回错误消息给前端
            return ; // 例如,可以返回错误响应或执行其他操作
        }

        //工厂模式获取对应的service
        AbstractCollectionService collectExcelService = collectExcelServiceFactory.getCollectExcelService(metricsFileNameType);

        //通过抽象对象来生成具体的excel
        writer = collectExcelService.getExcelWriter(writer);
        // 设置响应头 + 文件名称

        response.setHeader("Content-Disposition", "attachment;filename="+ metricsFileNameType.getFileName());
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        // 将Excel数据写入响应流
        writer.flush(response.getOutputStream(), true);
        // 关闭ExcelWriter
        writer.close();
        logger.info("结束获取数据数据");
    }

总结:很高兴你能看到这里😋。文章可能写不是很高,也许也有别人写过类似的,但是不碍事。嘿嘿嘿!抽象有的时候好像是不难的,不过大多数来说还是困难的,悲!。不过还是要慢慢养成这些习惯。写代码之前多思考🤔。也许,会有更好的解决方案。当然,我懂,可能没有那么多时间给我们完成。好了,下篇文章见。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论