今天我刚刚看到这篇文章,它描述了SOLID原理在F#开发中的相关性-
在谈到最后一个问题时-“依赖倒置原则”,作者说:
从功能的角度来看,这些容器和注入概念可以通过简单的高阶函数或内置在语言中的“中间钻孔”模式来解决。
但是他没有进一步解释。因此,我的问题是,依赖项反转与高阶函数有何关系?
今天我刚刚看到这篇文章,它描述了SOLID原理在F#开发中的相关性-
在谈到最后一个问题时-“依赖倒置原则”,作者说:
从功能的角度来看,这些容器和注入概念可以通过简单的高阶函数或内置在语言中的“中间钻孔”模式来解决。
但是他没有进一步解释。因此,我的问题是,依赖项反转与高阶函数有何关系?
Answers:
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#中完成。我只是使用此示例来说明,使用高阶函数而不是实现接口的对象更加容易和灵活。
IFilter<Customer>
根本不强制执行。高阶函数要灵活得多,这是一个很大的好处,而能够内联编写它们是另一个巨大的好处。Lambda也更容易捕获局部变量。
public delegate bool CustomerFilter(Customer customer)
。在纯函数式语言(如haskell)中,别名类型很简单:type customerFilter = Customer -> Bool
简短答案:
经典的依赖项注入/控制反转使用类接口作为占位符表示相关功能。该接口由一个类实现。
代替Interface / ClassImplementation,可以使用委托函数更轻松地实现许多依赖关系。
您可以在ioc-factory-pros-and-contras-for-interface-versus-delegates的 c#中找到两个示例。
比较一下:
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
该函数允许您传递最基本的最小值(即,要注入的依赖项-lambda表达式)。
附带LennyProgrammers示例...
其他示例缺少的一件事是,您可以将高阶函数与部分函数应用程序(PFA)结合使用,以将依赖项绑定(或“注入”)到函数中(通过其参数列表)以创建新函数。
如果不是:
doThisWith(Foo, anotherFunction)
我们(通常在PFA的常规处理方式中)具有底层工作程序功能,如(交换arg顺序):
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会很好,但是它变成了用一对公共的“ do it”方法(例如“名词王国”)制作一堆类的方法。