Як інверсія залежності пов'язана з функціями вищого порядку?


41

Сьогодні я щойно бачив цю статтю, в якій описано актуальність принципу SOLID у розробці F #

F # та принципи дизайну - SOLID

І звертаючись до останнього - "Принцип інверсії залежності", автор сказав:

З функціональної точки зору ці поняття щодо контейнерів та ін'єкцій можна вирішити за допомогою простої функції вищого порядку або схеми "отвір середнього типу", яка вбудована прямо в мову.

Але він не пояснив це далі. Отже, моє питання полягає в тому, як інверсія залежності пов'язана з функціями вищого порядку?

Відповіді:


38

Інверсія залежності в OOP означає, що ви кодуєте інтерфейс, який надається реалізацією в об'єкті.

Мови, які підтримують функції вищої мови, часто можуть вирішувати прості проблеми з інверсією залежності, передаючи поведінку як функцію замість об'єкта, який реалізує інтерфейс у OO-сенсі.

У таких мовах підпис функції може стати інтерфейсом, а функція передається замість традиційного об'єкта для забезпечення бажаної поведінки. Отвір у середньому малюнку є хорошим прикладом для цього.

Це дозволить вам досягти того ж результату з меншим кодом та більшою виразністю, оскільки вам не потрібно реалізувати цілий клас, що відповідає інтерфейсу (OOP), щоб забезпечити бажану поведінку для абонента. Натомість ви можете просто пройти просте визначення функції. Коротше кажучи: Код часто простіший у обслуговуванні, виразніший та гнучкіший, коли використовуються функції вищого порядку.

Приклад в C #

Традиційний підхід:

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);

З функціями вищого порядку:

public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
    foreach(var customer in customers)
    {
        if(filter(customer))
        {
            yield return customer;
        }
    }
}

Тепер реалізація та виклик стають менш громіздкими. Нам більше не потрібно поставляти реалізацію IFilter. Нам більше не потрібно впроваджувати класи для фільтрів.

var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);

Звичайно, це вже може зробити LinQ в C #. Я просто використав цей приклад, щоб проілюструвати, що легше та гнучкіше використовувати функції вищого порядку замість об'єктів, що реалізують інтерфейс.


3
Гарний приклад. Однак, як і Гюльшан, я намагаюся дізнатися більше про функціональне програмування, і мені було цікаво, чи подібний "функціональний DI" не приносить деякої суворості та значущості порівняно з "об'єктно-орієнтованим DI". Підпис вищого порядку лише говорить про те, що передана функція повинна брати Замовника як параметр і повертати bool, тоді як версія OO застосовує факт, що переданий об'єкт є фільтром (реалізує IFilter <Customer>). Це також робить поняття фільтра явним, що може бути хорошою справою, якщо це основне поняття Домену (див. DDD). Як ти гадаєш ?
guillaume31

2
@ ian31: Це справді цікава тема! Все, що передається FilterCustomer, поводитиметься як якийсь фільтр неявно. Коли концепція фільтра є важливою частиною домену і вам потрібні складні правила фільтру, які використовуються кілька разів у всій системі, то краще їх інкапсулювати. Як ні, чи лише дуже низький ступінь, я б прагнув технічної простоти та прагматизму.
Сокіл

5
@ ian31: Я повністю не згоден. Реалізація IFilter<Customer>- це взагалі не примусове виконання. Функція вищого порядку набагато гнучкіша, що є великою вигодою, а можливість їх записувати в рядок - ще одна величезна перевага. Лямбди також набагато легше здатні фіксувати локальні змінні.
DeadMG

3
@ ian31: Функцію можна також перевірити під час компіляції. Ви також можете написати функцію, назвати її, а потім передати її як аргумент до тих пір, поки вона заповнить очевидний контракт (забирає клієнта, повертає bool). Вам не обов'язково передавати лямбда-вираз. Таким чином, ви можете певною мірою прикрити цю відсутність виразності. Однак договір та його намір не так чітко виражені. Іноді це головний недолік. Загалом, це питання виразності, мови та інкапсуляції. Я думаю, що ви повинні судити про кожну справу самостійно.
Сокіл

2
якщо ви сильно про роз'яснення смислового значення введеної функції, ви можете в підписах функції # імені За допомогою делегатів: public delegate bool CustomerFilter(Customer customer). у чистих функціональних мовах, таких як haskell, типи псевдонімів є тривіальними:type customerFilter = Customer -> Bool
sara

8

Якщо ви хочете змінити поведінку функції

doThis(Foo)

ви можете передати іншу функцію

doThisWith(Foo, anotherFunction)

яка реалізує поведінку, якою ви хочете бути іншою.

"doThisWith" - це функція вищого порядку, оскільки вона приймає іншу функцію як аргумент.

Наприклад, ви могли

storeValues(Foo, writeToDatabase)
storeValues(Foo, imitateDatabase)

5

Коротка відповідь:

Класична залежність Впорскування / Інверсія управління використовує інтерфейси класу як заповнювача для залежної функціональності. Цей інтерфейс реалізований класом.

Замість інтерфейсу / ClassImplementation багато залежностей можна легше реалізувати за допомогою функції делегування.

Ви знайдете приклад для обох в c # на ioc-factory-pro-and-contras-for-interface-vers-delegati .


0

Порівняйте це:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = new LinkedList<String>();
for (String name : names) {
    if (name.startsWith("S")) {
        namesBeginningWithS.add(name);
    }
}

з:

String[] names = {"Fred", "Susan"};
List<String> namesBeginningWithS = names.stream().filter(n <- n.startsWith("S")).collect();

Друга версія - це спосіб Java 8 зменшити код котлопластини (циклічне виконання тощо), надаючи функції вищого порядку, наприклад, filterякі дозволяють пройти мінімум (тобто залежність, яку потрібно вводити - лямбда-вираз).


0

Piggy-backing з прикладу LennyProgrammers ...

Одне з речей, які пропустили інші приклади, - це те, що ви можете використовувати функції вищого порядку разом з додатком часткової функції (PFA) для прив'язки (або "введення") залежностей у функцію (через її список аргументів) для створення нової функції.

Якщо замість:

doThisWith(Foo, anotherFunction)

ми (щоб бути звичайним у тому, як зазвичай виконується ПФА), виконуємо функцію низького рівня працівника як (міняючи аргументацію порядку):

doThisWith( anotherFunction, Foo )

Тоді ми можемо частково застосувати doThisWith так:

doThis = doThisWith( anotherFunction )  // note that "Foo" is still missing, argument list is partial

Що дозволяє нам згодом використовувати нову функцію так:

doThis(Foo)

Або навіть:

doThat = doThisWith( yetAnotherDependencyFunction )
...
doThat( Bar )

Дивіться також: https://ramdajs.com/docs/#partial

... і, так, суматори / множники - непередумні приклади. Кращим прикладом може бути функція, яка приймає повідомлення та записує їх у електронне повідомлення або надсилає по електронній пошті залежно від того, якою функцією «споживач» передається залежність.

Розширюючи цю ідею, ще довші списки аргументів можуть поступово звужуватися до все більш спеціалізованих функцій із більш короткими та короткими списками аргументів, і звичайно будь-яка з цих функцій може бути передана іншим функціям, як залежності, які частково застосовуються.

OOP приємно, якщо вам потрібна група речей з декількома тісно пов'язаними операціями, але це перетворюється на макіяж, щоб зробити купу класів, кожен з єдиним загальнодоступним методом "зроби це", а-ля "Королівство іменників".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.