资讯专栏INFORMATION COLUMN

【译】软件设计原则

jsdt / 2019人阅读

摘要:软件设计一直是开发周期中最重要的阶段,在设计弹性和灵活的体系结构的花费的时间越多,在将来出现变更时就越节省时间。在本文中,我们将讨论有助于创建易于维护和可扩展的软件的关键设计原则。

翻译: 疯狂的技术宅
来源: Programmer Gate
原文标题: Software design principles
英文原文: http://programmergate.com/sof...
说明:本专栏文章首发于公众号:jingchengyideng 。

软件设计一直是开发周期中最重要的阶段,在设计弹性和灵活的体系结构的花费的时间越多,在将来出现变更时就越节省时间。需求总是变化的,如果不定期添加或维护功能,软件将出现为遗留问题,并且变更成本是根据系统的结构和体系结构来确定的。在本文中,我们将讨论有助于创建易于维护和可扩展的软件的关键设计原则。

1. 一个实际的场景

假设老板要求你写一个将word文档转换成PDF的程序。这个任务看起来很简单,只需找到一个可靠的库,它可以将word文档转换成PDF,并把它集成到你的程序中。在做了一些研究之后,你最终决定使用 Aspose.words 框架并创建了以下类:

代码:PDFConverter.java

/**
 * A utility class which converts a word document to PDF
 * @author Hussein
 *
 */
public class PDFConverter {
 
    /**
     * This method accepts as input the document to be converted and 
     * returns the converted one.
     * @param fileBytes
     * @throws Exception 
     */
    public byte[] convertToPDF(byte[] fileBytes) throws Exception
    {
        // We"re sure that the input is always a WORD. So we just use 
        //aspose.words framework and do the conversion.
        
        InputStream input = new ByteArrayInputStream(fileBytes);
        com.aspose.words.Document wordDocument = new com.aspose.words.Document(input);
        ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream();
        wordDocument.save(pdfDocument, SaveFormat.PDF);
        return pdfDocument.toByteArray();
    }
}

生活很简单,一切都很顺利!!

需求总是变化

几个月后,一些用户要求支持也 excel 文档,所以你又做了一些研究,决定使用ascell.cell 。然后你找到你原来的类,并添加了一个名为 documentType 的新字段,并修改了你的方法,代码如下:

代码:PDFConverter.java

public class PDFConverter {
    // we didn"t mess with the existing functionality, by default 
    // the class will still convert WORD to PDF, unless the client sets 
    // this field to EXCEL.
    public String documentType = "WORD";
 
    /**
     * This method accepts as input the document to be converted and 
     * returns the converted one.
     * @param fileBytes
     * @throws Exception 
     */
    public byte[] convertToPDF(byte[] fileBytes) throws Exception
    {
        if(documentType.equalsIgnoreCase("WORD"))
        {
            InputStream input = new ByteArrayInputStream(fileBytes);
            com.aspose.words.Document wordDocument = new com.aspose.words.Document(input);
            ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream();
            wordDocument.save(pdfDocument, SaveFormat.PDF);
            return pdfDocument.toByteArray();
        }
        else
        {
            InputStream input = new ByteArrayInputStream(fileBytes);
            Workbook workbook = new Workbook(input);
            PdfSaveOptions saveOptions = new PdfSaveOptions();
            saveOptions.setCompliance(PdfCompliance.PDF_A_1_B);
            ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream();
            workbook.save(pdfDocument, saveOptions);
            return pdfDocument.toByteArray();
        }
    }
}

该代码可以为新用户正常正常,而且仍然可以按照预期的方式为现有的用户工作,但是一些糟糕的设计气味开始出现在代码中,这样做是不完美的,当再一个新的文档类型时,我们将无法轻松修改这个类。

代码重复:正如你所看到的,在if/else块存在类似的代码,如果有一天再添加不同的扩展,那么将会出现大量的重复。如果我们决定返回一个文件而不是一个 byte[] 那么就必须在所有的块中做相同的修改。

刚性:所有的转换算法都是在同一种方法中进行耦合的,所以如果你改变了一些算法,其他的算法也会随之受到影响。

固定:上面的方法直接依赖于documentType字段,假如一些用户在调用convertToPDF()之前忘记了设置这个字段,那将得不到预期的结果,我们也不能在任何其他项目中重用该方法,因为它依赖于字段。

高级模块与框架之间的耦合:如果将来我们决定用更可靠的方式替换 Aspose 框架,那么最终修将会改整个 PDFConverter 类,并且会有许多用户受到影响。

正确的方式

通常情况下,并不是所有的开发人员都能够预见未来的变化。因此,他们中的大多数人将会像我们第一次实现的那样,完全实现程序,但是在第一次改变之后,情况就会变得很明显,将来会发生类似的变化。所以,好的开发人员将会为了尽可能减少将来变更的成本使用正确的方式,而不是用if / else块实现。所以我们在暴露的工具(PDFConverter)和低级转换算法之间创建一个抽象层,并将每个算法移动到一个多带带的类中,如下所示:

代码:Converter.java

/**
 * This interface represents an abstract algorithm for converting
 * any type of document to PDF.
 * @author Hussein
 *
 */
public interface Converter {
 
    public byte[] convertToPDF(byte[] fileBytes) throws Exception;
}

代码:ExcelPDFConverter.java

/**
 * This class holds the algorithm for converting EXCEL
 * documents to PDF.
 * @author Hussein
 *
 */
public class ExcelPDFConverter implements Converter{
 
    public byte[] convertToPDF(byte[] fileBytes) throws Exception {
        InputStream input = new ByteArrayInputStream(fileBytes);
        Workbook workbook = new Workbook(input);
        PdfSaveOptions saveOptions = new PdfSaveOptions();
        saveOptions.setCompliance(PdfCompliance.PDF_A_1_B);
        ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream();
        workbook.save(pdfDocument, saveOptions);
        return pdfDocument.toByteArray();
    };
}

代码:WordPDFConverter.java

/**
 * This class holds the algorithm for converting WORD 
 * documents to PDF.
 * @author Hussein
 *
 */
public class WordPDFConverter implements Converter {
 
    @Override
    public byte[] convertToPDF(byte[] fileBytes) throws Exception {
        InputStream input = new ByteArrayInputStream(fileBytes);
        com.aspose.words.Document wordDocument = new com.aspose.words.Document(input);
        ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream();
        wordDocument.save(pdfDocument, SaveFormat.PDF);
        return pdfDocument.toByteArray();
    }
}

代码:PDFConverter.java

public class PDFConverter {
 
    /**
     * This method accepts as input the document to be converted and 
     * returns the converted one.
     * @param fileBytes
     * @throws Exception 
     */
    public byte[] convertToPDF(Converter converter, byte[] fileBytes) throws Exception
    {
        return converter.convertToPDF(fileBytes);
    }
}

当调用convertToPDF()时,我们强制用户决定应该使用哪种转换算法。

2. 这样做的好处是什么?!

关注点分离(高内聚/低耦合): 现在 PDFConverter 类对程序中使用的转换算法一无所知,它主要关注的是为用户提供各种转换特性,而关心转换是如何进行的。现在,我们可以随时替换底层转换框架,只要我们能够返回预期的结果,就不会人会知道。

单一职责: 创建抽象层并将每个动态行为移到多带带的类之后,我们实际上删除了 convertToPDF() 方法在以前初始设计中的的多重职责,现在它只有一个职责,就是将用户的请求委托给抽象的转换层。此外,转换器接口的每个实现类现在都有一个单一的责任,即将某些文档类型转换为PDF。因此,每个组件都有一个被修改的理由,因此没有回归。

打开/关闭程序: 我们的程序现在对扩展开放,并且对修改关闭,当我们在未来想要支持一些新的文档类型时,只需要从 Converter 接口创建一个新的实现类,并且不需要修改 PDFConverter 工具,因为现在我们的工具依赖于抽象。

3. 从这篇文章中学到的设计原则

以下是构建应用程序架构时要遵循的最佳设计实践:

将程序划分为几个模块,并在每个模块的顶部添加一个抽象层。

有利于抽象实现:一定要依赖抽象层,这将有利于程序将来的扩展,抽象应该应用于程序的动态部分(最有可能经常改变的部分),不一定在所有的部分,因为在过度使用的情况下是你的代码变得非常复杂。

确定程序的不同方面,并将它们与保持不变的部分分开。

不要重复自己:永远把重复的功能在一些工具类中,并使其通过整个程序访问,这会使你的修改变得容易得多。

通过抽象层隐藏低级实现:低级模块有很高的可能性会定期更改,因此将其与高级模块分开。

每个类/方法/模块应该有一个理由去改变,所以为了减少回归,总是给每一个类单一的责任。

关注点分离:每个模块都知道其他模块做什么,但是它自己不知道该怎么做。

作者简介:
HUSSEINTEREK: programmergate.com的创始人,对软件工程和所有与java相关的东西都充满激情。

欢迎扫描二维码关注公众号,每天推送我翻译的技术文章。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67515.html

相关文章

  • []每位开发者都应该知道SOLID原则

    摘要:开闭原则软件实体类,模块,函数应该是可以扩展的,而不是修改。函数并不符合开闭原则,因为一旦有新动物出现,它需要修改代码。 By Chidume Nnamdi | Oct 9, 2018 原文 面向对象的编程类型为软件开发带来了新的设计。 这使开发人员能够在一个类中组合具有相同目的/功能的数据,来实现单独的一个功能,不必关心整个应用程序如何。 但是,这种面向对象的编程还是会让开发者困惑或...

    go4it 评论0 收藏0
  • Design Pattern – Overview(

    摘要:设计模式是软件开发人员在整个软件开发的过程中面临普遍问题的解决方案。这些作者被统称为四人帮。根据这些作者的观念,设计模式主要是基于一下几种面向对象的设计原则。例如,单例模式表示使用单一对象。我们还将讨论另外一个类别的设计模式。 原文链接译者:smallclover个人翻译,水平有限,如有错误欢迎指出,谢谢! 设计模式-概述 设计模式体现了经验丰富的面向对象软件开发人员的最佳实践。设计模...

    WilsonLiu95 评论0 收藏0
  • PHPer书单

    摘要:想提升自己,还得多看书多看书多看书下面是我收集到的一些程序员应该看得书单及在线教程,自己也没有全部看完。共勉吧当然,如果你有好的书想分享给大家的或者觉得书单不合理,可以去通过进行提交。讲师温铭,软件基金会主席,最佳实践作者。 想提升自己,还得多看书!多看书!多看书!下面是我收集到的一些PHP程序员应该看得书单及在线教程,自己也没有全部看完。共勉吧!当然,如果你有好的书想分享给大家的或者...

    jimhs 评论0 收藏0
  • ELSE 技术周刊(2017.10.16期)

    摘要:前端中的计算机领域的通常认为起源于。并对其主要内容作了自己的解读。搬到另一个地区会导致名气降低。年度报告,年最受欢迎的编程语言年上最流行的种编程语言及前十最火热的项目排行榜,分别由及登顶。技术周刊由小组出品,汇聚一周好文章,周刊原文。 showImg(https://segmentfault.com/img/bVWHC4?w=1000&h=710); 本期推荐 反击爬虫,前端工程师的脑...

    0xE7A38A 评论0 收藏0

发表评论

0条评论

jsdt

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<