W przypadku współczesnych języków jest to po prostu cukier składniowy; w sposób całkowicie niezależny od języka, to coś więcej.
Wcześniej ta odpowiedź mówiła po prostu, że to coś więcej niż cukier syntaktyczny, ale jeśli zauważysz w komentarzach, Falco podniósł kwestię, że istnieje jedna część układanki, której brakuje współczesnym językom; nie łączą przeciążenia metody z dynamicznym określaniem, którą funkcję wywołać w tym samym kroku. Wyjaśnimy to później.
Oto dlaczego powinno być więcej.
Rozważ język, który obsługuje zarówno przeciążanie metod, jak i zmienne bez typów. Możesz mieć następujące prototypy metody:
bool someFunction(int arg);
bool someFunction(string arg);
W niektórych językach prawdopodobnie będziesz skłonny wiedzieć w czasie kompilacji, który z nich zostałby wywołany przez dany wiersz kodu. Ale w niektórych językach nie wszystkie zmienne są wpisywane (lub wszystkie są domyślnie wpisywane jako Object
lub cokolwiek innego), więc wyobraź sobie zbudowanie słownika, którego klucze odwzorowują wartości różnych typów:
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.
A co teraz, jeśli chcesz złożyć podanie someFunction
na jeden z tych numerów pokoi? Nazywasz to:
someFunction(roomNumber[someSortOfKey]);
Jest someFunction(int)
nazywany lub jest someFunction(string)
nazywany? Oto jeden przykład, w którym nie są to całkowicie ortogonalne metody, szczególnie w językach wyższego poziomu. Język musi ustalić - w czasie wykonywania - który z nich wywołać, więc nadal musi traktować je jako przynajmniej w pewnym stopniu tę samą metodę.
Dlaczego nie skorzystać z szablonów? Dlaczego nie użyć po prostu nietypowego argumentu?
Elastyczność i dokładniejsza kontrola. Czasem użycie szablonów / nietypowych argumentów jest lepszym podejściem, ale czasem nie.
Musisz pomyśleć o przypadkach, w których na przykład możesz mieć dwie sygnatury metod, z których każda przyjmuje argumenty int
i string
jako argument, ale gdzie kolejność jest inna w każdej sygnaturze. Być może masz dobry powód, aby to zrobić, ponieważ implementacja każdego podpisu może w dużej mierze zrobić to samo, ale z nieco innym zwrotem; rejestrowanie może być na przykład inne. Lub nawet jeśli robią to samo dokładnie, możesz być w stanie automatycznie zebrać pewne informacje tylko z kolejności, w której argumenty zostały określone. Technicznie rzecz biorąc, możesz po prostu użyć instrukcji pseudo-switch, aby określić typ każdego z przekazywanych argumentów, ale robi się to bałagan.
Czy ten kolejny przykład jest złą praktyką programistyczną?
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;
}
}
Tak, ogólnie rzecz biorąc. W tym konkretnym przykładzie może powstrzymać kogoś od prób zastosowania tego do pewnych prymitywnych typów i odzyskania nieoczekiwanego zachowania (co może być dobrą rzeczą); ale załóżmy, że skróciłem powyższy kod i że w rzeczywistości masz przeciążenia dla wszystkich typów pierwotnych, a także dla Object
s. Zatem ten następny fragment kodu jest naprawdę bardziej odpowiedni:
bool stringIsTrue(untyped arg)
{
if (arg.toString() == "0")
{
return false;
}
else
{
return true;
}
}
Ale co, jeśli musisz użyć tego tylko dla int
s i string
s, a co, jeśli chcesz, aby zwrócił prawdę na podstawie odpowiednio prostszych lub bardziej skomplikowanych warunków? Masz dobry powód, aby użyć przeciążenia:
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;
}
}
Ale hej, dlaczego nie nadać tym funkcjom dwóch różnych nazw? Nadal masz taką samą drobnoziarnistą kontrolę, prawda?
Ponieważ, jak wspomniano wcześniej, niektóre hotele używają liczb, niektóre używają liter, a niektóre używają kombinacji cyfr i liter:
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
To wciąż nie jest dokładnie ten sam kod, którego używałbym w prawdziwym życiu, ale powinien on ilustrować punkt, który robię dobrze.
Ale ... Oto dlaczego we współczesnych językach jest to cukier syntaktyczny.
Falco podniósł w komentarzach, że obecne języki w zasadzie nie łączą przeciążenia metod i dynamicznego wyboru funkcji w tym samym kroku. Sposób, w jaki wcześniej rozumiałem niektóre języki, był taki, że można przeciążać appearsToBeFirstFloor
w powyższym przykładzie, a następnie język określałby w czasie wykonywania, która wersja funkcji ma zostać wywołana, w zależności od wartości środowiska uruchomieniowego zmiennej bez typu. To zamieszanie częściowo wynikało z pracy z językami podobnymi do ECMA, takimi jak ActionScript 3.0, w których można z łatwością losować, która funkcja jest wywoływana w określonym wierszu kodu w czasie wykonywania.
Jak zapewne wiesz, ActionScript 3 nie obsługuje przeciążania metod. Jeśli chodzi o VB.NET, możesz deklarować i ustawiać zmienne bez jawnego przypisywania typu, ale kiedy próbujesz przekazać te zmienne jako argumenty do przeciążonych metod, nadal nie chce odczytać wartości środowiska wykonawczego w celu ustalenia, którą metodę wywołać; zamiast tego chce znaleźć metodę z argumentami typu Object
lub bez typu albo coś podobnego. Więc w int
porównaniu string
powyższym przykładzie nie będzie działać w tym języku, albo. C ++ ma podobne problemy, ponieważ gdy używasz czegoś takiego jak wskaźnik pustki lub jakiś inny podobny mechanizm, nadal wymaga ręcznego rozróżnienia typu w czasie kompilacji.
Tak jak mówi pierwszy nagłówek ...
W przypadku współczesnych języków jest to po prostu cukier składniowy; w sposób całkowicie niezależny od języka, to coś więcej. Zwiększenie użyteczności i przydatności przeciążania metod, tak jak w powyższym przykładzie, może być dobrą cechą do dodania do istniejącego języka (jak powszechnie domagano się domyślnie w AS3) lub może też służyć jako jeden z wielu różnych fundamentalnych filarów stworzenie nowego języka proceduralnego / obiektowego.