温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Clojure与Java对比实例分析

发布时间:2022-06-10 09:26:10 来源:亿速云 阅读:93 作者:zzz 栏目:开发技术

这篇文章主要介绍“Clojure与Java对比实例分析”,在日常操作中,相信很多人在Clojure与Java对比实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Clojure与Java对比实例分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    问题所在

    我一直在写一个代理,接收javax.servlet.http.HttpServletRequest 并通过Apache HttpClientorg.apache.http.client.methods.HttpUriRequest,然后从org.apache.http.HttpResponsejavax.servlet.http.HttpServletResponse,尤其是关于(一个子集)头的响应。

    这是一件痛苦的事,因为每个人都有自己的头表示和使用headers的API:

    // javax.servlet.http.HttpServletRequest:
    Enumeration<String> getHeaderNames();
    /** Returns all the values of the specified request
        header as an Enumeration of String objects. */
    Enumeration<String> getHeaders(String name);
    
    // org.apache.http.client.methods.RequestBuilder:
    /** Add a header; repeat to add multiple values */
    RequestBuilder addHeader(String name, String value);
    
    //-------------
    // javax.servlet.http.HttpServletResponse:
    /** Add a header; repeat to add multiple values */
    void addHeader(String name, String value);
    
    // org.apache.http.HttpResponse:
    Header[] getAllHeaders();
    // Header:
    String getName();
    String getValue();

    这里,枚举和数组是通用的数据结构,但头和请求对getHeaderNamesgetHeaders的拆分需要特定的代码。

    因此,我必须编写translation函数,如:

    def copyRequestHeaders(HttpServletRequest source, RequestBuilder target) {
        source.getHeaderNames().each { String hdr ->
            source.getHeaders(hdr).each { String val ->
                if (undesirable(hdr)) return
                target.addHeader(hdr, val)
            }
        }
    }

    static void copyResponseHeaders(HttpResponse source, HttpServletResponse target) {
        source.allHeaders.each { Header hdr ->
            if (target.getHeader(hdr.name.toLowerCase()) == hdr.value) return // avoid duplicates
            if (undesirable(hdr.name)) return
            target.addHeader(hdr.name, hdr.value)
        }
    }

    理想情况下,我希望能够像target这样做target.request.headers = omitKeys(undesirable, source.request.headers)。但这是不可能的,我必须从一组类型映射到另一组类型。这里的主要问题是servlet请求被拆分为getHeaderNamesgetHeaders,而不是返回例如Map<String,String[]>,还有RequestBuilder,它有addHeader,但无法一次添加所有头(除非我们首先将它们包装在其域类中,即Header中)。

    (可以说,我可以找到一个更好的例子来说明这一点。在这里,我们仍然主要(但不总是)使用枚举、字符串、数组等基元/泛型类型,而不是嵌套的自定义类型层次结构。)

    Clojure解决方案

    在Clojure中,请求只是一个映射,标题很可能是列表的映射。即使这两个库(服务器、客户端)在密钥名称或数据结构上不一致,也没有“API”可学习-您只需使用相同的旧已知函数从一个数据结构转换到另一个数据结构,这是您在每个Clojure项目、web、数据或任何其他领域中所做的事情。唯一改变的是地图中关键点的名称。

    注意:如果您不知道Clojure,那么一些示例可能很难阅读,例如assocreduce-kv (key-value)函数以及偶尔的单字母名称。请记住,Clojure程序员反复使用相同的100个函数,并且非常熟悉它们。与其他一些语言相反,Clojure有意识地选择为有经验的开发人员进行优化。这对我来说很好。

    案例1:相同的Keys

    最简单的情况是,使用相同的key,我们只想选择一个子集:

    (assoc
      target-request
      :headers
      (select-keys (:headers source-request) [:pragma :content-type ...]))

    唯一区分大小写的部分是keys。在Java中,您不能像我们在这里使用通用选择键那样一次选择所有所需的keys,您需要通过类特定的getHeaders(name)逐个选择它们。

    案例2:不同的Key名,相同的数据结构

    (assoc
      target-request
      :headersX
      (clojure.set/rename-keys
        (select-keys (:headersY source-request) [:Pragma :ContentType ...])
        {:Pragma :pragma, :ContentType :content-type}))

    如果需要更复杂的key转换,我们可以使用例如map:

    (defn transform-key [k] ...)
    (let [hdrs (->> (select-keys headers [:a])
                    (map (fn [[k v]] [(transform-key k) v]))
                    (into {}))]
        (assoc target-request :headersX hdrs))

    关键是,在从一个数据结构映射到另一个数据结构的过程中,我们仍然使用我们所知道和喜爱的相同功能,唯一针对具体情况的部分是键和键转换函数。我们可以简单地映射头映射,这在HttpServletRequest的头上是不可能的。

    案例3:不同的数据结构

    headers作为name-value对列表(可能有重复的名称)进入name-value映射:

    (def headers-in [["pragma" "no-cache"] ["accept" "X"] ["accept" "Y"]])
    (->> headers-in
         (group-by first)
         (reduce-kv
           (fn [m k vs]
             (assoc
               m
               k
               (map second vs)))
           {}))
    ; => {"pragma" ("no-cache"), "accept" ("X" "Y")}

    案例4:Reality

    实际上,我们可能会使用Ring作为服务器,并将Clojure包装器clj-http用于Apache HttpClient。

    请求如下所示:

    {:headers {"accept" "x,y", "pragma" "no-cache"}}

    (我们可以添加ring-request-headers-middleware,将连接的值转换为单个值的列表。)

    Clj-http遵循Ring规范,因此支持相同的格式,但更为宽松:

    clj http对头的处理比ring规范指定的要宽松一些。

    clj http允许任何大小写的字符串或关键字,而不是强制所有请求头都是小写字符串。关键字将转换为它们的规范表示形式,因此:content-md5标头将作为“content-md5”发送到服务器。但是,请求头中的字符串键将被发送到服务器,其大小写保持不变。

    响应标题可以作为任何大小写的关键字或字符串读取。如果服务器以“Date”标头响应,则可以访问该标头的值,如:Date、“Date”、“Date”等。

    这就是上面第1种情况。

    Java Vs Clojure

    我想指出的一点是,Clojure在解决两个问题方面更为有效:数据选择和转换,这要归功于对其使用通用数据结构和函数。

    选择

    在Clojure中,通过选择另一个映射的子集来创建映射非常简单(assoc将键与值关联,select keys返回映射):

    (assoc
      request
      :headers
      (select-keys
        (:headers other-request)
        [:pragma ...]))

    使用典型的Java数据类(还记得DTOs吗?)您需要逐个获取和设置各个属性。即使我们使用Groovy便利:

    new Person(
      firstName: employee.firstName,
      lastName: employee.lastName,
      ...)

    这里的重点并不是键入的数量,而是在Clojure中,我们可以使用现有函数(并将它们组合成新的可重用函数)来完成这项工作,而在Java中,您必须编写(更多)自定义的一次性代码。(或者使用映射器库、注释和其他黑魔法:-))

    转换

    如上所述,在Clojure中,将头从一个请求复制到另一个请求是微不足道的。在典型的Java中,标头将由它们自己的类型(可能是标头)表示,因此,即使它们在两个库中具有相同的形状,它们仍然是不同的类型,我们需要从一种类型转换为另一种类型:

    // fake code <img src="https://cache.yisu.com/upload/information/20220610/112/32986.gif" alt=":-)" />
    def toClientHdr(servlet.Header hdr) {
      return new httpclient.Header(
        name: hdr.name,
        values: hdr.values)
    }
    clientRequest.headers =
      servletRequest.headers
        .map(toClientHdr)

    在Clojure中,toClientHdr是不必要的,因为我们只有映射,没有要从/映射到的类型。我们在这里的前提是,数据的“形状”在两端都是相同的,但即使不是,也更容易从一个转换到另一个,因为数据转换是FP的主要优势之一,尤其是Clojure。核心库中有许多有用的数据选择和转换功能,旨在以多种强大的方式进行组合。

    验证、封装?

    即使您同意使用一些具有强大功能的通用数据结构比将数据包装在类型中更有效,您也可能会担心类的其他好处,例如封装和数据验证。这超出了本文的范围,但请确保FP/Clojure具有满足这些需求的解决方案,尽管它们明显不同于OOP。

    到此,关于“Clojure与Java对比实例分析”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI