오늘 저는 F # 개발에서 SOLID 원리의 관련성을 설명하는이 기사를 보았습니다.
그리고 저자는 "종속성 역전 원리"라는 마지막 주제를 다루면서 다음과 같이 말했습니다.
기능적인 관점에서, 이러한 컨테이너 및 주입 개념은 간단한 고차 함수 또는 언어에 내장 된 중간자 형태의 패턴으로 해결할 수 있습니다.
그러나 그는 더 이상 설명하지 않았다. 그래서 내 질문은 의존성 반전이 고차 함수와 어떻게 관련이 있습니까?
오늘 저는 F # 개발에서 SOLID 원리의 관련성을 설명하는이 기사를 보았습니다.
그리고 저자는 "종속성 역전 원리"라는 마지막 주제를 다루면서 다음과 같이 말했습니다.
기능적인 관점에서, 이러한 컨테이너 및 주입 개념은 간단한 고차 함수 또는 언어에 내장 된 중간자 형태의 패턴으로 해결할 수 있습니다.
그러나 그는 더 이상 설명하지 않았다. 그래서 내 질문은 의존성 반전이 고차 함수와 어떻게 관련이 있습니까?
답변:
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 #으로 이미 수행 할 수 있습니다. 방금이 예제를 사용하여 인터페이스를 구현하는 객체 대신 고차 함수를 사용하는 것이 더 쉽고 유연하다는 것을 보여주었습니다.
IFilter<Customer>
은 전혀 시행되지 않습니다. 고차 함수는 훨씬 더 유연하여 큰 이점이 있으며 인라인으로 작성할 수 있다는 것도 큰 이점입니다. 람다는 지역 변수를 훨씬 쉽게 캡처 할 수 있습니다.
public delegate bool CustomerFilter(Customer customer)
. haskell과 같은 순수 기능 언어에서 앨리어싱 유형은 사소한 것입니다.type customerFilter = Customer -> Bool
짧은 답변:
Classical Dependency Injection / Inversion of Control은 종속 인터페이스의 자리 표시 자로 클래스 인터페이스를 사용합니다. 이 인터페이스는 클래스로 구현됩니다.
Interface / ClassImplementation 대신 대리자 함수를 사용하여 많은 종속성을 쉽게 구현할 수 있습니다.
c #의 ioc-factory-pros-and-contras-for-faceface-versus-delegates에 대한 예제가 있습니다 .
이것을 비교하십시오 :
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
(예 : 주입되는 종속성-람다 식)를 전달할 수있는 고차 함수를 제공하여 상용구 코드 (루핑 등)를 줄이는 방법입니다 .
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가 좋지만 단일 "공용"방법 ( "명명의 왕국")을 사용하여 많은 클래스를 만드는 작업이됩니다.