최근 에 C #에서 Functional Programming 이라는 제목의 책을 읽었으며, 함수 프로그래밍 의 변경 불가능하고 상태 비 저장 특성은 종속성 주입 패턴과 유사한 결과를 달성하며 특히 단위 테스트와 관련하여 더 나은 접근 방식 일 수 있습니다.
두 가지 접근 방식을 모두 경험 한 사람이 자신의 생각과 경험을 공유하여 주된 질문에 대답 할 수 있다면 감사 할 것입니다 .
최근 에 C #에서 Functional Programming 이라는 제목의 책을 읽었으며, 함수 프로그래밍 의 변경 불가능하고 상태 비 저장 특성은 종속성 주입 패턴과 유사한 결과를 달성하며 특히 단위 테스트와 관련하여 더 나은 접근 방식 일 수 있습니다.
두 가지 접근 방식을 모두 경험 한 사람이 자신의 생각과 경험을 공유하여 주된 질문에 대답 할 수 있다면 감사 할 것입니다 .
답변:
종속성 관리는 다음 두 가지 이유로 OOP에서 큰 문제입니다.
대부분의 OO 프로그래머는 데이터와 코드의 긴밀한 결합이 전적으로 유익하다고 생각하지만 비용이 따릅니다. 계층을 통한 데이터 흐름 관리는 패러다임에서 프로그래밍의 불가피한 부분입니다. 데이터와 코드를 결합 하면 특정 지점에서 함수 를 사용하려는 경우 객체를 해당 지점으로 가져 오는 방법을 찾아야 한다는 추가적인 문제가 추가 됩니다.
부작용을 사용하면 비슷한 어려움이 발생합니다. 일부 기능에 부작용을 사용하지만 해당 구현을 스왑 아웃하려면 해당 종속성을 주입하는 것 외에 다른 선택의 여지가 없습니다.
예를 들어 이메일 주소를 위해 웹 페이지를 긁어 내고 이메일을 보내는 스패머 프로그램을 고려하십시오. DI 마인드가 있다면 지금 당장 인터페이스 뒤에 캡슐화 할 서비스와 어떤 서비스를 어디에 삽입 할 것인지 생각하고 있습니다. 나는 그 디자인을 독자들을위한 연습으로 남겨 둘 것이다. FP 마인드가 있다면 지금 당장 다음과 같은 가장 낮은 기능 계층에 대한 입력 및 출력을 생각합니다.
입력 및 출력과 관련하여 생각할 때 함수 종속성이없고 데이터 종속성 만 있습니다. 그것이 단위 테스트를 매우 쉽게 만드는 것입니다. 다음 계층은 한 함수의 출력이 다음 함수의 입력으로 공급되도록 준비하고 필요에 따라 다양한 구현을 쉽게 교체 할 수 있습니다.
매우 실제적인 의미에서, 함수형 프로그래밍은 자연스럽게 함수 종속성을 항상 반전 시키도록 자극하므로 일반적으로 사실 후에 특별한 조치를 취할 필요가 없습니다. 그렇게하면 고차 함수, 클로저 및 부분 적용과 같은 도구를 사용하여 보일러 플레이트를 줄이면 더 쉽게 달성 할 수 있습니다.
문제가되는 것은 의존성 자체가 아닙니다. 잘못된 길 을 가리키는 의존성입니다 . 다음 레이어는 다음과 같은 기능을 할 수 있습니다.
processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses
하위 계층 기능을 함께 붙이는 것이 유일한 목적이기 때문에이 계층이 이와 같이 하드 코딩 된 종속성을 갖는 것은 괜찮습니다. 구현 스왑은 다른 컴포지션을 만드는 것만 큼 간단합니다.
processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses
이 쉬운 재구성은 부작용이 없어서 가능합니다. 하위 계층 기능은 서로 완전히 독립적입니다. 다음 계층 processText
은 일부 사용자 구성에 따라 실제로 사용되는 것을 선택할 수 있습니다 .
actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText
다시 말하지만, 모든 종속성이 한 방향을 가리 키기 때문에 문제가되지 않습니다. 순수한 함수는 이미 우리에게 그렇게하도록 강요했기 때문에 의존성을 모두 같은 방식으로 가리 키기 위해 의존성을 뒤집을 필요는 없습니다.
config
맨 위를 확인하는 대신 가장 낮은 레이어 로 전달 하여 더 많이 결합 할 수 있습니다. FP는이 작업을 방해하지는 않지만 시도하면 더 성가신 경향이 있습니다.
System.String
. 모듈 시스템을 사용하면 System.String
문자열 구현의 선택이 하드 코딩되지 않지만 컴파일 타임에 여전히 해결되도록 변수로 바꿀 수 있습니다 .
함수형 프로그래밍은 의존성 주입 패턴에 대한 실행 가능한 대안입니까?
이것은 나를 이상한 질문으로 생각합니다. 함수형 프로그래밍 접근법은 의존성 주입에 크게 접합니다.
물론, 불변의 상태를 가짐으로써 부작용을 갖거나 클래스 상태를 함수 사이의 암묵적 계약으로 사용하여 "속임수"가되지 않도록 할 수 있습니다. 그것은 데이터의 전달을보다 명확하게 만듭니다. 이것은 가장 기본적인 형태의 의존성 주입이라고 가정합니다. 그리고 함수를 전달하는 함수형 프로그래밍 개념으로 훨씬 쉽게 할 수 있습니다.
그러나 의존성을 제거하지는 않습니다. 운영 상태는 변경 가능할 때 필요한 모든 데이터 / 작업이 여전히 필요합니다. 그리고 당신은 여전히 어떻게 그러한 의존성을 가져와야합니다. 따라서 함수형 프로그래밍 접근 방식이 DI를 전혀 대체 한다고 말하지 는 않으므로 대안이 아닙니다.
OO 코드가 암묵적 종속성을 생성하여 프로그래머가 거의 생각하지 않는 것보다 OO 코드가 얼마나 나쁜지를 보여주었습니다.
귀하의 질문에 빠른 답은 없습니다 .
그러나 다른 사람들이 주장한 것처럼,이 질문은 다소 관련이없는 두 가지 개념과 결혼합니다.
이 단계를 단계별로 수행하겠습니다.
함수 프로그래밍의 핵심에는 순수한 함수-입력을 출력에 매핑하는 함수가 있으므로 주어진 입력에 대해 항상 동일한 출력을 얻습니다.
DI는 일반적으로 출력이 주입에 따라 달라질 수 있으므로 장치가 더 이상 순수하지 않음을 의미합니다. 예를 들어 다음 기능에서
const bookSeats = ( seatCount, getBookedSeatCount ) => { ... }
getBookedSeatCount
(함수)는 동일한 주어진 입력에 대해 다른 결과를 산출 할 수 있습니다. 이것은 bookSeats
또한 불완전합니다.
여기에는 예외가 있습니다. 다른 알고리즘을 사용하더라도 동일한 입력-출력 매핑을 구현하는 두 가지 정렬 알고리즘 중 하나를 삽입 할 수 있습니다. 그러나 이것들은 예외입니다.
시스템이 순수 할 수 없다는 사실은 기능적 프로그래밍 소스에서 주장한 것과 동일하게 무시됩니다.
시스템은 명백한 예와 함께 부작용이 있어야합니다.
따라서 시스템의 일부에는 부작용이 포함되어야하며 해당 부분에는 명령형 스타일 또는 OO 스타일도 포함될 수 있습니다.
Gary Bernhardt의 탁월한 경계에 관한 용어를 빌려 보면 , 좋은 시스템 (또는 모듈) 아키텍처에는 다음 두 계층이 포함됩니다.
핵심 요소는 시스템을 순수한 부분 (핵심)과 불순한 부분 (쉘)으로 '분할'하는 것입니다.
약간의 결함이있는 솔루션 (및 결론)을 제공하지만 이 Mark Seemann의 기사 는 매우 동일한 개념을 제안합니다. Haskell 구현은 FP를 사용하여 모두 수행 할 수 있음을 보여 주므로 특히 통찰력이 있습니다.
대부분의 응용 프로그램이 순수한 경우에도 DI를 사용하는 것이 합리적입니다. 핵심은 불순한 껍질 안에 DI를 가두는 것입니다.
예를 들어 API 스텁이 있습니다. 프로덕션에서는 실제 API를 원하지만 테스트에는 스텁을 사용하십시오. 쉘 코어 모델을 준수하면 여기에서 많은 도움이 될 것입니다.
따라서 FP와 DI는 정확한 대안이 아닙니다. 시스템에 둘 다있을 수 있으며 FP와 DI가 각각 상주하는 시스템의 순수한 부분과 불순한 부분을 분리하는 것이 좋습니다.
OOP 관점에서 함수는 단일 메소드 인터페이스로 간주 될 수 있습니다.
인터페이스는 함수보다 강력한 계약입니다.
기능적 접근 방식을 사용하고 DI를 많이 사용하는 경우 OOP 접근 방식을 사용하는 것과 비교하여 각 종속성에 대해 더 많은 후보를 얻게됩니다.
void DoStuff(Func<DateTime> getDateTime) {}; //Anything that satisfies the signature can be injected.
vs
void DoStuff(IDateTimeProvider dateTimeProvider) {}; //Only types implementing the interface can be injected.