【问题标题】: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 发布您的实际工作代码。

标签: java spring inheritance design-patterns interface


【解决方案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() 方法)。
  • 这点让我很困惑,因为我没有足够的经验。那么,您能否将这两种方法添加为更新?我会非常感激并从这样的例子中学到很多东西。提前致谢。
  • 请回复?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-21
  • 1970-01-01
  • 2011-03-03
  • 1970-01-01
  • 2019-11-15
相关资源
最近更新 更多