Для современных языков это просто синтаксический сахар; в совершенно не зависящем от языка виде, это нечто большее.
Ранее в этом ответе просто говорилось, что это больше, чем синтаксический сахар, но, если вы увидите в комментариях, Фалько поднял вопрос о том, что есть одна часть загадки, которая, по-видимому, отсутствует во всех современных языках; они не смешивают перегрузку методов с динамическим определением того, какую функцию вызывать на одном шаге. Это будет уточнено позже.
Вот почему это должно быть больше.
Рассмотрим язык, который поддерживает как перегрузку метода, так и нетипизированные переменные. Вы можете иметь следующие прототипы методов:
bool someFunction(int arg);
bool someFunction(string arg);
В некоторых языках вы, вероятно, будете вынуждены знать во время компиляции, какой из них будет вызываться заданной строкой кода. Но в некоторых языках не все переменные типизированы (или все они неявно типизированы как Object
или что угодно), поэтому представьте себе создание словаря, ключи которого сопоставляются со значениями различных типов:
dict roomNumber; // some hotels use numbers, some use letters, and some use
// alphanumerical strings. In some languages, built-in dictionary
// types automatically use untyped values for their keys to map to,
// so it makes more sense then to allow for both ints and strings in
// your code.
Что же делать, если вы хотите подать заявку someFunction
на один из этих номеров? Вы называете это:
someFunction(roomNumber[someSortOfKey]);
Является ли someFunction(int)
называется, или someFunction(string)
называется? Здесь вы видите один пример, где это не полностью ортогональные методы, особенно в языках более высокого уровня. Язык должен выяснить - во время выполнения - какой из них вызывать, поэтому он все равно должен рассматривать их как, по крайней мере, один и тот же метод.
Почему бы просто не использовать шаблоны? Почему бы просто не использовать нетипизированный аргумент?
Гибкость и более тонкий контроль. Иногда лучше использовать шаблоны / нетипизированные аргументы, но иногда это не так.
Вы должны подумать о случаях, когда, например, у вас может быть две сигнатуры метода, каждая из которых принимает аргументы int
a и a string
, но порядок в каждой сигнатуре различен. У вас вполне может быть веская причина для этого, так как реализация каждой подписи может делать в основном одно и то же, но только с немного другим поворотом; регистрация может быть другой, например. Или даже если они делают одну и ту же вещь, вы можете автоматически получать определенную информацию только из того порядка, в котором были указаны аргументы. Технически вы могли бы просто использовать операторы псевдопереключателя, чтобы определить тип каждого из переданных аргументов, но это может привести к путанице.
Так что это следующий пример плохой практики программирования?
bool stringIsTrue(int arg)
{
if (arg.toString() == "0")
{
return false;
}
else
{
return true;
}
}
bool stringIsTrue(Object arg)
{
if (arg.toString() == "0")
{
return false;
}
else
{
return true;
}
}
bool stringIsTrue(string arg)
{
if (arg == "0")
{
return false;
}
else
{
return true;
}
}
Да по большому счету. В этом конкретном примере это может помешать кому-то попытаться применить это к определенным примитивным типам и получить неожиданное поведение (что может быть хорошо); но давайте просто предположим, что я сократил приведенный выше код и что у вас фактически есть перегрузки для всех примитивных типов, а также для Object
s. Тогда следующий фрагмент кода действительно более уместен:
bool stringIsTrue(untyped arg)
{
if (arg.toString() == "0")
{
return false;
}
else
{
return true;
}
}
Но что, если вам нужно использовать это только для int
s и string
s, и что, если вы хотите, чтобы он возвращал true, основываясь на более простых или более сложных условиях соответственно? Тогда у вас есть веская причина использовать перегрузку:
bool appearsToBeFirstFloor(int arg)
{
if (arg.digitAt(0) == 1)
{
return true;
}
else
{
return false;
}
}
bool appearsToBeFirstFloor(string arg)
{
string firstCharacter = arg.characterAt(0);
if (firstCharacter.isDigit())
{
return appearsToBeFirstFloor(int(firstCharacter));
}
else if (firstCharacter.toUpper() == "A")
{
return true;
}
else
{
return false;
}
}
Но почему бы просто не дать этим функциям два разных имени? Вы все еще обладаете таким же количеством мелкозернистого контроля, не так ли?
Потому что, как указывалось ранее, некоторые отели используют цифры, некоторые используют буквы, а некоторые используют комбинацию цифр и букв:
appearsToBeFirstFloor(roomNumber[someSortOfKey]);
// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called
Это все еще не совсем тот код, который я использовал бы в реальной жизни, но он должен проиллюстрировать то, что я делаю просто отлично.
Но ... Вот почему это не более чем синтаксический сахар в современных языках.
Falco поднял вопрос в комментариях о том, что современные языки в основном не смешивают перегрузку методов и динамический выбор функций в пределах одного шага. Ранее я понимал, что некоторые языки работают, так как вы могли перегрузить appearsToBeFirstFloor
в приведенном выше примере, а затем язык во время выполнения будет определять, какую версию функции вызывать, в зависимости от значения времени выполнения нетипизированной переменной. Эта путаница частично возникла из-за работы с языками ECMA-типов, такими как ActionScript 3.0, в которых вы можете легко рандомизировать, какая функция вызывается для определенной строки кода во время выполнения.
Как вы, возможно, знаете, ActionScript 3 не поддерживает перегрузку методов. Что касается VB.NET, вы можете объявлять и устанавливать переменные без явного присвоения типа, но когда вы пытаетесь передать эти переменные в качестве аргументов перегруженным методам, он все равно не хочет читать значение времени выполнения, чтобы определить, какой метод вызывать; вместо этого он хочет найти метод с аргументами типа Object
или без типа или что-то подобное. Таким образом, приведенный выше пример int
против string
не будет работать и на этом языке. С ++ имеет аналогичные проблемы, например, когда вы используете что-то вроде указателя void или какой-то другой механизм, подобный этому, он все равно требует, чтобы вы вручную устраняли неоднозначность типа во время компиляции.
Так как первый заголовок говорит ...
Для современных языков это просто синтаксический сахар; в совершенно не зависящем от языка виде, это нечто большее. Делая перегрузку методов более полезной и актуальной, как в приведенном выше примере, на самом деле может быть хорошей функцией для добавления к существующему языку (как это было неявно запрошено для AS3), или она также может служить одним из множества фундаментальных элементов создание нового процедурного / объектно-ориентированного языка.