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

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

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

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

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

Ответ 1

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

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

Простое имя метода, например GetProducts(), понятно и понятно, но оно оставляет много недосказанности.

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

Подумайте о том, что уже реализовано инфраструктурой .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 fine, as the parameters are defined as distinct types...
 public List<Employee> GetEmployees(EmployeeId supervisorId);
 public List<Employee> GetEmployees(DepartmentId  departmentId);

Ответ 8

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

public List<Product> GetProducts(Query query)

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

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

Ответ 9

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

Если вы используете .Net 3.5+ и вам нужно применить несколько фильтров, вам, вероятно, лучше использовать IQueryable и chaining, т.е.

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

Таким образом, вы можете повторно использовать логику фильтрации много раз, когда вам это нужно.

public static IQueryable<T> ApplyXYZFilter(this IQueryable<T> query, string filter)
{
     return query.Where(XYZ => XYZ == filter);
} 

Ответ 10

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

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

Ответ 11

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

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

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

  • AVOID несовместим в упорядочении параметров в перегруженных элементах.

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

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

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

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

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

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

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

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

Ответ 15

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

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

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

Ответ 16

Как насчет

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

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

И так?