종속성 반전은 고차 함수와 어떤 관련이 있습니까?


41

오늘 저는 F # 개발에서 SOLID 원리의 관련성을 설명하는이 기사를 보았습니다.

F # 및 설계 원칙 – SOLID

그리고 저자는 "종속성 역전 원리"라는 마지막 주제를 다루면서 다음과 같이 말했습니다.

기능적인 관점에서, 이러한 컨테이너 및 주입 개념은 간단한 고차 함수 또는 언어에 내장 된 중간자 형태의 패턴으로 해결할 수 있습니다.

그러나 그는 더 이상 설명하지 않았다. 그래서 내 질문은 의존성 반전이 고차 함수와 어떻게 관련이 있습니까?

답변:


38

OOP의 Dependency Inversion은 인터페이스에 대해 코딩 한 다음 객체의 구현에서 제공합니다.

더 높은 언어 함수를 지원하는 언어는 종종 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
좋은 예입니다. 그러나 Gulshan처럼 나는 함수형 프로그래밍에 대해 더 많은 것을 찾으려고 노력하고 있는데 이런 종류의 "기능적 DI"가 "객체 지향 DI"와 비교하여 어떤 엄격함과 중요성을 희생시키지 않는지 궁금합니다. 상위 서명은 전달 된 함수가 고객을 매개 변수로 가져와 부울을 리턴해야한다고 명시하지만, OO 버전 전달 된 오브젝트 필터 라는 사실을 강제합니다 (IFilter <Customer> 구현). 또한 필터 개념을 명시 적으로 만들어 도메인의 핵심 개념 인 경우 좋은 방법이 될 수 있습니다 (DDD 참조). 어떻게 생각해 ?
guillaume31

2
@ ian31 : 이것은 실제로 흥미로운 주제입니다! FilterCustomer에 전달 된 모든 것은 암시 적으로 일종의 필터로 작동합니다. 필터 개념이 도메인의 필수 부분이고 시스템에 여러 번 사용되는 복잡한 필터 규칙이 필요한 경우이를 캡슐화하는 것이 좋습니다. 그렇지 않거나 매우 낮은 수준이라면 기술 단순성과 실용주의를 목표로합니다.
팔콘

5
@ ian31 : 나는 완전히 동의하지 않습니다. 구현 IFilter<Customer>은 전혀 시행되지 않습니다. 고차 함수는 훨씬 더 유연하여 큰 이점이 있으며 인라인으로 작성할 수 있다는 것도 큰 이점입니다. 람다는 지역 변수를 훨씬 쉽게 캡처 할 수 있습니다.
DeadMG

3
@ ian31 : 컴파일 타임에 함수를 확인할 수도 있습니다. 함수를 작성하고 이름을 지정한 다음 명백한 계약을 가득 채우는 한 (고객을 받아 부울을 반환하는 경우) 인수로 전달할 수도 있습니다. 람다 식을 반드시 전달할 필요는 없습니다. 따라서 표현력 부족을 어느 정도까지 다룰 수 있습니다. 그러나 계약과 그 의도는 명확하게 표현되지 않았습니다. 그것은 때때로 큰 단점입니다. 대체로 표현력, 언어 및 캡슐화의 문제입니다. 각 사건을 스스로 판단해야한다고 생각합니다.
팔콘

2
주입 된 함수의 의미 적 의미를 명확하게 밝히고 싶다면 델리게이트를 사용하여 C # 이름 함수 서명을 사용할 수 있습니다 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

짧은 답변:

Classical Dependency Injection / Inversion of Control은 종속 인터페이스의 자리 표시 자로 클래스 인터페이스를 사용합니다. 이 인터페이스는 클래스로 구현됩니다.

Interface / ClassImplementation 대신 대리자 함수를 사용하여 많은 종속성을 쉽게 구현할 수 있습니다.

c #의 ioc-factory-pros-and-contras-for-faceface-versus-delegates에 대한 예제가 있습니다 .


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

LennyProgrammers 예제의 피기 백 ...

다른 예제에서 놓친 것 중 하나는 부분 함수 응용 프로그램 (PFA)과 함께 고차 함수를 사용하여 종속성을 인수 목록을 통해 함수에 바인딩 (또는 "주입")하여 새 함수를 만들 수 있다는 것입니다.

대신에 :

doThisWith(Foo, anotherFunction)

우리는 (PFA가 일반적으로 수행되는 방식에서 일반적으로) 낮은 수준의 작업자 기능을 다음과 같이 (스왑 교환 순서)합니다.

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가 좋지만 단일 "공용"방법 ( "명명의 왕국")을 사용하여 많은 클래스를 만드는 작업이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.