在LISP中使用依赖注入(以及反转控制容器)的意义是什么?

4
几年前我读过ESR的文章,名为“如何成为黑客?”(链接可以在我的个人资料中找到),Eric建议学习LISP。嗯,我学了相当长时间的LISP,并且我很喜欢它,以至于我决定用它编写一个Web应用程序。
由于我已经使用Spring有一段时间了,我认为编写解耦组件并使用IoC容器和依赖注入将它们粘合在一起是个好主意。我在谷歌上进行了强力搜索,结果发现LISP中没有实现这种想法。我错过了什么吗?在LISP中是否有这个概念的良好实现,或者出于某些尚不清楚的原因,没有使用它的意义?

2
Common Lisp是动态类型的,具有完整的匿名函数和基于通用函数的面向对象编程。我对IoC/DI模式的经验不足以确定是否可以将它们纳入语言本身,因此无法给出完整的答案。 - Ramarren
这就是我希望得到答案的问题。 :) - Adam Arold
1
@Ramarren,依赖注入简单来说就是软件构建块不会自己寻找它们的依赖关系,而是从“外部”接收它们。在我看来,这种设计模式不能被语言特性所概括,当然也不能被动态类型或匿名函数所概括。可以被概括的是IoC容器,因为完全可以手动组装对象图。也许在Lisp中这样做很容易,人们根本不需要任何额外的库来帮助他们完成。 (我很高兴手动在Objective-C中进行DI。) - zoul
2个回答

22
“控制反转”在Lisp中被广泛使用。这是非常简单的,因为函数和闭包是一等对象。
依赖注入很简单。类和函数可以通过符号和一等类进行配置。
通常在Common Lisp中不需要IoC或DI的“框架”,因为许多功能用于配置和参数化应用程序和库已经内置。
“一等”意味着某些东西可以存储在变量中,作为参数传递或作为结果返回。
在像Common Lisp这样的语言中,函数和类是一等对象。此外,为了通过延迟绑定进行解耦,您可以使用符号作为它们的名称。Common Lisp对象系统知道元类和符号作为类的名称。甚至通用函数和方法也是对象,并且具有元类。
如果“concurrent-secure-server”是一个类,“default-response”是一个函数,您可以执行以下操作:
(make-instance 'web-services
               :server-class 'concurrent-secure-server
               :default-response-function 'default-reponse)

以上使用符号作为类和函数的名称。如果该函数有新版本,Web服务可能会在以后调用新版本。

或者:

(make-instance 'web-services
               :server-class (find-class 'concurrent-secure-server)
               :default-response-function #'default-reponse)

在上述情况中,我们传递了类对象和函数对象。
在Common Lisp中,软件模块可以拥有全局变量,您可以使用正确的信息进行设置:
 (defvar *default-server-class* 'concurrent-secure-server)

或者您可以像下面这样将它们设置在插槽中。

(defclass server-object ()
  ((default-response-function
      :initarg :default-response-function
      :initform *server-default-response-function*)))

(defvar *my-server*
   (make-instance 'server-object
                  :default-response-function 'my-default-response-function))

您甚至可以在配置阶段创建对象并稍后更改其类。 Common Lisp对象系统允许您更改类并更新现有对象。

如果您创建实例,您可以尽可能灵活:

  • 您可以传递类
  • 您可以传递参数

就像这样:

(let ((my-class 'foo-class)
      (my-args  `(:response-function ',*some-reponse-function)))
  (apply #'make-instance my-class my-args))

有时你会看到在运行时计算这些参数列表的Lisp库。
另一个可以在运行时配置Lisp应用程序的方法是通过通用函数。通用函数允许使用:before、:after和:around方法,甚至允许您自定义调用方案。因此,通过使用从其他类和混合类继承的自己的类,通用函数得到重新配置。这就像内置了面向方面编程的基本机制。
对于那些对这些更高级的面向对象概念感兴趣的人,施乐帕克(Xerox PARC)有一些文献,在创建CLOS时研究了这些问题。当时它被称为“开放实现”: http://www2.parc.com/csl/groups/sda/publications.shtml(原始链接,现在已失效)
存档:

https://web.archive.org/web/20110906111530/http://www2.parc.com/csl/groups/sda/publications.shtml

在开放实现方法中,模块允许其客户端单独控制模块自身的实现策略。这使得客户端能够根据自己的需求定制模块的实现策略,有效地使模块更具可重用性,客户端代码更简单。该控制通过一个精心设计的辅助接口提供给客户端。
您不需要的一件事:XML。通用Lisp是自己的配置语言。编写扩展以实现更轻松的配置可以通过宏完成。这些配置的加载可以通过LOAD轻松完成。

1
答案中的链接现在只是重定向到主要的parc网站。原始内容仍然可以通过wayback机器访问:https://web.archive.org/web/20110906111530/http://www2.parc.com/csl/groups/sda/publications.shtml - Pedro Henrique A. Oliveira
1
"Open Implementations and Metaobject Protocols"一文的PDF直接链接,作者为Gregor Kiczales和Andreas Paepcke:https://web.archive.org/web/20040612144144/http://www2.parc.com/csl/groups/sda/publications/papers/Kiczales-TUT95/for-web.pdf。 - Jérôme Radix

3

实际上,IoC是大多数Web框架的构建原则,不仅限于Java或Lisp。 考虑到DI,如Rammaren所指出的,在像Lisp这样的动态语言中,它是一种隐式模式。如果您比较SpringRestas(其中一个受支持的CL Web框架)中的Hello World应用程序,您会发现相同的模式,除了Lisp中不需要花哨的类型/类/接口声明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接