clojure
这是在学习Clojure的焦点series.Other职位包括4 个职位:
- 解码Clojure代码,让您不知所措
- 学习Clojure:应对动态打字
- 学习Clojure:arrow和doto宏
- 学习Clojure:动态调度 (本篇文章)
- 学习Clojure:依赖类型和基于合同的编程
- 学习Clojure:与Java流进行比较
- 关于学习Clojure的反馈:与Java流进行比较
- 学习Clojure:换能器
介绍
动态开发(或路由)表是软件开发中的常见模式。 该表负责返回特定输入的相关输出。
在面向对象的编程世界中,对象模型看起来像这样:
将输入发送到注册表时,它会在内部找到可以处理输入的处理程序,并委派给选定的处理程序。
这种模式的常见用例是(非常简化的)HTTP请求-响应路由表。 例如,可以根据Accept
HTTP标头确定路由:
- 使用特定的
Accept
标头发送HTTP请求 - 注册表搜索能够处理MIME类型的注册处理程序
- 处理程序被调用
- 它返回带有相关
Content-Type
标头的HTTP响应
由实现者决定是否为HTML,XML,JSON等创建处理程序,然后将其注册到注册表中。 或者,某些默认实现可能已经可以直接使用,也可以注册。
Clojure的方法
从Clojure的角度来看,注册表非常像样:如果提供了一种通用的机制来实现相同目的。 此机制基于两个宏:
-
defmulti
扮演注册表的角色。 它接受两个参数:标签和路由逻辑。 它返回的值将与处理程序提供的值匹配。 -
defmethod
扮演处理程序的角色。 它接受一些参数:- 标签,引用它适用的注册表
- 与注册表的路由逻辑返回的值匹配的值。
- 可供处理程序逻辑使用的参数列表
- 处理程序的逻辑
给我看代码
鉴于此,让我们在Clojure中实现上述模型。
与其余代码无关,第一步是创建一个实用程序函数,以从类似地图的请求中提取accept
标头。
(defn extract-header [request (1)
header] (2)
(-> request (3)
(get :headers)
(get header)))
(extract-header {:headers {:accept "foo/bar"}} :accept) ; "foo/bar"
(extract-header {:headers {:accept "text/html"}} :accept) ; "text/html"
- 类似地图的请求参数
- 标头名称, 例如
:accept
- 线程优先宏的用法- 上周已详细说明
下一步是定义defmulti
。 请记住,它定义了主要的路由逻辑。
(defmulti dispatch-accept (1)
(fn [req] (extract-header req :accept))) (2)
- 如上所述,这是
defmulti
名称 - 从请求返回
header
值
最后一步是定义一个实现:
(defmethod dispatch-accept "text/html" [req] (1)
(let [formatter (f/formatters :date) (2)
date (f/unparse-local-date formatter (extract-header req :date))] (3)
{:status 200
:headers {:content-type (extract-header req :accept)} (4)
:body (str "<html><body>Date is " date)})) (5)
- 将
defmethod
“附加”到defmethod
的defmulti
。 当前者返回text/html
时, 即当它是:accept
标头的值时,将调用此方法。 - 使用clj-time库定义格式化程序
- 使用上述格式化程序格式化请求的日期标头
- 将请求的接受标头放入响应的内容类型标头中
- 将格式化的日期存储在响应中
:body
现在是时候通过类似地图的请求来调用已定义的方法了:
(dispatch-accept {:headers {:accept "text/html"
:date (t/today)}})
如预期的那样,将产生:
{:status 200,
:headers {:content-type "text/html"},
:body "<html><body>Date is 2018-09-29"}
在必要时添加更多处理程序很简单:
(defmethod dispatch-accept "application/xml" [req]
(let [year (f/formatters :year)
month (f/formatter "MM")
day (f/formatter "dd")
date (extract-header req :date)]
{:status 200
:headers {:content-type (extract-header req :accept)}
:body (str "<?xml version=\"1.0\"?><date><year>"
(f/unparse-local-date year date)
"</year><month>"
(f/unparse-local-date month date)
"</month><day>"
(f/unparse-local-date day date)
"</day></date>")}))
(defmethod dispatch-accept "application/json" [req]
(let [year (f/formatters :year)
month (f/formatter "M")
day (f/formatter "dd")
date (extract-header req :date)]
{:status 200
:headers {:content-type (extract-header req :accept)}
:body (str "{ \"date\" : { \"year\" : "
(f/unparse-local-date year date)
", \"month\" : "
(f/unparse-local-date month date)
", \"day\" : "
(f/unparse-local-date day date)
"}}")}))
dispatch-accept {:headers {:accept "application/xml"
:date (t/today)}})
(dispatch-accept {:headers {:accept "application/json"
:date (t/today)}})
上面的代码分别返回:
{:status 200,
:headers {:content-type "application/xml"},
:body "<?xml version=\"1.0\"?><date><year>2018</year><month>09</month><day>29</day></date>"}
{:status 200,
:headers {:content-type "application/json"},
:body "{ \"date\" : { \"year\" : 2018, \"month\" : 9, \"day\" : 29}}"}
遗失案件
此时,调用dispatch-accept
返回一组accept
标头的值: text/html
, application/xml
和application/json
。 如果使用非托管标头发送请求怎么办?
(dispatch-accept {:headers {:accept "image/jpeg"
:date (t/today)}})
java.lang.IllegalArgumentException: No method in multimethod 'dispatch-accept' for dispatch value: image/jpeg
为了管理非托管案例,Clojure允许定义默认的defmethod
,当没有其他匹配时将调用该默认defmethod
。 这类似于switch
语句的default
情况。 让我们补充一点:
(defmethod dispatch-accept :default [req]
{:status 400
:body (str "Unable to process content of type " (extract-header req :accept))})
具有以上定义:
(dispatch-accept {:headers {:accept "image/jpeg"
:date (t/today)}})
{:status 400,
:body "Unable to process content of type image/jpeg"}
结论
虽然OOP通过继承提供多态性,但Clojure也提供继承性,但没有继承性。 此功能是通过defmulti
/ defmethod
宏对实现的。
更进一步:
clojure