服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Clojure 与Java对比少数据结构多函数胜过多个单独类的优点

Clojure 与Java对比少数据结构多函数胜过多个单独类的优点

2023-02-22 15:54sofia Java教程

这篇文章主要介绍了Clojure 与Java对比少数据结构多函数胜过多个单独类的优点,在Clojure中,我们一次又一次地使用相同的数据结构,并在其上运行许多函,更多相关介绍需要的朋友可以参考一下下面文章内容

前言:

在Clojure中,我们一次又一次地使用相同的数据结构,并在其上运行许多函数。另一方面,Java程序员为每一组数据创建一个唯一的类,并使用自己的“API”(getter、setter、return type等)来访问和操作数据。由于被迫在两个这样的“类API”之间进行翻译,我想与大家分享我的经验,从而在实践中证明格言中的真理

请注意,本文谈论的是数据和数据承载类,而不是“业务逻辑”,它将由Java中所述对象上的方法和Clojure中命名空间中的函数(最好是纯函数)实现。

注意:本文会交替使用Java和Groovy,因为它们基本相同;本文所说的一个也适用于另一个。

问题所在

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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函数,如:

?
1
2
3
4
5
6
7
8
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)
        }
    }
}

?
1
2
3
4
5
6
7
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,那么一些示例可能很难阅读,例如assoc和reduce-kv (key-value)函数以及偶尔的单字母名称。请记住,Clojure程序员反复使用相同的100个函数,并且非常熟悉它们。与其他一些语言相反,Clojure有意识地选择为有经验的开发人员进行优化。这对我来说很好。

案例1:相同的Keys

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

?
1
2
3
4
(assoc
  target-request
  :headers
  (select-keys (:headers source-request) [:pragma :content-type ...]))

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

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

?
1
2
3
4
5
6
(assoc
  target-request
  :headersX
  (clojure.set/rename-keys
    (select-keys (:headersY source-request) [:Pragma :ContentType ...])
    {:Pragma :pragma, :ContentType :content-type}))

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

?
1
2
3
4
5
(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映射:

?
1
2
3
4
5
6
7
8
9
10
11
(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。

请求如下所示:

?
1
{: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返回映射):

?
1
2
3
4
5
6
(assoc
  request
  :headers
  (select-keys
    (:headers other-request)
    [:pragma ...]))

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

?
1
2
3
4
new Person(
  firstName: employee.firstName,
  lastName: employee.lastName,
  ...)

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

转换

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

?
1
2
3
4
5
6
7
8
9
// fake code <img src="https://javakk.com/wp-content/themes/Tint-master/images/smilies/icon_smile.gif" id="codetool">

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

验证、封装?

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

结论

Clojure在任何地方都使用相同的少数数据结构(map、set、list、vector),并具有许多操作这些结构的函数(许多函数如map on all,一些函数如select key only on some)。最终,您将非常熟练地使用这些功能以及将它们结合起来以实现您想要的任何功能的方法。

Java开发人员必须为每个类学习一个新的“数据访问API”,并进行大量的手动翻译。她在一节课上学到的东西在另一节课上通常是无用的。

Clojure方法似乎更有成效。但它超越了开发人员的生产力。所有Clojure库都使用相同的少数通用数据结构,因此可以编写同样通用的实用程序库来处理数据,如Specter或Balagan,这些数据可以用于Ring请求、Hiccup HTML表示、“来自后端服务的json”数据以及其他任何数据。

到此这篇关于Clojure 与Java对比少数据结构多函数胜过多个单独类的优点的文章就介绍到这了,更多相关Clojure 与 Java 内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://javakk.com/2660.html

延伸 · 阅读

精彩推荐
  • Java教程Java List接口的集合使用详解

    Java List接口的集合使用详解

    这篇文章主要介绍了Java集合操作之List接口及其实现方法,详细分析了Java集合操作中List接口原理、功能、用法及操作注意事项,需要的朋友可以参考下...

    深浅Java8882021-12-08
  • Java教程java实现腾讯ocr图片识别接口调用

    java实现腾讯ocr图片识别接口调用

    这篇文章主要为大家详细介绍了java实现腾讯ocr图片识别接口调用,拍车牌识别车牌号功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    心怀寰宇6642021-06-17
  • Java教程Idea开发工具之SpringBoot整合JSP的过程

    Idea开发工具之SpringBoot整合JSP的过程

    最近在学习SpringBoot,看到SpringBoot整合jsp,顺带记录一下。本文通过图文实例相结合给大家讲解SpringBoot整合JSP的过程,感兴趣的朋友一起看看吧...

    蹦蹦跳的鼻涕泡6492022-01-04
  • Java教程java随机生成8位数授权码的实例

    java随机生成8位数授权码的实例

    下面小编就为大家带来一篇java随机生成8位数授权码的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    Java之家6702020-08-20
  • Java教程Java中Spring使用Quartz任务调度定时器

    Java中Spring使用Quartz任务调度定时器

    本篇文章主要介绍了Java中Spring使用Quartz任务调度定时器,具有一定的参考价值,有兴趣的可以了解一下。 ...

    Mafly4962020-08-03
  • Java教程mybatis plus实现分页逻辑删除

    mybatis plus实现分页逻辑删除

    这篇文章主要为大家介绍了mybatis plus实现分页逻辑删除的方式详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    把苹果咬哭的测试笔记10622022-12-27
  • Java教程IDEA 开发配置SparkSQL及简单使用案例代码

    IDEA 开发配置SparkSQL及简单使用案例代码

    这篇文章主要介绍了IDEA 开发配置SparkSQL及简单使用案例代码,本文通过代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的...

    zhangfei_bk5302021-11-12
  • Java教程SpringBoot使用validation做参数校验说明

    SpringBoot使用validation做参数校验说明

    这篇文章主要介绍了SpringBoot使用validation做参数校验说明,首先通过添加hibernate-validator展开全文内容,具有一定的参考价值,需要的小伙伴可以参考与喜爱...

    小小小LIN子8022022-11-27