【问题标题】:How to apply Single Principle Responsibility in Java如何在 Java 中应用单一原则责任
【发布时间】:2021-01-21 22:29:43
【问题描述】:

我有一项将 xls 文件转换为 html 的服务。它工作得很好,但它是一个相当大的方法,不遵循任何 SOLID 原则。因此,我想改进它以至少遵循单一责任原则。但我真的不知道如何应用它并在我的案例中找到抽象级别。

@Service
public class xlsToHtmlImpl implements MultipartFileToHtmlService {

private final HtmlLayout htmlLayout;

@Autowired
public xlsToHtmlImpl(HtmlLayout htmlLayout) {
    this.htmlLayout = htmlLayout;
}


@Override
public InputStream multipartFileToHtml(MultipartFile multipartFile, boolean hasOnlyOneSheet, boolean hasBorders) throws IOException {

    String fileName = multipartFile.getOriginalFilename();
    BufferedInputStream inputStream = new BufferedInputStream(multipartFile.getInputStream());

    Workbook workbook;
    assert fileName != null;
    //Selecting workbook depending on FileType
    if (fileName.toLowerCase().endsWith(htmlLayout.FILE_TYPES[0])) {
        workbook = new HSSFWorkbook(inputStream);
    } else {
        workbook = new XSSFWorkbook(inputStream);
    }
    //Writing content of multipartFile to outputstream
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    outputStream.write(htmlLayout.openStyle());

    //Selecting style to apply depending on user input
    if (hasBorders) {
        outputStream.write(htmlLayout.noBordersStyle());
    } else {
        outputStream.write(htmlLayout.withBordersStyle());
    }
    outputStream.write(htmlLayout.closeStyle());

    outputStream.write(htmlLayout.openNewHtml());
    outputStream.write(fileName.getBytes());


    //Different algorithm for the content of the body depending on user input
    Sheet sheet;
    if (hasOnlyOneSheet) {
        sheet = workbook.getSheetAt(0);
        Iterator<Row> rows = sheet.rowIterator();
        while (rows.hasNext()) {
            Row row = rows.next();
            Iterator<Cell> cells = row.cellIterator();
            outputStream.write(htmlLayout.newLine());
            outputStream.write(htmlLayout.newRow());
            while (cells.hasNext()) {
                Cell cell = cells.next();
                outputStream.write(htmlLayout.newCell());
                outputStream.write(cell.toString().getBytes());
                outputStream.write(htmlLayout.closeCell());

            }
            outputStream.write(htmlLayout.closeRow());
        }
    } else {
        for (int i = 0; i< workbook.getNumberOfSheets(); i++) {
            sheet = workbook.getSheetAt(i);
            Iterator<Row> rows = sheet.rowIterator();
            outputStream.write(htmlLayout.newLine());
            outputStream.write(htmlLayout.newRow());
            outputStream.write(htmlLayout.closeCell());
            outputStream.write(htmlLayout.closeRow());
            outputStream.write(htmlLayout.newLine());
            while (rows.hasNext()) {
                Row row = rows.next();
                Iterator<Cell> cells = row.cellIterator();
                outputStream.write(htmlLayout.newLine());
                outputStream.write(htmlLayout.newRow());
                while (cells.hasNext()) {
                    Cell cell = cells.next();
                    outputStream.write(htmlLayout.newCell());
                    outputStream.write(cell.toString().getBytes());
                    outputStream.write(htmlLayout.closeCell());

                }
                outputStream.write(htmlLayout.closeRow());
            }
        }
    }

    outputStream.write(htmlLayout.newLine());
    outputStream.write(htmlLayout.closeHtml());
    outputStream.close();

    //Returning result as ByteArrayInputStream to controller
    return new ByteArrayInputStream(outputStream.toByteArray());
}

其中htmlLayout 包含html sn-p like:

public byte[] closeHtml() {return "&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;".getBytes();}

我尝试关注这篇文章:https://www.baeldung.com/java-single-responsibility-principle#:~:text=As%20the%20name%20suggests%2C%20this,only%20one%20reason%20to%20change.&text=These%20classes%20are%20harder%20to%20maintain

根据这篇文章,我尝试创建不同的类,如下所示:

public class HtmlStyleWrapper {

private byte[] style;


public byte[] withBordersStyle() {
    return ("table, td{" +
            "    border: 1px solid black;\n" +
            "    border-collapse: collapse;\n" +
            "    padding: 9px;\n" +
            "}").getBytes();
}

public byte[] noBordersStyle() {
    return ("td {" +
            "    padding: 9px;\n" +
            "}").getBytes();
}

public byte[] openStyle() {
    return "</title></head><body><style>".getBytes();
}

public byte[] closeStyle() {
    return "</style><table>".getBytes();
}

public void wrapStyle(ByteArrayOutputStream outputStream, boolean hasBorders) throws IOException {
    outputStream.write(openStyle());
    if (hasBorders) {
        outputStream.write(noBordersStyle());
    } else {
        outputStream.write(withBordersStyle());
    }
    outputStream.write(closeStyle());
}


public class HtmlBodyWrapper {

private byte[] body;

public byte[] openNewHtml() {
    return "<!DOCTYPE html><html><head><title>".getBytes();
}

public byte[] newLine() {
    return "\n".getBytes();
}

public byte[] closeHtml() {
    return "</table></body></html>".getBytes();
}

public byte[] newRow() {
    return "<tr>".getBytes();
}

public byte[] closeRow() {
    return "</tr>".getBytes();
}

public byte[] newCell() {
    return "<td>".getBytes();
}

public byte[] closeCell() {
    return "</td>".getBytes();
}

public void wrapBody(ByteArrayOutputStream outputStream, String fileName, boolean hasOnlyOneSheet, Workbook workbook) throws IOException {
//Write to outputstream
}

目标是得到类似的东西

wrapHTMLBody(wrapStyle(htmlLayout.getHTML_STYLE()), table)

但我觉得我没有采取正确的方法,而且我没有正确理解 SRP。

【问题讨论】:

  • 我认为这个问题更适合codereview.stackexchange.com,因为代码工作得很好。
  • @Amongalen 我猜你是对的,我会转移到那里

标签: java design-patterns solid-principles single-responsibility-principle


【解决方案1】:

单一职责原则 (SRP) 是一种计算机编程原则,它规定计算机程序中的每个类都应对该程序功能的单个部分负责,并对其进行封装。所有该模块、类或函数的服务都应与该职责紧密结合

这是来自wikipedia

在我看来,SRP 是一种设计思维,使用它取决于您的案例和系统目标。

简而言之,一个类必须向其调用者提供哪些公共函数以及这些函数足以实现该类定义的目标。

在你的情况下,我们应该先回答一个问题:

该类应为其调用者提供什么特性或功能?

例如,它只是将 Excel 文件转换为 Html。 xlsToHtmlImpl 类只提供了一个公共函数multipartFileToHtml,足以达到目的。

第二个问题是类支持调用者设置哪些选项?

也许我们需要定义两个类:

  • HtmlStyleWrapper 类定义了如何在内存中生成Html Data
  • HtmlWritter 类定义了如何将Html Data 输出到文件中。

不管怎样,SRP 就是每个类只为自己的职责提供功能。

【讨论】:

    【解决方案2】:

    在高层次上,单一职责仅仅意味着您想要分离独立的事物并将它们粘在方法或类中。

    这样,当您想要更改算法的某个方面时,您可以转到该方法/类并更改那里的行为,而无需更改其余代码。 您也可以在 2 种方法中对算法的某一方面进行 2 次实现,并通过调用适当的方法来切换行为。

    它使代码更模块化、更可测试、更容易调试。

    因此,对于您的示例,让我们首先确定职责。我可以看到一些:

    • 打开工作簿
    • 编写样式块
    • 编写 html 块
    • 关闭输出流

    有了“编写 html 块”职责,我可以看到嵌套职责

    • 打开 HTML 标签
    • 写单张
    • 写多页
    • 关闭 HTML 标记

    在那里,你可以看到写单张和多张共享一些代码,所以也可以在一个方法中提取。

    所以这是一种编写所有这些的方法,在方法中很好地分开:

    public InputStream multipartFileToHtml(MultipartFile multipartFile, boolean hasOnlyOneSheet, boolean hasBorders) throws IOException {
        String fileName = multipartFile.getOriginalFilename();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            Workbook workbook = createWorkbook(multipartFile);
    
            writeStyleBlock(outputStream, hasBorders);
    
            writeHtmlBlock(outputStream, workbook, fileName, hasOnlyOneSheet);
        } finally {
            outputStream.close();
        }
    
        return new ByteArrayInputStream(outputStream.toByteArray());
    
    }
    
    private Workbook createWorkbook(MultipartFile multipartFile) {
        BufferedInputStream inputStream = new BufferedInputStream(multipartFile.getInputStream());
        String fileName = multipartFile.getOriginalFilename();
        assert fileName != null;
    
        Workbook workbook;
        //Selecting workbook depending on FileType
        if (fileName.toLowerCase().endsWith(htmlLayout.FILE_TYPES[0])) {
            workbook = new HSSFWorkbook(inputStream);
        } else {
            workbook = new XSSFWorkbook(inputStream);
        }
    
        return workbook;
    }
    
    private void writeStyleBlock(ByteArrayOutputStream outputStream, boolean hasBorders) {
        //Writing content of multipartFile to outputstream
        outputStream.write(htmlLayout.openStyle());
    
        //Selecting style to apply depending on user input
        if (hasBorders) {
            outputStream.write(htmlLayout.noBordersStyle());
        } else {
            outputStream.write(htmlLayout.withBordersStyle());
        }
        outputStream.write(htmlLayout.closeStyle());
    }
    
    private void writeHtmlBlock(ByteArrayOutputStream outputStream, Workbook workbook, String fileName, boolean hasOnlyOneSheet) throws IOException {
        openHtmlTag(outputStream, fileName);
    
        //Different algorithm for the content of the body depending on user input
        if (hasOnlyOneSheet) {
            writeSingleSheet(outputStream, workbook);
        } else {
            writeMultiSheet(outputStream, workbook);
        }
    
        closeHtmlTag(outputStream);
    }
    
    private void openHtmlTag(ByteArrayOutputStream outputStream, String fileName) throws IOException {
        outputStream.write(htmlLayout.openNewHtml());
        outputStream.write(fileName.getBytes());
    }
    
    private void closeHtmlTag(ByteArrayOutputStream outputStream) {
        outputStream.write(htmlLayout.newLine());
        outputStream.write(htmlLayout.closeHtml());
    }
    
    private void writeSingleSheet(ByteArrayOutputStream outputStream, Workbook workbook) throws IOException {
        Sheet sheet = workbook.getSheetAt(0);
        writeRows(outputStream, sheet);
    }
    
    private void writeMultiSheet(ByteArrayOutputStream outputStream, Workbook workbook) throws IOException {
        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
            Sheet sheet = workbook.getSheetAt(i);
            outputStream.write(htmlLayout.newLine());
            outputStream.write(htmlLayout.newRow());
            outputStream.write(htmlLayout.closeCell());
            outputStream.write(htmlLayout.closeRow());
            outputStream.write(htmlLayout.newLine());
            writeRows(outputStream, sheet);
        }
    }
    
    private void writeRows(ByteArrayOutputStream outputStream, Sheet sheet) throws IOException {
        Iterator<Row> rows = sheet.rowIterator();
        while (rows.hasNext()) {
            Row row = rows.next();
            Iterator<Cell> cells = row.cellIterator();
            outputStream.write(htmlLayout.newLine());
            outputStream.write(htmlLayout.newRow());
            while (cells.hasNext()) {
                Cell cell = cells.next();
                outputStream.write(htmlLayout.newCell());
                outputStream.write(cell.toString().getBytes());
                outputStream.write(htmlLayout.closeCell());
    
            }
            outputStream.write(htmlLayout.closeRow());
        }
    }
    

    您甚至不需要新的类来分离职责。 现在,如果您愿意,您可以进一步将这些方法提取到新的类中,WorkbookCreatorStyleBlockWriterHTMLBlockWriter、...
    达到您想要的粒度。

    我想在这里,我们不需要进入类,但如果 HTML writer 部分后来变得太大,可以将其提取到一个新类中

    【讨论】:

    • SRP 不同于“只做一件事”。见:What is the scope of the Single Responsibility Principle?
    • @jaco0646 好的,我可以删除“只做一件事”位。但是我分解方法的方式意味着,如果有人想更改单个工作表的编写方式,他将不会触及编写样式块的方法。这就是 SRP 对我的意义。现在,可以在不同的级别、方法、类或模块上进行分离。
    • @Bentaye 谢谢你,我会继续挖掘这个话题,但你的回答帮助我更好地理解了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-19
    相关资源
    最近更新 更多