Перегрузка метода. Вы можете злоупотреблять этим? - PullRequest
36 голосов
/ 29 октября 2008

Что лучше использовать при определении нескольких методов, которые возвращают одну и ту же форму данных с разными фильтрами? Явные имена методов или перегруженные методы?

Например. Если у меня есть некоторые продукты, и я вытаскиваю из базы данных

явный способ:

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 }

Я понимаю, что вы можете столкнуться с проблемой подобных подписей , но если вы передаете объекты вместо базовых типов (string, int, char, DateTime и т. Д.), Это будет меньше проблем , Итак ... это хорошая идея перегрузить метод , чтобы уменьшить количество методов, которые у вас есть, и для ясности, или должен каждый метод , который фильтрует данные по-разному имеют другое имя метода ?

Ответы [ 16 ]

44 голосов
/ 30 октября 2008

Да, перегрузка может быть легко использована.

Я обнаружил, что ключом к определению того, оправдана ли перегрузка или нет, является учет аудитории - не компилятора, а программиста по обслуживанию, который придет через несколько недель / месяцев / лет и должен понять, что код пытается достичь.

Простое имя метода, такое как 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 # или что-то совершенно новое, еще не изобретенное. Возможно, язык не поддерживает перегрузки.

16 голосов
/ 29 октября 2008

Насколько я могу судить, у вас не будет меньше методов, просто меньше имен. Я обычно предпочитаю перегруженную систему именования методов, но я не думаю, что это действительно имеет большое значение, если вы хорошо комментируете и документируете свой код (что вы должны делать в любом случае).

15 голосов
/ 29 октября 2008

Можете ли вы злоупотреблять этим? ну да, это правда.

Однако приведенные вами примеры являются прекрасными примерами того, когда использовать перегрузку метода. Все они выполняют одну и ту же функцию, зачем давать им разные имена только потому, что вы передаете им разные типы.

Главное правило - делать самые ясные и простые для понимания вещи. Не используйте перегрузки только для того, чтобы быть умным или умным, делайте это, когда это имеет смысл. Над этим кодом могут работать и другие разработчики. Вы хотите, чтобы они как можно проще воспринимали и понимали код и могли вносить изменения без ошибок.

11 голосов
/ 29 октября 2008

Мне нравится перегружать мои методы, чтобы в дальнейшем в intellisense у меня не было миллиона таких же методов. И мне кажется более логичным, что его просто перегружают, а не называют его по-разному дюжину раз.

5 голосов
/ 29 октября 2008

Одна вещь, которую вы можете учитывать, это то, что вы не можете выставлять перегруженные методы как контракты операций в веб-службе WCF. Поэтому, если вы думаете, что вам когда-нибудь понадобится это сделать, это будет аргументом для использования разных имен методов.

Другим аргументом для разных имен методов является то, что их легче обнаружить с помощью intellisense.

Но есть плюсы и минусы для любого выбора - весь дизайн - компромисс.

4 голосов
/ 30 октября 2008

Точка перегрузки для облегчения кривой обучения того, кто использует ваш код ... и позволяет вам использовать схемы именования, которые информируют пользователя о том, что делает метод.

Если у вас есть десять различных методов, которые все возвращают коллекцию сотрудников, то генерация десяти различных имен (особенно если они начинаются с разных букв!) Заставляет их появляться в виде нескольких записей в раскрывающемся списке ваших пользователей, расширяя длина раскрывающегося списка и скрытие различия между набором из десяти методов, которые все возвращают коллекцию сотрудников, и какими бы то ни было другими методами в вашем классе ...

Подумайте о том, что уже применяется платформой .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);
4 голосов
/ 29 октября 2008

Возможно, вам нужны стандарты для всего проекта. Лично я считаю, что перегруженные методы намного легче читать. Если у вас есть поддержка IDE, сделайте это.

2 голосов
/ 29 октября 2008

Другой вариант - использовать объект Query для построения «предложения WHERE». Таким образом, у вас будет только один метод, подобный этому:

public List<Product> GetProducts(Query query)

Объект Query содержит условие, выраженное объектно-ориентированным способом. GetProducts получает запрос путем "анализа" объекта Query.

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

1 голос
/ 30 октября 2008

Краткий взгляд на структуру должен убедить вас, что многочисленные перегрузки являются общепринятым положением дел. Перед лицом множества перегрузок проектирование перегрузок для удобства использования напрямую рассматривается в разделе 5.1.1 Руководства по проектированию Microsoft Framework (Kwalina and Abrams, 2006). Вот краткая информация об этом разделе:

  • DO попытаться использовать описательные имена параметров, чтобы указать значение по умолчанию, используемое при более коротких перегрузках.

  • AVOID произвольно меняющиеся имена параметров в перегрузках.

  • ИЗБЕГАТЬ несовместимо при упорядочении параметров в перегруженных элементах.

  • DO создает только самую длинную виртуальную перегрузку (если требуется расширяемость). Короткие перегрузки должны просто вызывать более длительную перегрузку.

  • НЕ использовать параметры ref или out для перегрузки элементов.

  • DO позволяет передавать null для необязательных аргументов.

  • DO использовать перегрузку элементов вместо определения элементов с аргументами по умолчанию.

1 голос
/ 29 октября 2008

Я видел перегрузку, когда у вас есть только незначительные различия в аргументах метода. Например:

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 быть идентификатором владельца или клиента.

...