40

定义几个使用不同过滤器返回相同形状数据的方法时,有什么更好的做法?显式方法名称或重载方法?

例如。如果我有一些产品并且我正在从数据库中提取

显式方式:

public List<Product> GetProduct(int productId) {    // return a List    }
public List<Product> GetProductByCategory(Category category) {    // return a List    }
public List<Product> GetProductByName(string Name ) {    // return a List    }

重载方式:

public List<Product> GetProducts() {    // return a List of all products    }
public List<Product> GetProducts(Category category) { // return a List by Category }
public List<Product> GetProducts(string searchString ) { // return a List by search string }

我意识到您可能会遇到类似签名的问题,但如果您传递的是对象而不是基本类型(字符串、int、char、DateTime 等),这将不是问题。所以...重载方法以减少您拥有的方法数量并为了清楚起见是一个好主意,还是每个以不同方式过滤数据的方法都应该具有不同的方法名称

4

16 回答 16

47

是的,重载很容易被过度使用。

我发现确定是否需要重载的关键是考虑受众——不是编译器,而是维护程序员,他们将在数周/数月/数年内出现并且必须了解代码是什么试图达到。

像 GetProducts() 这样的简单方法名称是清晰易懂的,但它确实留下了很多未说明的地方。

在许多情况下,如果传递给 GetProducts() 的参数命名良好,维护人员将能够计算出重载的作用——但这依赖于使用时良好的命名规则,而您无法强制执行。您可以强制执行的是他们正在调用的方法的名称。

我遵循的准则是仅在方法可互换时才重载方法——如果它们做同样的事情。这样,我不介意我的班级的消费者调用哪个版本,因为它们是等价的。

为了说明,我很乐意为 DeleteFile() 方法使用重载:

void DeleteFile(string filePath);
void DeleteFile(FileInfo file);
void DeleteFile(DirectoryInfo directory, string fileName);

但是,对于您的示例,我将使用单独的名称:

public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByName(string Name ) {...}

拥有全名可以让维护人员(很可能是我)的代码更加明确。它避免了签名冲突的问题:

// No collisions, even though both methods take int parameters
public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesByDepartment(int departmentId);

还有机会为每个目的引入重载:

// Examples for GetEmployees

public IList<Employee> GetEmployeesBySupervisor(int supervisorId);
public IList<Employee> GetEmployeesBySupervisor(Supervisor supervisor);
public IList<Employee> GetEmployeesBySupervisor(Person supervisor);

public IList<Employee> GetEmployeesByDepartment(int departmentId);
public IList<Employee> GetEmployeesByDepartment(Department department);

// Examples for GetProduct

public IList<Product> GetProductById(int productId) {...}
public IList<Product> GetProductById(params int[] productId) {...}

public IList<Product> GetProductByCategory(Category category) {...}
public IList<Product> GetProductByCategory(IEnumerable<Category> category) {...}
public IList<Product> GetProductByCategory(params Category[] category) {...}

阅读的代码比编写的要多得多——即使您在最初签入源代码控制后再也没有回到代码中,您仍然会在编写代码时阅读该行代码几十次下面的代码。

最后,除非您正在编写一次性代码,否则您需要允许其他人从其他语言调用您的代码。似乎大多数业务系统最终都停留在生产中,远远超过了它们的使用日期。可能是在 2016 年使用您的课程的代码最终是用 VB.NET、C# 6.0、F# 或尚未发明的全新的东西编写的。可能是该语言不支持重载。

于 2008-10-30T09:05:57.240 回答
18

据我所知,你不会有更少的方法,只有更少的名字。我通常更喜欢命名的重载方法系统,但我认为只要您很好地注释和记录您的代码(无论哪种情况都应该这样做),我认为它并没有太大的区别。

于 2008-10-29T20:09:29.400 回答
16

你能过度使用它吗?嗯,是的,这是真的。

但是,您给出的示例是何时使用方法重载的完美示例。它们都执行相同的功能,为什么仅仅因为您向它们传递不同的类型而给它们不同的名称。

主要规则是做最清晰、最容易理解的事情。不要仅仅为了巧妙或聪明而使用重载,在有意义的时候使用。其他开发人员也可能正在处理此代码。您希望让他们尽可能轻松地获取和理解代码,并能够在不实现错误的情况下实现更改。

于 2008-10-29T20:13:27.533 回答
13

我喜欢重载我的方法,以便稍后在智能感知中我没有一百万个相同的方法。对我来说,让它重载而不是让它多次命名似乎更合乎逻辑。

于 2008-10-29T20:10:51.830 回答
5

您可能需要一些项目范围的标准。就个人而言,我发现重载的方法更容易阅读。如果你有 IDE 支持,那就去吧。

于 2008-10-29T20:10:12.097 回答
5

您可能会考虑的一件事是,您不能将重载方法公开为 WCF Web 服务中的操作协定。因此,如果您认为您可能需要这样做,这将是使用不同方法名称的一个论据。

不同方法名称的另一个论点是,使用智能感知可能更容易发现它们。

但是这两种选择都有利有弊——所有的设计都是权衡取舍。

于 2008-10-29T20:23:15.127 回答
5

重载的目的是为了减轻使用您的代码的人的学习曲线......并允许您使用命名方案来告知用户该方法的作用。

如果您有十个不同的方法都返回一个员工集合,然后生成十个不同的名称,(特别是如果它们以不同的字母开头!)会使它们在用户的智能感知下拉列表中显示为多个条目,从而延长下拉,并隐藏所有返回员工集合的十个方法集与您的类中的任何其他方法之间的区别......

想想 .Net 框架已经为构造函数和索引器强制执行的内容......它们都被迫具有相同的名称,您只能通过重载它们来创建多个......

如果您超载它们,它们将全部显示为一个,它们的不同签名和评论放在一边。

如果它们执行不同或不相关的功能,则不应重载两个方法...

至于当您想按类型重载具有相同签名的两个方法时可能存在的混淆,如

public List<Employee> GetEmployees(int supervisorId);
public List<Employee> GetEmployees(int departmentId); // Not Allowed !!

好吧,您可以创建单独的类型作为有问题的核心类型的包装器来区分签名。

  public struct EmployeeId 
  { 
      private int empId;
      public int EmployeeId { get { return empId; } set { empId = value; } }
      public EmployeeId(int employeId) { empId = employeeId; }
  }

  public struct DepartmentId 
  {
   // analogous content
  }

 // Now it's fine, as the parameters are defined as distinct types...
 public List<Employee> GetEmployees(EmployeeId supervisorId);
 public List<Employee> GetEmployees(DepartmentId  departmentId);
于 2008-10-29T21:24:59.123 回答
3

简要浏览一下框架应该会让您相信,大量的重载是一种公认​​的状态。面对无数的重载,Microsoft 框架设计指南(Kwalina 和 Abrams,2006 年)的第 5.1.1 节直接解决了可用性重载的设计。以下是该部分的简要介绍:

  • 尝试使用描述性参数名称来指示较短重载使用的默认值。

  • 避免在重载中任意改变参数名称。

  • 避免重载成员中参数的顺序不一致。

  • 使最长的重载虚拟化(如果需要可扩展性)。较短的重载应该简单地调用较长的重载。

  • 不要使用reforout参数来重载成员。

  • 允许null为可选参数传递。

  • 使用成员重载,而不是使用默认参数定义成员。

于 2008-10-29T22:00:50.000 回答
2

另一种选择是使用查询对象来构建“WHERE 子句”。所以你只有一种这样的方法:

public List<Product> GetProducts(Query query)

Query 对象包含以面向对象的方式表达的条件。GetProducts 通过“解析” Query 对象来获取查询。

http://martinfowler.com/eaaCatalog/queryObject.html

于 2008-10-29T20:09:47.933 回答
2

当方法的参数只有细微差别时,我已经看到过度使用重载。例如:

public List<Product> GetProduct(int productId) { // return a List  }
public List<Product> GetProduct(int productId, int ownerId ) { // return a List  }
public List<Product> GetProduct(int productId, int vendorId, boolean printInvoice) { // return a List  }

在我的小例子中,很快就不清楚第二个int参数应该是所有者还是客户 ID。

于 2008-10-29T20:23:04.643 回答
1

是的,你可以过度使用它,但是这里有另一个概念可以帮助控制它的使用......

如果您使用的是 .Net 3.5+ 并且需要应用多个过滤器,您可能最好使用 IQueryable 和链接,即

GetQuery<Type>().ApplyCategoryFilter(category).ApplyProductNameFilter(productName);

这样,您就可以在任何需要的地方反复重用过滤逻辑。

public static IQueryable<T> ApplyXYZFilter(this IQueryable<T> query, string filter)
{
     return query.Where(XYZ => XYZ == filter);
} 
于 2008-10-29T20:18:57.883 回答
1

您可以根据需要尽可能多地使用重载。同样从最佳实践的角度来看,如果您尝试对数据执行相同的“操作”(整体上),建议您使用重载。例如 getProduct()

此外,如果您看到 Java API,重载无处不在。你不会找到比这更大的认可。

于 2008-10-29T21:49:57.480 回答
1

重载是理想的多态行为。它可以帮助人类程序员记住方法名称。如果显式与类型参数是多余的,那就不好了。如果类型参数不暗示方法正在做什么,那么显式开始有意义。

在您的示例中, getProductByName 是明确可能有意义的唯一情况,因为您可能希望通过其他字符串获取产品。这个问题是由原始类型的歧义引起的;getProduct(Name n) 在某些情况下可能是更好的重载解决方案。

于 2008-10-29T22:33:40.653 回答
0

是的,你可以过度使用它。在您的示例中,似乎第一个和第三个可能会返回一个项目,而第二个会返回几个。如果这是正确的,那么我会调用第一个和第三个 GetProduct 和第二个 GetProducts 或 GetProductList

如果不是这种情况并且所有三个都返回几个(如果您将它传递给 productID 5,它会返回 productid 中包含 5 的任何项目,或者返回名称中包含字符串参数的任何项目),那么我将调用所有三个 GetProducts或 GetProductList 并覆盖所有这些。

无论如何,名称应该反映函数的作用,因此当它返回产品列表时调用它 GetProduct(单数)并不是一个好的函数名称。明示

于 2008-10-30T13:33:03.130 回答
0

我是“显式”方式的忠实拥护者:给每个函数一个不同的名称。我什至将一些Add(...)过去有很多功能的代码重构为AddRecord(const Record&)AddCell(const Cell&)等。

我认为这有助于避免一些混淆、无意的强制转换(至少在 C++ 中)和编译器警告,并且提高了清晰度。

也许在某些情况下,您需要其他策略。我还没有遇到过。

于 2009-01-14T07:42:27.220 回答
0

怎么样

public IList<Product> GetProducts() { /* Return all. */}

public IList<Product> GetProductBy(int productId) {...}
public IList<Product> GetProductBy(Category category) {...}
public IList<Product> GetProductBy(string Name ) {...}

等等?

于 2012-05-17T11:47:00.967 回答