Недавно я читал книгу под названием « Функциональное программирование на C #», и мне пришло в голову, что неизменяемая и не имеющая состояния природа функционального программирования обеспечивает результаты, аналогичные шаблонам внедрения зависимостей, и, возможно, даже лучший подход, особенно в отношении модульного тестирования.
Я был бы признателен, если бы кто-либо, имеющий опыт использования обоих подходов, мог поделиться своими мыслями и опытом, чтобы ответить на главный вопрос: является ли функциональное программирование жизнеспособной альтернативой шаблонам внедрения зависимости?
Ответы:
Управление зависимостями является большой проблемой в ООП по следующим двум причинам:
Большинство OO-программистов считают, что тесная связь данных и кода является полностью выгодной, но это обходится дорого. Управление потоком данных через слои является неизбежной частью программирования в любой парадигме. Сопоставление ваших данных и кода добавляет дополнительную проблему, заключающуюся в том, что если вы хотите использовать функцию в определенный момент, вы должны найти способ доставить ее объект к этой точке.
Использование побочных эффектов создает аналогичные трудности. Если вы используете побочный эффект для какой-то функциональности, но хотите иметь возможность поменять его реализацию, у вас практически нет другого выбора, кроме как внедрить эту зависимость.
В качестве примера рассмотрим спамерскую программу, которая очищает веб-страницы для адресов электронной почты, а затем отправляет их по электронной почте. Если у вас есть мышление DI, сейчас вы думаете о сервисах, которые вы инкапсулируете за интерфейсами, и какие сервисы будут внедрены куда. Я оставлю этот дизайн в качестве упражнения для читателя. Если у вас образ мышления FP, сейчас вы думаете о входах и выходах для нижнего уровня функций, таких как:
Когда вы думаете с точки зрения входов и выходов, нет никаких зависимостей функций, только зависимости данных. Вот что делает их такими простыми для модульного тестирования. Ваш следующий уровень вверх организует вывод одной функции на вход следующей и может легко заменять различные реализации по мере необходимости.
В очень реальном смысле, функциональное программирование естественным образом побуждает вас всегда инвертировать свои функциональные зависимости, и, следовательно, вам обычно не нужно предпринимать никаких специальных мер, чтобы сделать это после факта. Когда вы это делаете, такие инструменты, как функции более высокого порядка, замыкания и частичное применение, облегчают выполнение с меньшими затратами.
Обратите внимание, что проблема заключается не в самих зависимостях. Это зависимости, которые указывают неверный путь. Следующий слой может иметь такую функцию:
Для этого слоя вполне нормально иметь жестко запрограммированные зависимости, так как его единственная цель - склеить функции нижнего уровня. Поменять реализацию так же просто, как создать другую композицию:
Это простое перекомпонование стало возможным благодаря отсутствию побочных эффектов. Функции нижнего уровня полностью независимы друг от друга. Следующий уровень может выбрать, какой из
processText
них фактически используется, основываясь на некоторой пользовательской конфигурации:Опять же, не проблема, потому что все зависимости указывают в одну сторону. Нам не нужно инвертировать некоторые зависимости, чтобы все они указывали одинаково, потому что чистые функции уже заставили нас сделать это.
Обратите внимание, что вы могли бы сделать это гораздо более взаимосвязанным, перейдя
config
на самый нижний слой, вместо того, чтобы проверять его сверху. FP не мешает вам делать это, но, как правило, делает его намного более раздражающим, если вы пытаетесь.источник
System.String
. Модульная система позволит вам заменитьSystem.String
переменную, чтобы выбор строковой реализации не был жестко запрограммирован, но все же решался во время компиляции.Это кажется мне странным вопросом. Подходы функционального программирования в значительной степени касаются внедрения зависимостей.
Несомненно, наличие неизменяемого состояния может подтолкнуть вас к тому, чтобы не «обманывать» из-за побочных эффектов или использования состояния класса в качестве неявного контракта между функциями. Это делает передачу данных более явной, что, я полагаю, является основной формой внедрения зависимостей. А концепция функционального программирования, заключающаяся в передаче функций, делает это намного проще.
Но это не удаляет зависимости. Для ваших операций по-прежнему нужны все данные / операции, которые были необходимы им, когда ваше состояние было изменчивым. И вам все равно нужно как-то получить эти зависимости. Так что я бы не сказал, что подходы функционального программирования вообще заменяют DI, поэтому не являются альтернативой.
Во всяком случае, они только что показали вам, как плохой ОО-код может создавать неявные зависимости, о которых программисты редко думают.
источник
Быстрый ответ на ваш вопрос: нет .
Но, как утверждают другие, этот вопрос объединяет два, несколько не связанных понятия.
Давайте сделаем это шаг за шагом.
DI приводит к нефункциональному стилю
В основе программирования функций лежат чистые функции - функции, которые отображают входные данные в выходные, поэтому вы всегда получаете один и тот же выход для данного ввода.
DI обычно означает, что ваше устройство больше не является чистым, поскольку выходной сигнал может варьироваться в зависимости от впрыска. Например, в следующей функции:
getBookedSeatCount
(функция) может варьироваться, приводя к разным результатам для одного и того же заданного ввода. Это также делаетbookSeats
нечистым.Для этого есть исключения: вы можете внедрить один из двух алгоритмов сортировки, которые реализуют одно и то же отображение ввода-вывода, хотя и с использованием разных алгоритмов. Но это исключения.
Система не может быть чистой
Тот факт, что система не может быть чистой, в равной степени игнорируется, как утверждается в источниках функционального программирования.
Система должна иметь побочные эффекты с очевидными примерами:
Таким образом, часть вашей системы должна включать побочные эффекты, и эта часть может также включать императивный стиль или стиль OO.
Парадигма ядро-оболочка
Заимствуя термины из превосходного доклада Гэри Бернхардта о границах , хорошая архитектура системы (или модуля) будет включать в себя следующие два уровня:
Главное, что нужно сделать, - это «разделить» систему на ее чистую часть (ядро) и нечистую часть (оболочку).
Хотя в статье Марка Симанна предлагается несколько ошибочное решение (и вывод), в ней предлагается та же концепция. Реализация Haskell особенно проницательна, поскольку показывает, что все это можно сделать с помощью FP.
DI и FP
Использование DI вполне разумно, даже если основная часть вашего приложения чистая. Ключ заключается в том, чтобы ограничить ДИ внутри нечистой оболочки.
Примером будут API-заглушки - вы хотите настоящий API-интерфейс в производстве, но используете заглушки в тестировании. Придерживаться модели оболочки ядра очень поможет здесь.
Вывод
Так что FP и DI не совсем альтернативы. Скорее всего, в вашей системе есть и то и другое, и совет состоит в том, чтобы обеспечить разделение между чистой и нечистой частью системы, где находятся соответственно FP и DI.
источник
С точки зрения ООП функции могут рассматриваться как интерфейсы с одним методом.
Интерфейс - это более сильный контракт, чем функция.
Если вы используете функциональный подход и выполняете много DI, то по сравнению с подходом ООП вы получите больше кандидатов для каждой зависимости.
против
источник