代码重复:我应该使用单独的方法还是构建器模式?

Posted

技术标签:

【中文标题】代码重复:我应该使用单独的方法还是构建器模式?【英文标题】:Code Duplication: Should I use a separate method or Builder Pattern? 【发布时间】:2021-12-06 02:30:44 【问题描述】:

我的 Java (Spring) 应用程序中有以下 2 种方法(为简洁起见省略了一些代码),我想减少这些方法中的重复块。至此,经过一番研究,有人提出了Builder Pattern,也有人提出了类似的Template Method。我也认为,我可以简单地创建 2 个单独的方法并将每个重复的代码块移动到这些方法中。

但是,我对这个问题没有经验,我想先请你遵循最正确的方法。那么,我应该如何减少以下2种方法中的代码重复?我也想使用继承,但我真的很困惑找到合适的方法。

不是:为了简洁起见,我省略了我的代码并使用了一个简单的代码:

@Override
public MultipartFile exportAaaaa() throws IOException 

    // repeated code block I
    workbook = new XSSFWorkbook();
    sheet = workbook.createSheet(TextBundleUtil.read(TITLE));
    rowCount = new AtomicInteger(0);    
    //

    // private block to this method
    final Page<Aaaaa> page = aaaaaService.findAll());

    // ...

    // repeated code block II
    outputFile = File.createTempFile(TextBundleUtil.read(TITLE), EXTENSION);
    try (FileOutputStream outputStream = new FileOutputStream(outputFile)) 
        workbook.write(outputStream);
     catch (IOException e) 
        LoggingUtils.error("Writing is failed ", e);
    
    final FileInputStream input = new FileInputStream(outputFile);

    final String fileName = TextBundleUtil.read(TITLE).concat(EXTENSION);
    return new MockMultipartFile(fileName,
            fileName, CONTENT_TYPE, IOUtils.toByteArray(input));
    //

@Override
public MultipartFile exportBbbbb() throws IOException 

    // repeated code block I
    workbook = new XSSFWorkbook();
    sheet = workbook.createSheet(TextBundleUtil.read(TITLE));
    rowCount = new AtomicInteger(0);    
    //

    // private block to this method
    final Page<Bbbbb> page = bbbbbService.findBy Uuid(uuid));

    // ...

    // repeated code block II
    outputFile = File.createTempFile(TextBundleUtil.read(TITLE), EXTENSION);
    try (FileOutputStream outputStream = new FileOutputStream(outputFile)) 
        workbook.write(outputStream);
     catch (IOException e) 
        LoggingUtils.error("Writing is failed ", e);
    
    final FileInputStream input = new FileInputStream(outputFile);

    final String fileName = TextBundleUtil.read(TITLE).concat(EXTENSION);
    return new MockMultipartFile(fileName,
            fileName, CONTENT_TYPE, IOUtils.toByteArray(input));
    //

【问题讨论】:

我建议您为code review 发布您的实际工作代码。 【参考方案1】:

我在这里看到,重复的代码是日志记录(或控制台打印)。 但是在这个例子中并不清楚 aServicebService 是做什么的?可能他们内部有不同的逻辑?正如我所看到的,他们返回了不同的结果。 但是如果A.classB.class是一个接口的实现,aServicebService也是一个接口的实现。这可能是一种方法。也许)

【讨论】:

非常感谢您的帮助。对不起,它被误解了,因此我发布了完整的代码。你能看看并更新你的答案吗? 奇科?有回复吗?【参考方案2】:

如果aaaaaServicebbbbbService共用一个接口,例如

interface PageService<T> 
    Page<T> findAll();

然后我们可以使用带有类型参数的单个方法。

private <T> MultipartFile export(Service<T> service) throws IOException 
    // repeated code block I
    final Page<T> page = service.findAll());
    // repeated code block II

我们可以将此方法声明为public,也可以保留以前的方法作为覆盖方法,这取决于您是否希望客户端处理/访问底层服务以及您是否可以更改@987654326指示的接口或超类@。

public MultipartFile exportAaaaa() throws IOException 
    // assuming that 'aaaaaService' is a class member
    return export(aaaaaService);


如果没有通用接口,我会简单地将通用代码块提取到单独的方法中。

private MultipartFile exportAaaaa() throws IOException 
    prepareWorkbook(/* parameters */);
    final Page<T> page = aaaaaService.findAll());
    constructOutput(/* parameters */);


private /* return type */ prepareWorkbook(/* parameters */) 
    // repeated code block I


private /* return type */ constructOutput(/* parameters */) 
    // repeated code block II

【讨论】:

非常感谢您的宝贵解释。但我关注的重点是重复的代码部分而不是服务。所以,忽略服务(假设他们完全做不相关的事情)并通过建议一个正确的方法来更新你的答案。我应该将 2 个重复的部分提取到 2 个单独的方法中并在这些 exportAaaaaexportBbbbb 方法中调用它们吗? @Jonathan 我在回答中提到了这两种选择。如果有一个通用接口,你可以使用它。但是,如果您不想依赖服务来履行特定合同,则可以选择我强调的第二种方法——将两个重复的部分提取到专用方法中。【参考方案3】:

看起来你的代码中的重复可以很容易地提取到单独的方法中:

private void doCodeBlockI() 
    // repeated code block I
    workbook = new XSSFWorkbook();
    sheet = workbook.createSheet(TextBundleUtil.read(TITLE));
    rowCount = new AtomicInteger(0);    
    

private MultipartFile doCodeBlockII() 
    // repeated code block II
    outputFile = File.createTempFile(TextBundleUtil.read(TITLE), 
    EXTENSION);
    try (FileOutputStream outputStream = new FileOutputStream(outputFile)) 
        workbook.write(outputStream);
     catch (IOException e) 
        LoggingUtils.error("Writing is failed ", e);
    
    final FileInputStream input = new FileInputStream(outputFile);

    final String fileName = TextBundleUtil.read(TITLE).concat(EXTENSION);
    return new MockMultipartFile(fileName,
            fileName, CONTENT_TYPE, IOUtils.toByteArray(input));
    

@Override
public MultipartFile exportAaaaa() throws IOException 
    doCodeBlockI();

    // private block to this method
    final Page<Aaaaa> page = aaaaaService.findAll();

    // ...

    return doCodeBlockII();


@Override
public MultipartFile exportBbbbb() throws IOException 
    doCodeBlockI();

    // private block to this method
    final Page<Bbbbb> page = bbbbbService.findByUuid(uuid);

    // ...

    return doCodeBlockII();

实现这一目标的另一种方法是使用抽象类:

public abstract class Exporter 

    public MultipartFile export() throws IOException 

        // repeated code block I
        workbook = new XSSFWorkbook();
        sheet = workbook.createSheet(TextBundleUtil.read(TITLE));
        rowCount = new AtomicInteger(0);    
        //

        // private block will be called inside the abstract method
        doPrivateBlock();
    
        // repeated code block II
        outputFile = File.createTempFile(TextBundleUtil.read(TITLE), EXTENSION);
        try (FileOutputStream outputStream = new FileOutputStream(outputFile)) 
            workbook.write(outputStream);
         catch (IOException e) 
            LoggingUtils.error("Writing is failed ", e);
        
        final FileInputStream input = new FileInputStream(outputFile);

        final String fileName = TextBundleUtil.read(TITLE).concat(EXTENSION);
        return new MockMultipartFile(fileName,
            fileName, CONTENT_TYPE, IOUtils.toByteArray(input));
        //
    

    protected abstract void doPrivateBlock();

然后将其扩展两次,实现doPrivateBlock()方法:

public class AaaaaExporter extends Exporter 

    AaaaaService aaaaaService; // need to instantiate this

    public AaaaaExporter() 

    @Override
    private void doPrivateBlock() 
        // private block to this method
        final Page<Aaaaa> page = aaaaaService.findAll());

        // ...
    


public class BbbbbExporter extends Exporter 

    BbbbbService bbbbbService; // need to instantiate this

    public BbbbbExporter() 

    @Override
    private void doPrivateBlock() 
        // private block to this method
            final Page<Bbbbb> page = bbbbbService.findByUuid(uuid));

        // ...
    

我忽略了代码中使用的共享字段(例如workbook),您需要将它们作为参数添加到 export(...) 方法或将它们作为字段添加到 @ 987654326@ 类并将它们与您的类一起实例化,这将取决于您的问题中未明确定义的范围。

【讨论】:

非常感谢,这似乎是一个适当且最简单的解决方案。另一方面,如果其他类也有这些doCodeBlockIdoCodeBlockII 呢?在这种情况下,我应该如何移动这些方法(doCodeBlockIdoCodeBlockII)并在此方法和其他导出类中使用它们?我应该将它们(doCodeBlockIdoCodeBlockII)设为静态方法并从此类调用吗?或者有没有必要使用继承? 这真的取决于你的具体需求,但是是的,一个帮助类可能会解决问题(你也可以根据需要传递服务作为参数,这些服务可以根据上下文有不同的行为(例如@ 987654333@ vs CloudPrinter vs DummyPrinter 都实现了IPrinterprint() 方法)。 这点让我很困惑,因为我没有足够的经验。那么,您能否将这两种方法添加为更新?我会非常感激并从这样的例子中学到很多东西。提前致谢。 请回复?

以上是关于代码重复:我应该使用单独的方法还是构建器模式?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Builder 模式时,我应该通过值还是可变引用来获取“self”?

在 PHP 中实现方法结果缓存的装饰器模式的最佳方法

用 JS 构建 JSON 的最佳方法 [重复]

什么是Scala等效于Java构建器模式?

Spring:使用构建器模式创建 bean

在界面构建器中使用约束