clojure_学习Clojure:动态调度

clojure

这是在学习Clojure的焦点series.Other职位包括4 职位:

  1. 解码Clojure代码,让您不知所措
  2. 学习Clojure:应对动态打字
  3. 学习Clojure:arrow和doto宏
  4. 学习Clojure:动态调度 (本篇文章)
  5. 学习Clojure:依赖类型和基于合同的编程
  6. 学习Clojure:与Java流进行比较
  7. 关于学习Clojure的反馈:与Java流进行比较
  8. 学习Clojure:换能器

介绍

动态开发(或路由)表是软件开发中的常见模式。 该表负责返回特定输入的相关输出。

在面向对象的编程世界中,对象模型看起来像这样:

动态调度

将输入发送到注册表时,它会在内部找到可以处理输入的处理程序,并委派给选定的处理程序。

这种模式的常见用例是(非常简化的)HTTP请求-响应路由表。 例如,可以根据Accept HTTP标头确定路由:

  1. 使用特定的Accept标头发送HTTP请求
  2. 注册表搜索能够处理MIME类型的注册处理程序
  3. 处理程序被调用
  4. 它返回带有相关Content-Type标头的HTTP响应
请求响应调度

由实现者决定是否为HTML,XML,JSON等创建处理程序,然后将其注册到注册表中。 或者,某些默认实现可能已经可以直接使用,也可以注册。

Clojure的方法

从Clojure的角度来看,注册表非常像样:如果提供了一种通用的机制来实现相同目的。 此机制基于两个宏:

  • defmulti扮演注册表的角色。 它接受两个参数:标签和路由逻辑。 它返回的值将与处理程序提供的值匹配。
  • defmethod扮演处理程序的角色。 它接受一些参数:
    1. 标签,引用它适用的注册表
    2. 与注册表的路由逻辑返回的值匹配的值。
    3. 可供处理程序逻辑使用的参数列表
    4. 处理程序的逻辑

给我看代码

鉴于此,让我们在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"
  1. 类似地图的请求参数
  2. 标头名称, 例如 :accept
  3. 线程优先宏的用法- 上周已详细说明

下一步是定义defmulti 。 请记住,它定义了主要的路由逻辑。

(defmulti dispatch-accept                           (1)
          (fn [req] (extract-header req :accept)))  (2)
  1. 如上所述,这是defmulti名称
  2. 从请求返回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)
  1. defmethod “附加”到defmethoddefmulti 。 当前者返回text/html时, 当它是:accept标头的值时,将调用此方法。
  2. 使用clj-time定义格式化程序
  3. 使用上述格式化程序格式化请求的日期标头
  4. 将请求的接受标头放入响应的内容类型标头中
  5. 将格式化的日期存储在响应中: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/htmlapplication/xmlapplication/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宏对实现的。

翻译自: https://blog.frankel.ch/learning-clojure/3/

clojure

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值