Inwersja zależności w OOP oznacza, że kodujesz w interfejsie, który jest następnie zapewniany przez implementację w obiekcie.
Języki obsługujące funkcje wyższego języka mogą często rozwiązać proste problemy z odwracaniem zależności, przekazując zachowanie jako funkcję zamiast obiektu, który implementuje interfejs w sensie OO.
W takich językach sygnatura funkcji może stać się interfejsem, a funkcja jest przekazywana zamiast tradycyjnego obiektu w celu zapewnienia pożądanego zachowania. Dobrym tego przykładem jest dziura w środkowym wzorze.
Pozwala osiągnąć ten sam wynik przy mniejszym kodzie i większej ekspresji, ponieważ nie trzeba implementować całej klasy, która jest zgodna z interfejsem (OOP), aby zapewnić pożądane zachowanie osoby wywołującej. Zamiast tego możesz po prostu przekazać prostą definicję funkcji. W skrócie: Kod jest często łatwiejszy w utrzymaniu, bardziej wyrazisty i bardziej elastyczny, gdy używa się funkcji wyższego rzędu.
Przykład w C #
Tradycyjne podejście:
public IEnumerable<Customer> FilterCustomers(IFilter<Customer> filter, IEnumerable<Customers> customers)
{
foreach(var customer in customers)
{
if(filter.Matches(customer))
{
yield return customer;
}
}
}
//now you've got to implement all these filters
class CustomerNameFilter : IFilter<Customer> /*...*/
class CustomerBirthdayFilter : IFilter<Customer> /*...*/
//the invocation looks like this
var filteredDataByName = FilterCustomers(new CustomerNameFilter("SomeName"), customers);
var filteredDataBybirthDay = FilterCustomers(new CustomerBirthdayFilter(SomeDate), customers);
Z funkcjami wyższego rzędu:
public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
foreach(var customer in customers)
{
if(filter(customer))
{
yield return customer;
}
}
}
Teraz implementacja i wywoływanie stają się mniej kłopotliwe. Nie musimy już dostarczać implementacji IFilter. Nie musimy już implementować klas dla filtrów.
var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);
Oczywiście może to już zrobić LinQ w C #. Użyłem tego przykładu, aby zilustrować, że łatwiej i bardziej elastycznie jest używać funkcji wyższego rzędu zamiast obiektów, które implementują interfejs.
IFilter<Customer>
nie jest wcale egzekwowaniem. Funkcja wyższego rzędu jest znacznie bardziej elastyczna, co jest dużą zaletą, a możliwość pisania ich w linii to kolejna ogromna korzyść. Lambdy są również znacznie łatwiejsze do przechwytywania zmiennych lokalnych.public delegate bool CustomerFilter(Customer customer)
. w czysto funkcjonalnych językach, takich jak haskell, typy aliasingu są banalne:type customerFilter = Customer -> Bool
Jeśli chcesz zmienić zachowanie funkcji
możesz przekazać inną funkcję
która implementuje zachowanie, które chcesz być inne.
„doThisWith” jest funkcją wyższego rzędu, ponieważ przyjmuje inną funkcję jako argument.
Na przykład możesz mieć
źródło
Krótka odpowiedź:
Wstrzykiwanie / odwracanie kontroli klasycznej wykorzystuje interfejsy klas jako symbole zastępcze dla funkcji zależnych. Ten interfejs jest implementowany przez klasę.
Zamiast interfejsu / implementacji klasy wiele zależności można łatwiej zaimplementować dzięki funkcji delegowania.
Przykład dla obu znajdziesz w c # w ioc-factory-pros-and-contras-for-interface-vers-delegates .
źródło
Porównaj to:
z:
Druga wersja to sposób, w jaki Java 8 redukuje kod bojlera (zapętlanie itp.) Poprzez udostępnienie funkcji wyższego rzędu,
filter
które pozwalają przekazać absolutne minimum (tj. Zależność do wstrzyknięcia - wyrażenie lambda).źródło
Piggy-backing off of LennyProgrammers example ...
Jedną z rzeczy, których pominięto w innych przykładach, jest to, że można użyć funkcji wyższego rzędu wraz z aplikacją funkcji częściowej (PFA) w celu powiązania (lub „wstrzyknięcia”) zależności do funkcji (poprzez listę argumentów) w celu utworzenia nowej funkcji.
Jeśli zamiast:
my (aby być konwencjonalnym w sposobie, w jaki zazwyczaj wykonuje się PFA), mamy niskopoziomową funkcję roboczą jako (zamiana porządku arg):
Następnie możemy częściowo zastosować doThisW ten sposób:
Co pozwala nam później korzystać z nowej funkcji w następujący sposób:
Lub nawet:
Zobacz także: https://ramdajs.com/docs/#partial
... a tak, sumatory / mnożniki są niewyobrażalnymi przykładami. Lepszym przykładem może być funkcja, która pobiera wiadomości i rejestruje je lub wysyła e-mailem w zależności od tego, co przekazała funkcja „konsumenta” jako zależność.
Rozszerzając ten pomysł, jeszcze dłuższe listy argumentów można stopniowo zawęzić do coraz bardziej wyspecjalizowanych funkcji z coraz krótszymi listami argumentów, i oczywiście każdą z tych funkcji można przekazać do innych funkcji jako zależności, które należy częściowo zastosować.
OOP jest fajny, jeśli potrzebujesz pakietu rzeczy z wieloma ściśle powiązanymi operacjami, ale zamienia się on w make-work, aby stworzyć grupę klas, z których każda ma jedną publiczną metodę „zrób to”, a la „Królestwo rzeczowników”.
źródło