Method Overloading. Can you overuse it?
Asked Answered
D

16

41

What's better practice when defining several methods that return the same shape of data with different filters? Explicit method names or overloaded methods?

For example. If I have some Products and I'm pulling from a database

explicit way:

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    }

overloaded way:

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 }

I realize you may get into a problem with similar signatures, but if you're passing objects instead of base types (string, int, char, DateTime, etc) this will be less of an issue. So... is it a good idea to overload a method to reduce the number of methods you have and for clarity, or should each method that filters the data a different way have a different method name?

Dejection answered 29/10, 2008 at 20:6 Comment(1)
In the blog post of mine (The Hidden Dangers of Method Overloading) I explain that method overloading may negatively impact code quality, because maintaining two or more implementations under the same name gives rise to concealed semantics, which inevitably result in misunderstandings and functional defects.Explication
E
49

Yes, overloading can easily be overused.

I've found that the key to working out whether an overload is warranted or not is to consider the audience - not the compiler, but the maintenance programmer who will be coming along in weeks/months/years and has to understand what the code is trying to achieve.

A simple method name like GetProducts() is clear and understandable, but it does leave a lot unsaid.

In many cases, if the parameter passed to GetProducts() are well named, the maintenance guy will be able to work out what the overload does - but that's relying on good naming discipline at the point of use, which you can't enforce. What you can enforce is the name of the method they're calling.

The guideline that I follow is to only overload methods if they are interchangable - if they do the same thing. That way, I don't mind which version the consumer of my class invokes, as they're equivalent.

To illustrate, I'd happily use overloads for a DeleteFile() method:

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

However, for your examples, I'd use separate names:

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

Having the full names makes the code more explicit for the maintenance guy (who might well be me). It avoids issues with having signature collisions:

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

There is also the opportunity to introduce overloading for each purpose:

// 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) {...}

Code is read a lot more than it is written - even if you never come back to the code after the initial check in to source control, you're still going to be reading that line of code a couple of dozen times while you write the code that follows.

Lastly, unless you're writing throwaway code, you need to allow for other people calling your code from other languages. It seems that most business systems end up staying in production well past their use by date. It may be that the code that consumes your class in 2016 ends up being written in VB.NET, C# 6.0, F# or something completely new that's not been invented yet. It may be that the language doesn't support overloads.

Eskilstuna answered 30/10, 2008 at 9:5 Comment(3)
+1 excelent advice, agree wholeheartedly. Method overloading without a real need is evil. I'd add that (in Java at least) it's even more evil in settersArdeha
As I mentioned, we should make logical groups of methods GetEntityByFilter1, GetEntityByFilter2,... and each method can be overloaded. DO NOT use GetEntity with overloading for all filters. Isn't it?Grimona
The key point I was making is that meaningful names are very important. If I came across methods like GetEntityByFilter1() and GetEntityByFilter2() during a code review you'd be lucky to retain commit rights.Eskilstuna
E
18

As far as I can tell, you won't have fewer methods, just fewer names. I generally prefer the overloaded method system of naming, but I don't think it really makes much difference as long as you comment and document your code well (which you should do in either case).

Eastwardly answered 29/10, 2008 at 20:9 Comment(0)
M
16

Can you overuse it? well, yes, that is true.

However, the examples you've given are perfect examples of when to use method overloading. They all perform the same function, why give them different names just because you're passing different types to them.

The main rule, is doing the clearest, easiest to understand thing. Don't use overloading just to be slick or clever, do it when it makes sense. Other developers might be working on this code as well. You want to make it as easy as possible for them to pick up and understand the code and be able to implement changes without implementing bugs.

Marlite answered 29/10, 2008 at 20:13 Comment(0)
D
13

I like overloading my methods so that later on in the intellisense I don't have a million of the same method. And it seems more logical to me to have it just overloaded instead of having it named differently a dozen times.

Deliberate answered 29/10, 2008 at 20:10 Comment(0)
H
5

You probably need some project-wide standards. Personally, I find the overloaded methods much easier to read. If you have the IDE support, go for it.

Hall answered 29/10, 2008 at 20:10 Comment(0)
P
5

One thing you may consider is that you can't expose overloaded methods as operation contracts in a WCF Web Service. So if you think you may ever need to do this, it would be an argument for using different method names.

Another argument for different method names is that they may be more easily discoverable using intellisense.

But there are pros and cons for either choice - all design is trade-off.

Popp answered 29/10, 2008 at 20:23 Comment(0)
H
5

The point of overloading to to ease the learning curve of someone using your code... and to allow you to use naming schemes that inform the user as to what the method does.

If you have ten different methods that all return a collection of employees, Then generating ten different names, (Especially if they start with different letters!) causes them to appear as multiple entries in your users' intellisense drop down, extending the length of the drop down, and hiding the distinction between the set of ten methods that all return an employee collection, and whatever other methods are in your class...

Think about what is already enforced by the .Net framework for, say constructors, and indexers... They are all forced to have the same name, and you can only create multiples by overloading them...

If you overload them, they will all appear as one, with their disparate signatures and comments off to tthe side.

You should not overload two methods if they perform different or unrelated functions...

As to the confusion that can exist when you want to overload two methods with the same signature by type as in

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

Well you can create separate types as wrappers for the offending core type to distinguish the signatures..

  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);
Hofuf answered 29/10, 2008 at 21:24 Comment(2)
This is a GREAT Solution! Thanks! There are many good comments here. I want to pick more than one as a good solution! This is definitely one of them.Dejection
It can be in some cases, but it adds complexity. DON'T overuse this. Wrapping all of your int IDs in structs isn't a good solution...Shovelboard
M
3

A brief glance at the framework should convince you that numerous overloads is an accepted state of affairs. In the face of myriad overloads, the design of overloads for usability is directly addressed by section 5.1.1 of the Microsoft Framework Design Guidelines (Kwalina and Abrams, 2006). Here is a brief prècis of that section:

  • DO try to use descriptive parameter names to indicate the default used by shorter overloads.

  • AVOID arbitrarily varying parameter names in overloads.

  • AVOID being inconsistent in the ordering of parameters in overloaded members.

  • DO make only the longest overload virtual (if extensibility is required). Shorter overloads should simply call through to a longer overload.

  • DO NOT use ref or out parameters to overload members.

  • DO allow null to be passed for optional arguments.

  • DO use member overloading rather than defining members with default arguments.

Merras answered 29/10, 2008 at 22:0 Comment(0)
C
2

Another option is to use a Query object to build the "WHERE Clause". So you would have only one method like this:

public List<Product> GetProducts(Query query)

The Query object contains the condidition expressed in an Object Oriented way. The GetProducts obtain the query by "parsing" the Query object.

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

Clearheaded answered 29/10, 2008 at 20:9 Comment(5)
However, what would you do in the layer where you don't want to expose the fact that you're using SQL anymore? Or lets say you're querying the DOM?Mycobacterium
This is a classic antipattern.Rosenzweig
I think this would be difficult to implement with an Object Store like LinQ though. But I am curious.Dejection
The Query object is not tied to SQL or to XML DOM, it is a way to build a query in an object oriented way that can be translated in SQL or in any other language. Take a look here: martinfowler.com/eaaCatalog/queryObject.htmlClearheaded
The clean architecture template by ardalis follows this query patternStereometry
A
2

I have seen overloading overused when you have only subtle differences in the arguments to the method. For example:

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  }

In my small example, it quickly becomes unclear if the second int argument should be the owner or customer id.

Archivist answered 29/10, 2008 at 20:23 Comment(3)
and that is why you have to put very clear comments (or Javadoc, if this is Java, which it appears to be) into the code so that the next developer can figure it out.Eastwardly
THis is true, and it what I was trying to avoid. This is why I figure I need to send Objects instead of CategoryIds...Dejection
No, just be very careful that the code and comments agree... and make sure the comments are very clear...Eastwardly
R
1

Yes you can overuse it, however here is another concept which could help keep the usage of it under control ...

If you are using .Net 3.5+ and need to apply multiple filters you are probably better to use IQueryable and chaining i.e.

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

That way you can reuse the filtering logic over and over whereever you need it.

public static IQueryable<T> ApplyXYZFilter(this IQueryable<T> query, string filter)
{
     return query.Where(XYZ => XYZ == filter);
} 
Ronen answered 29/10, 2008 at 20:18 Comment(2)
This is fascinating. In my implementation, I have Articles, ArticleCategory, ArticleSubCategory etc in Linq DataContext Class. Each method currently called a different Stored Proc. I really need to dig into the IQueryable<T> interface to understand it.Dejection
The deferred execution of Linq is the key to how this works :) It's taken me a while to get my head around IQueryable, Generics and extension methods but I have to say I enjoy programming more than ever now.Ronen
A
1

You can use Overloading as much as you want. From the best practices point of view as well, it is recommended that you use Overloading if you are trying to perform the same "operation" (holistically) on the data. E.g. getProduct()

Also, if you see Java API, Overloading is everywhere. You wouldn't find a bigger endorsement than that.

Alpheus answered 29/10, 2008 at 21:49 Comment(0)
V
1

Overloading is desirable polymorphic behavior. It helps the human programmer remember the method name. If explicit is redundant with the type parameter, then it is bad. If the type parameter does not imply what the method is doing, then explicit starts to make sense.

In your example, getProductByName is the only case where explicit might make sense, since you might want to get product by some other string. This problem was caused by the ambiguity of primitive types; getProduct(Name n) might be a better overload solution in some cases.

Vernation answered 29/10, 2008 at 22:33 Comment(0)
H
0

yes you can overuse it. In your example it would seem like the first and third would probably return a single item, where the second would return several. If that is correct, then I would call the first and third GetProduct and the second GetProducts or GetProductList

if this is not the case and all three return several (as in if you pass it productID 5, it returns any items with 5 in the productid, or returns any items with the string parameter in its name) then I would call all three GetProducts or GetProductList and override all of them.

In any event, the name should reflect what the function does, so calling it GetProduct (singular) when it returns a list of Products doesn't make a good function name. IMNSHO

Husch answered 30/10, 2008 at 13:33 Comment(0)
S
0

I'm a total fan of the "explicit" way: giving each function a different name. I've even refactored some code which had lots of Add(...) functions in the past, to AddRecord(const Record&), AddCell(const Cell&), etc.

I think this helps to avoid some confusions, inadvertent casts (in C++, at least) and compiler warnings, and it improves clarity.

Maybe in some cases you need the other strategy. I haven't encountered one yet.

Scutate answered 14/1, 2009 at 7:42 Comment(0)
F
0

How about

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

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

And so on?

Flintlock answered 17/5, 2012 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.