Перегрузка метода.Можно ли злоупотреблять этим?

StackOverflow https://stackoverflow.com/questions/248222

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

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

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 и т. д.), это будет меньшей проблемой.Так...это хорошая идея перегрузить метод чтобы уменьшить количество имеющихся у вас методов и для ясности, или должен каждый метод который фильтрует данные по-другому иметь другое имя метода?

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

Подумайте о том, что уже применяется платформой .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);

Другой вариант заключается в использовании объекта Query для создания " WHERE Clause " ;. Таким образом, у вас будет только один метод, подобный этому:

public List<Product> GetProducts(Query query)

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

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

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

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

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

  • ДЕЛАТЬ попробуйте использовать описательные имена параметров, чтобы указать значения по умолчанию, используемые в более коротких перегрузках.

  • ИЗБЕГАТЬ произвольное изменение имен параметров в перегрузках.

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

  • ДЕЛАТЬ сделайте виртуальной только самую длинную перегрузку (если требуется расширяемость).Более короткие перегрузки должны просто вызывать более длинную перегрузку.

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

  • ДЕЛАТЬ позволять null для передачи в качестве необязательных аргументов.

  • ДЕЛАТЬ используйте перегрузку членов вместо определения членов с аргументами по умолчанию.

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

Если вы используете .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);
} 

Вы можете использовать Перегрузку столько, сколько хотите. Также с точки зрения передового опыта, рекомендуется использовать перегрузку, если вы пытаетесь выполнить ту же «операцию» " (целостно) на данных. Например. getProduct ()

Кроме того, если вы видите Java API, перегрузка везде. Вы не найдете большего одобрения, чем это.

Перегрузка - желательное полиморфное поведение. Это помогает программисту запомнить имя метода. Если явный избыточен с параметром типа, то это плохо. Если параметр типа не подразумевает того, что делает метод, тогда явное начало иметь смысл.

В вашем примере getProductByName - единственный случай, когда явное объяснение может иметь смысл, поскольку вы можете получить продукт по какой-либо другой строке. Эта проблема была вызвана неоднозначностью примитивных типов; getProduct (Name n) может быть лучшим решением для перегрузки в некоторых случаях.

да, вы можете злоупотреблять этим. В вашем примере может показаться, что первый и третий, вероятно, вернут один элемент, а второй вернет несколько. Если это правильно, то я бы назвал первый и третий GetProduct, а второй GetProducts или GetProductList

если это не так, и все три возвращают несколько (как если бы вы передали ему productID 5, он возвращает любые элементы с 5 в productid или возвращает любые элементы с строковым параметром в его имени), тогда я бы вызвал все три GetProducts или GetProductList и переопределить их все.

В любом случае имя должно отражать то, что делает функция, поэтому вызов его GetProduct (в единственном числе), когда он возвращает список продуктов, не дает правильного имени функции. IMNSHO

Я - полный поклонник "явного" способ: дать каждой функции другое имя. Я даже переделал код, в котором в прошлом было множество функций Add (...) , на AddRecord (const Record & amp;) , AddCell (const Ячейка & amp;) и т. Д.

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

Возможно, в некоторых случаях вам нужна другая стратегия. Я еще не сталкивался с этим.

Как насчет

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

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

И так далее?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top