如何避免过多的方法重载?


16

我们的应用程序源代码中有很多地方,其中一个类有许多具有相同名称和不同参数的方法。这些方法始终具有“上一个”方法的所有参数,再加上一个。

这是经过长期发展(遗留代码)和这种想法(我相信)的结果:

有一个方法M做A,我需要做A +B。好吧,我知道...我将向M添加一个新参数,为此创建一个新方法,将代码从M移到新方法使用一个以上的参数,在那儿执行A + B,然后使用新参数的默认值从M调用新方法。

这是一个示例(类似于Java的语言):

class DocumentHome {

  (...)

  public Document createDocument(String name) {
    // just calls another method with default value of its parameter
    return createDocument(name, -1);
  }

  public Document createDocument(String name, int minPagesCount) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false);
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false, "");
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
    // here the real work gets done
    (...)
  }

  (...)
}

我觉得这是错误的。不仅我们不能一直这样添加新参数,而且由于方法之间的所有依赖关系,使得代码难以扩展/更改。

这里有几种方法可以更好地做到这一点:

  1. 介绍一个参数对象:

    class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    
  2. DocumentHome在调用之前将参数设置为对象createDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. 将工作分为不同的方法,并根据需要调用它们:

      @In
      DocumentHome dh = null;
    
      Document newDocument = dh.createDocument();
      dh.changeName(newDocument, "name");
      dh.addFirstBlankPage(newDocument);
      dh.changeMinPagesCount(new Document, 10);
    

我的问题:

  1. 所描述的问题真的有问题吗?
  2. 您如何看待建议的解决方案?根据您的经验,您更喜欢哪一个?
  3. 您能想到其他解决方案吗?

1
您针对的是哪种语言,或者只是一种语言?
Knerd

没有特定的语言,只是一般。随时显示其他语言的功能如何帮助您解决此问题。
2014年

就像我在这里说的那样,programms.stackexchange.com / questions / 235096 /… C#和C ++具有某些功能。
Knerd'4

很明显,这个问题适用于支持这种方法重载的每种语言。
Doc Brown

1
@DocBrown可以,但是并非每种语言都支持相同的替代方法;)
Knerd 2014年

Answers:


20

也许尝试构建器模式?(注意:Google结果相当随机:)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

我无法给出为什么我偏爱builder而不是您提供的选项的完整摘要,但是您发现了很多代码存在很大的问题。如果您认为一个方法需要两个以上的参数,则可能是代码结构错误(有些人会争论一个!)。

params对象的问题是(除非您创建的对象在某种程度上是真实的),您只是将问题推进了一个层次,最后得到了一系列不相关的参数,形成了“对象”。

在我看来,您的其他尝试就像有人在尝试构建器模式,但还不太到那里:)


谢谢。您的答案可以是解决方案编号。4,是的。我正在以这种方式寻找更多的答案:“以我的经验,这会导致...并且可以解决...。” 或“当我在同事的代码中看到此内容时,建议他...代替。”
Ytus 2014年

我不知道...在我看来,您只是在解决问题。举例来说,如果我可以在应用程序的不同部分上构建文档,则最好组织和测试此文档,以在单独的类上隔离此文档结构(例如使用DocumentoFactory)。在builder不同的地方,很难控制Document结构的将来更改(例如,向示例添加新的必填字段),并在测试上添加额外的样板代码,以仅满足使用builder的类上的document builder必需。
Dherik '19

1

使用参数对象是避免方法(过度)重载的好方法:

  • 它清理代码
  • 将数据与功能分开
  • 使代码更易于维护

但是,我不会太过分。

这里有一个过载,这不是一件坏事。编程语言支持它,因此请充分利用它。

我没有意识到构建器模式,但是在某些场合“偶然”使用了它。同样适用于此处:不要过度使用。您示例中的代码将从中受益,但是花很多时间为每个具有单个重载方法的方法实现它不是很有效。

只是我的2美分。


0

老实说,我认为代码没有什么大问题。在C#和C ++中,您可以使用可选参数,这是一种选择,但是据我所知Java不支持这种参数。

在C#中,您可以将所有重载设置为私有,并使用一种带有可选参数的方法将其公开。

为了回答您的问题,第2部分,我将使用参数对象,甚至是字典/ HashMap。

像这样:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

作为免责声明,我首先是C#和JavaScript程序员,然后是Java程序员。


4
它是一个解决方案,但我认为它不是一个很好的解决方案(至少,在预期类型安全的情况下,至少不是这样)。
Doc Brown

那就对了。由于这种情况,我喜欢重载方法或可选参数。
Knerd 2014年

2
使用字典作为参数是减少方法的表观参数数量的简便方法,但是却掩盖了方法的实际依赖性。这迫使调用者在其他地方寻找字典中确切需要的内容,无论是在注释中,对该方法的其他调用还是该方法实现本身。
迈克·帕特里奇

仅将字典用于真实 可选的参数,因此基本用例无需阅读过多文档。
user949300 '18

0

我认为这是构建器模式的不错选择。当您要创建相同类型但具有不同表示形式的对象时,构建器模式非常有用。

在您的情况下,我将使用以下方法构建一个:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

然后,您可以通过以下方式使用构建器:

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

附带一提,我不介意几个简单的重载-.NET框架到底在HTML帮助程序中使用它们的地方。但是,如果必须向每个方法传递两个以上的参数,我将重新评估我在做什么。


0

我认为 builder解决方案可以在大多数情况下使用,但是在更复杂的情况下,您的构建器也将很复杂设置,因为很容易在方法的顺序,是否需要公开哪些方法方面犯一些错误。等等。因此,我们许多人将更喜欢最简单的解决方案。

如果仅创建一个简单的构建器来构建文档并将此代码分布在应用程序的不同部分(类)上,则将很难:

  • 组织:您将有很多课程以不同的方式构建文档
  • 保持:文档实例化的任何更改(例如添加新的强制性文件)都将导致您进入a 弹枪外科手术
  • 测试:如果您正在测试用于构建文档的类,则将需要添加样板代码来满足文档实例化的需要。

但这不能回答OP问题。

替代过载

一些替代方案:

  • 重命名方法名:如果同样的方法名是创建一些混乱,尝试重命名方法来创建一个有意义的名字比createDocument,如:createLicenseDriveDocumentcreateDocumentWithOptionalFields等等。当然,这可能会导致你巨头方法名,所以这不是适用于所有情况的解决方案。
  • 使用静态方法。如果与上面的第一种方法相比,这种方法有点相似。您可以为每种情况使用一个有意义的名称,并通过上的静态方法实例化文档Document,例如:Document.createLicenseDriveDocument()
  • 创建一个通用的接口:您可以创建称为单一的方法createDocument(InterfaceDocument interfaceDocument)和创建不同的实现InterfaceDocument。例如:createDocument(new DocumentMinPagesCount("name"))。当然,对于每种情况,您都不需要一个实现,因为您可以在每个实现上创建多个构造函数,并将一些对该实现有意义的字段分组。这种模式称为伸缩构造函数

仅使用过载解决方案。即使有时是一个丑陋的解决方案,使用它也没有很多缺点。在那种情况下,我更喜欢在单独的类上使用重载方法,例如DocumentoFactory可以作为对需要创建文档的类的依赖项注入的重载方法。我可以组织和验证字段,而无需创建一个好的生成器,也不必在一个地方维护代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.