• 重构:以Java POI 导出EXCEL为例


    重构

    开头先抛出几个问题吧,这几个问题也是《重构:改善既有代码的设计》这本书第2章的问题。

    什么是重构?

    为什么要重构?

    什么时候要重构?

    接下来就从这几个问题出发,通过这几个问题来系统的了解重构的意义。

    什么是重构?

    《重构:改善既有代码的设计》这本书中将重构以名词和动词形式进行解释,如下

    重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

    重构(动词):使用一系列重构的手法,在不改变软件可观察行为的前提下,调整其结构。

    然后作者又将定义往两个方面扩展

    1. 重构的目的是使软件更容易被理解和修改。
    2. 重构不会改变软件可观察的行为------重构之后软件功能一如既往。

    简单来说就是

    1. 对编码人员来说重构可以让代码可读性变高,可扩展性变高,修改变得更容易。
    2. 对用户来说重构不会影响原有的操作流程。

    为什么要重构?

    1. 重构改进软件设计
    2. 重构使软件更容易理解
    3. 重构帮忙找到bug
    4. 重构提高编程速度

    重构可以让代码变得结构化,利用设计模式对其合理的进行修改,使得代码可读性变高。如果代码没有进行重构,某些功能会变得越来越臃肿,将慢慢失去自己所应有的结构,而后续的人也会在上面添砖加瓦,使得程序越来越难以理解其含义。

    什么时候要重构?

    关于何时重构,这本书中给出了几点

    1. 事不过三,三则重构(当重复代码超过三次,就应该重构)
    2. 添加功能时重构(代码的设计无法帮助我轻松添加我所需要的特性)
    3. 修补错误时重构(可读性不高,不能清晰快速的理解它)
    4. 复审代码时重构(可以改善开发状态,帮助理解软件系统中的更多部分)

    实例

    这篇博文主要讲述博主将网上原有的execl导出功能代码进行一步步的重构,设计成一个通用的、可扩展的代码。

    我们先来看看早期的代码,如下

    public class ExportExcel<T> {
        public void exportExcel(Collection<T> dataset, OutputStream out) {
            exportExcel("测试POI导出EXCEL文档", null, dataset, out, "yyyy-MM-dd");
        }
    
        public void exportExcel(String[] headers, Collection<T> dataset, OutputStream out) {
            exportExcel("测试POI导出EXCEL文档", headers, dataset, out, "yyyy-MM-dd");
        }
    
        public void exportExcel(String[] headers, Collection<T> dataset, OutputStream out, String pattern) {
            exportExcel("测试POI导出EXCEL文档", headers, dataset, out, pattern);
        }
    
        /**
         * 这是一个通用的方法,利用了JAVA的反射机制,可以将放置在JAVA集合中并且符号一定条件的数据以EXCEL 的形式输出到指定IO设备上
         * 
         * @param title
         *            表格标题名
         * @param headers
         *            表格属性列名数组
         * @param dataset
         *            需要显示的数据集合,集合中一定要放置符合javabean风格的类的对象。此方法支持的
         *            javabean属性的数据类型有基本数据类型及String,Date,byte[](图片数据)
         * @param out
         *            与输出设备关联的流对象,可以将EXCEL文档导出到本地文件或者网络中
         * @param pattern
         *            如果有时间数据,设定输出格式。默认为"yyy-MM-dd"
         */
        @SuppressWarnings("unchecked")
        public void exportExcel(String title, String[] headers, Collection<T> dataset, OutputStream out, String pattern) {
            // 声明一个工作薄
            HSSFWorkbook workbook = new HSSFWorkbook();
            // 生成一个表格
            HSSFSheet sheet = workbook.createSheet(title);
            // 设置表格默认列宽度为15个字节
            sheet.setDefaultColumnWidth((short) 15);
            // 生成一个样式
            HSSFCellStyle style = workbook.createCellStyle();
            // 设置这些样式
            style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            // 生成一个字体
            HSSFFont font = workbook.createFont();
            font.setColor(HSSFColor.VIOLET.index);
            font.setFontHeightInPoints((short) 12);
            font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
            // 把字体应用到当前的样式
            style.setFont(font);
            // 生成并设置另一个样式
            HSSFCellStyle style2 = workbook.createCellStyle();
            style2.setFillForegroundColor(HSSFColor.LIGHT_YELLOW.index);
            style2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style2.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style2.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
            // 生成另一个字体
            HSSFFont font2 = workbook.createFont();
            font2.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
            // 把字体应用到当前的样式
            style2.setFont(font2);
    
            // 声明一个画图的顶级管理器
            HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
            // 定义注释的大小和位置,详见文档
            HSSFComment comment = patriarch.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short) 4, 2, (short) 6, 5));
            // 设置注释内容
            comment.setString(new HSSFRichTextString("可以在POI中添加注释!"));
            // 设置注释作者,当鼠标移动到单元格上是可以在状态栏中看到该内容.
            comment.setAuthor("leno");
    
            // 产生表格标题行
            HSSFRow row = sheet.createRow(0);
            for (short i = 0; i < headers.length; i++) {
                HSSFCell cell = row.createCell(i);
                cell.setCellStyle(style);
                HSSFRichTextString text = new HSSFRichTextString(headers[i]);
                cell.setCellValue(text);
            }
    
            // 遍历集合数据,产生数据行
            Iterator<T> it = dataset.iterator();
            int index = 0;
            while (it.hasNext()) {
                index++;
                row = sheet.createRow(index);
                T t = (T) it.next();
                // 利用反射,根据javabean属性的先后顺序,动态调用getXxx()方法得到属性值
                Field[] fields = t.getClass().getDeclaredFields();
                for (short i = 0; i < fields.length; i++) {
                    HSSFCell cell = row.createCell(i);
                    cell.setCellStyle(style2);
                    Field field = fields[i];
                    String fieldName = field.getName();
                    String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    try {
                        Class tCls = t.getClass();
                        Method getMethod = tCls.getMethod(getMethodName, new Class[] {});
                        Object value = getMethod.invoke(t, new Object[] {});
                        // 判断值的类型后进行强制类型转换
                        String textValue = null;
                        // if (value instanceof Integer) {
                        // int intValue = (Integer) value;
                        // cell.setCellValue(intValue);
                        // } else if (value instanceof Float) {
                        // float fValue = (Float) value;
                        // textValue = new HSSFRichTextString(
                        // String.valueOf(fValue));
                        // cell.setCellValue(textValue);
                        // } else if (value instanceof Double) {
                        // double dValue = (Double) value;
                        // textValue = new HSSFRichTextString(
                        // String.valueOf(dValue));
                        // cell.setCellValue(textValue);
                        // } else if (value instanceof Long) {
                        // long longValue = (Long) value;
                        // cell.setCellValue(longValue);
                        // }
                        if (value instanceof Boolean) {
                            boolean bValue = (Boolean) value;
                            textValue = "男";
                            if (!bValue) {
                                textValue = "女";
                            }
                        } else if (value instanceof Date) {
                            Date date = (Date) value;
                            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                            textValue = sdf.format(date);
                        } else if (value instanceof byte[]) {
                            // 有图片时,设置行高为60px;
                            row.setHeightInPoints(60);
                            // 设置图片所在列宽度为80px,注意这里单位的一个换算
                            sheet.setColumnWidth(i, (short) (35.7 * 80));
                            // sheet.autoSizeColumn(i);
                            byte[] bsValue = (byte[]) value;
                            HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 1023, 255, (short) 6, index, (short) 6,
                                    index);
                            anchor.setAnchorType(2);
                            patriarch.createPicture(anchor, workbook.addPicture(bsValue, HSSFWorkbook.PICTURE_TYPE_JPEG));
                        } else {
                            // 其它数据类型都当作字符串简单处理
                            textValue = value.toString();
                        }
                        // 如果不是图片数据,就利用正则表达式判断textValue是否全部由数字组成
                        if (textValue != null) {
                            Pattern p = Pattern.compile("^//d+(//.//d+)?$");
                            Matcher matcher = p.matcher(textValue);
                            if (matcher.matches()) {
                                // 是数字当作double处理
                                cell.setCellValue(Double.parseDouble(textValue));
                            } else {
                                HSSFRichTextString richString = new HSSFRichTextString(textValue);
                                HSSFFont font3 = workbook.createFont();
                                font3.setColor(HSSFColor.BLUE.index);
                                richString.applyFont(font3);
                                cell.setCellValue(richString);
                            }
                        }
                    } catch (SecurityException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } finally {
                        // 清理资源
                    }
                }
            }
            try {
                workbook.write(out);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            // 测试学生
            ExportExcel<Student> ex = new ExportExcel<Student>();
            String[] headers = { "学号", "姓名", "年龄", "性别", "出生日期" };
            List<Student> dataset = new ArrayList<Student>();
            dataset.add(new Student(10000001, "张三", 20, true, new Date()));
            dataset.add(new Student(20000002, "李四", 24, false, new Date()));
            dataset.add(new Student(30000003, "王五", 22, true, new Date()));
            // 测试图书
            ExportExcel<Book> ex2 = new ExportExcel<Book>();
            String[] headers2 = { "图书编号", "图书名称", "图书作者", "图书价格", "图书ISBN", "图书出版社", "封面图片" };
            List<Book> dataset2 = new ArrayList<Book>();
            try {
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D://USER//Desktop//1.jpg"));
                byte[] buf = new byte[bis.available()];
                while ((bis.read(buf)) != -1) {
                    //
                }
                dataset2.add(new Book(1, "jsp", "leno", 300.33f, "1234567", "清华出版社", buf));
                dataset2.add(new Book(2, "java编程思想", "brucl", 300.33f, "1234567", "阳光出版社", buf));
                dataset2.add(new Book(3, "DOM艺术", "lenotang", 300.33f, "1234567", "清华出版社", buf));
                dataset2.add(new Book(4, "c++经典", "leno", 400.33f, "1234567", "清华出版社", buf));
                dataset2.add(new Book(5, "c#入门", "leno", 300.33f, "1234567", "汤春秀出版社", buf));
    
                OutputStream out = new FileOutputStream("E://a.xls");
                OutputStream out2 = new FileOutputStream("E://b.xls");
                ex.exportExcel(headers, dataset, out);
                ex2.exportExcel(headers2, dataset2, out2);
                out.close();
                out2.close();
                JOptionPane.showMessageDialog(null, "导出成功!");
                System.out.println("excel导出成功!");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    View Code

    这边将整个导出功能都放在一个类里面,这样代码修改起来不容易。

    接下来我们先来提炼函数(Extract Method)

    上述代码中的导出流程大致上可以分为五个步骤,如下

    1. 创建一系列的对象(workbook、sheet...)
    2. 创建样式、字体
    3. 产生表格标题行
    4. 遍历集合数据,产生数据行
    5. 写入io流

    那么我们就可以按照这几个流程,将他们封装成方法,一些对象可以修改才类的成员变量

    开始重构

    1.创建一系列的对象(workbook、sheet...)

    之前的代码

    // 声明一个工作薄
    HSSFWorkbook workbook = new HSSFWorkbook();
    View Code

    将workbook修改成成员变量,在构造函数时初始化它

        private HSSFWorkbook workbook;
        
        public ExportExcel() {
            this(new HSSFWorkbook());
        }
    
        public ExportExcel(HSSFWorkbook workbook) {
            this.workbook = workbook;
        }

    2.创建样式、字体

     之前的代码

            // 生成一个样式
            HSSFCellStyle style = workbook.createCellStyle();
            // 设置这些样式
            style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            // 生成一个字体
            HSSFFont font = workbook.createFont();
            font.setColor(HSSFColor.VIOLET.index);
            font.setFontHeightInPoints((short) 12);
            font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
            // 把字体应用到当前的样式
            style.setFont(font);
            // 生成并设置另一个样式
            HSSFCellStyle style2 = workbook.createCellStyle();
            style2.setFillForegroundColor(HSSFColor.LIGHT_YELLOW.index);
            style2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style2.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style2.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
            // 生成另一个字体
            HSSFFont font2 = workbook.createFont();
            font2.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
            // 把字体应用到当前的样式
            style2.setFont(font2);
    View Code

    将创建两个样式代码提炼成两个函数,数据标题样式方法getRowTitleStyle和数据行样式方法getRowDataStyle

        /**
         * Description:生成数据标题样式
         */
        private HSSFCellStyle getRowTitleStyle() {
            HSSFCellStyle style = workbook.createCellStyle();
            // 设置这些样式
            style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            
            // 生成一个字体
            HSSFFont font = workbook.createFont();
            font.setColor(HSSFColor.VIOLET.index);
            font.setFontHeightInPoints((short) 12);
            font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
            
            // 把字体应用到当前的样式
            style.setFont(font);
            
            return style;
        }
        
        /**
         * Description:生成数据行样式
         */
        private HSSFCellStyle getRowDataStyle() {
            HSSFCellStyle style = workbook.createCellStyle();
            // 设置这些样式
            style.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
            style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
            style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
            style.setBorderRight(HSSFCellStyle.BORDER_THIN);
            style.setBorderTop(HSSFCellStyle.BORDER_THIN);
            style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
            style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
            
            // 生成另一个字体
            HSSFFont font = workbook.createFont();
            font.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
            
            style.setFont(font);
                    
            return style;
        }
    View Code

    3、4、5.产生表格标题行、遍历集合数据,产生数据行、写入io流

            // 产生表格标题行
            HSSFRow row = sheet.createRow(0);
            for (short i = 0; i < headers.length; i++) {
                HSSFCell cell = row.createCell(i);
                cell.setCellStyle(style);
                HSSFRichTextString text = new HSSFRichTextString(headers[i]);
                cell.setCellValue(text);
            }

    将上面产生表格标题行代码块提炼成方法,产生数据行和写入io流也是如此。代码就不贴了

    提炼后的主方法

        public void exportExcel(String title, String[] headers, Collection<T> dataset, OutputStream out, String pattern) {
            // 生成一个表格
            HSSFSheet sheet = workbook.createSheet(title);
            
            // 生成数据标题和数据行样式
            HSSFCellStyle rowTirtleStyle = getRowTitleStyle();
            HSSFCellStyle rowDataStyle = getRowDataStyle();
            
            //创建数据标题和数据行
            createRowTitle(headers, sheet, rowTirtleStyle);
            createRowData(dataset, pattern, sheet, rowDataStyle);
            
            //写入流
            writeExecl(out);
        }

    提炼后是不是精简了许多,好了,这篇博文暂时就介绍一种重构方式--提炼函数。

    后续博文会继续对Java POI 导出EXCEL进行重构

    将一些重复代码替换成已有的工具类中的代码

    将一些方法提炼成类

    将数据封装成对象

    等等

    觉得不错的可以关注我哦

    github地址在这,后续会增加其他各种各样的工具项目,觉得不错的点个star


    作者: 云枭zd
    Github: Github地址
    出处: https://www.cnblogs.com/fixzd/
    版权声明:本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
  • 相关阅读:
    JAVA学习笔记 -- 包资源文件jar包裹
    Hibernate一个简短的引论
    JMS分布式应用程序异步消息解决方案EhCache 高速缓存同步问题
    一个红
    Android技术——切换视图(两)随着ViewPage达到Tab幻灯片浏览
    hdu 2243 考研绝望——复杂的文字(AC自己主动机+矩阵高速功率)
    我在这里3在引发众1.8万的经验分享
    关系数据库的基本概念和MySQL说明
    2015第5周日
    HTML5
  • 原文地址:https://www.cnblogs.com/fixzd/p/8982739.html
Copyright © 2020-2023  润新知