最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java 設(shè)計(jì)模式 Monads 的美麗世界

2021-11-10 19:46 作者:信碼由韁  | 我要投稿

【注】本文譯自:Beautiful world of Monad - Dev Community(https://dev.to/siy/beautiful-world-of-mondas-2cd6)

讓我從免責(zé)聲明開始。從函數(shù)式編程的角度來看,下面的解釋絕不是精確的或絕對(duì)準(zhǔn)確的。相反,我將重點(diǎn)解釋的清晰和簡(jiǎn)單性上,以便讓盡可能多的 Java 開發(fā)人員進(jìn)入這個(gè)美麗的世界。

幾年前,當(dāng)我開始深入研究函數(shù)式編程時(shí),我很快發(fā)現(xiàn)有大量的信息,但對(duì)于幾乎完全具有命令式背景的普通 Java 開發(fā)人員來說,幾乎無法理解。如今,情況正在慢慢改變。例如,有很多文章解釋了例如基本的 FP 概念(參考: [實(shí)用函數(shù)式 Java (PFJ)簡(jiǎn)介]https://icodewalker.com/blog/172/)以及它們?nèi)绾芜m用于 Java?;蚪忉屓绾握_使用 Java 流的文章。但是 Monads 仍然不在這些文章的重點(diǎn)之外。我不知道為什么會(huì)發(fā)生這種情況,但我會(huì)努力填補(bǔ)這個(gè)空白。

那么,Monad 是什么?

Monad 是……一種設(shè)計(jì)模式。就這么簡(jiǎn)單。這種設(shè)計(jì)模式由兩部分組成:

  • Monad 是一個(gè)值的容器。對(duì)于每個(gè) Monad,都有一些方法可以將值包裝到 Monad 中。

  • Monad 為內(nèi)部包含的值實(shí)現(xiàn)了“控制反轉(zhuǎn)”。為了實(shí)現(xiàn)這一點(diǎn),Monad 提供了接受函數(shù)的方法。這些函數(shù)接受與 Monad 中存儲(chǔ)的類型相同的值,并返回轉(zhuǎn)換后的值。轉(zhuǎn)換后的值被包裝到與源值相同的 Monad 中。

為了理解模式的第二部分,我們可以看看 Monad 的接口:

interface Monad<T> {

? ? <R> Monad<R> map(Function<T, R> mapper);

? ? <R> Monad<R> flatMap(Function<T, Monad<R>> mapper);

}


當(dāng)然,特定的 Monad 通常有更豐富的接口,但這兩個(gè)方法絕對(duì)應(yīng)該存在。

乍一看,接受函數(shù)而不是訪問值并沒有太大區(qū)別。事實(shí)上,這使 Monad 能夠完全控制如何以及何時(shí)應(yīng)用轉(zhuǎn)換功能。當(dāng)您調(diào)用 getter 時(shí),您希望立即獲得值。在 Monad 轉(zhuǎn)換的情況下可以立即應(yīng)用或根本不應(yīng)用,或者它的應(yīng)用可以延遲。缺乏對(duì)內(nèi)部值的直接訪問使 monad 能夠表示甚至尚不可用的值!

下面我將展示一些 Monad 的例子以及它們可以解決哪些問題。

Monad?缺失值或 Optional/Maybe 的場(chǎng)景

這個(gè) Monad 有很多名字——Maybe、Option、Optional。最后一個(gè)聽起來很熟悉,不是嗎? 好吧,因?yàn)?Java 8 Optional 是 Java 平臺(tái)的一部分。

不幸的是,Java Optional 實(shí)現(xiàn)過于尊崇傳統(tǒng)的命令式方法,這使得它的用處不大。特別是 Optional 允許應(yīng)用程序使用 .get() 方法獲取值。如果缺少值,甚至?xí)伋?NPE。因此,Optional 的用法通常僅限于表示返回潛在的缺失值,盡管這只是潛在用法的一小部分。

也許 Monad 的目的是表示可能會(huì)丟失的值。傳統(tǒng)上,Java 中的這個(gè)角色是為 null 保留的。不幸的是,這會(huì)導(dǎo)致許多不同的問題,包括著名的 NullPointerException。

例如,如果您期望某些參數(shù)或某些返回值可以為 null,則應(yīng)該在使用前檢查它:

public UserProfileResponse getUserProfileHandler(final User.Id userId) {

? ? final User user = userService.findById(userId);

? ? if (user == null) {

? ? return UserProfileResponse.error(USER_NOT_FOUND);

? ? }

? ?

? ? final UserProfileDetails details = userProfileService.findById(userId);

? ?

? ? if (details == null) {

? ? return UserProfileResponse.of(user, UserProfileDetails.defaultDetails());

? ? }

? ?

? ? return UserProfileResponse.of(user, details);

}


看起來熟悉嗎?當(dāng)然了。

讓我們看看 Option Monad 如何改變這一點(diǎn)(為簡(jiǎn)潔起見,使用一個(gè)靜態(tài)導(dǎo)入):

? ? public UserProfileResponse getUserProfileHandler(final User.Id userId) {

? ? ? ? return ofNullable(userService.findById(userId))

? ? ? ? ? ? ? ? .map(user -> UserProfileResponse.of(user,

? ? ? ? ? ? ? ? ? ? ? ? ofNullable(userProfileService.findById(userId)).orElseGet(UserProfileDetails::defaultDetails)))

? ? ? ? ? ? ? ? .orElseGet(() -> UserProfileResponse.error(USER_NOT_FOUND));

? ? }


請(qǐng)注意,代碼更加簡(jiǎn)潔,對(duì)業(yè)務(wù)邏輯的“干擾”也更少。

這個(gè)例子展示了 monadic 的“控制反轉(zhuǎn)”是多么方便:轉(zhuǎn)換不需要檢查 null,只有當(dāng)值實(shí)際可用時(shí)才會(huì)調(diào)用它們。

“如果/當(dāng)值可用時(shí)做某事”是開始方便地使用 Monads 的關(guān)鍵心態(tài)。

請(qǐng)注意,上面的示例保留了原始 API 的完整內(nèi)容。但是更廣泛地使用該方法并更改 API 是有意義的,因此它們將返回 Optional 而不是 null:

? ? public Optional<UserProfileResponse> getUserProfileHandler4(final User.Id userId) {

? ? ? ? return optionalUserService.findById(userId).flatMap(

? ? ? ? ? ? ? ? user -> userProfileService.findById(userId).map(profile -> UserProfileResponse.of(user, profile)));

? ? }


一些觀察:

  • 代碼更簡(jiǎn)潔,包含幾乎零樣板。

  • 所有類型都是自動(dòng)派生的。雖然并非總是如此,但在絕大多數(shù)情況下,類型是由編譯器派生的---盡管與 Scala 相比,Java 中的類型推斷較弱。

  • 沒有明確的錯(cuò)誤處理,而是我們可以專注于“快樂日子場(chǎng)景”。

  • 所有轉(zhuǎn)換都方便地組合和鏈接,不會(huì)中斷或干擾主要業(yè)務(wù)邏輯。

事實(shí)上,上面的屬性對(duì)于所有的 Monad 都是通用的。

拋還是不拋是個(gè)問題

事情并不總是如我們所愿,我們的應(yīng)用程序生活在現(xiàn)實(shí)世界中,充滿痛苦、錯(cuò)誤和失誤。有時(shí)我們可以和他們一起做點(diǎn)什么,有時(shí)不能。如果我們不能做任何事情,我們至少希望通知調(diào)用者事情并不像我們預(yù)期的那樣進(jìn)行。

在 Java 中,我們傳統(tǒng)上有兩種機(jī)制來通知調(diào)用者問題:

  • 返回特殊值(通常為空)

  • 拋出異常

除了返回 null 我們還可以返回 Option Monad(見上文),但這通常是不夠的,因?yàn)樾枰嚓P(guān)于錯(cuò)誤的詳細(xì)信息。通常在這種情況下我們會(huì)拋出異常。

但是這種方法有一個(gè)問題。事實(shí)上,甚至很少有問題。

  • 異常中斷執(zhí)行流程

  • 異常增加了很多心理開銷

異常引起的心理開銷取決于異常的類型:

  • 檢查異常迫使你要么在這里處理它們,要么在簽名中聲明它們并將麻煩轉(zhuǎn)移到調(diào)用者身上

  • 未經(jīng)檢查的異常會(huì)導(dǎo)致相同級(jí)別的問題,但編譯器不支持

不知道哪個(gè)更差。

Either Monad 來了

讓我們先分析一下這個(gè)問題。我們想要返回的是一些特殊值,它可以是兩種可能的事情之一:結(jié)果值(成功時(shí))或錯(cuò)誤(失敗時(shí))。請(qǐng)注意,這些東西是相互排斥的——如果我們返回值,則不需要攜帶錯(cuò)誤,反之亦然。

以上是對(duì)Either Monad 的幾乎準(zhǔn)確描述:任何給定的實(shí)例都只包含一個(gè)值,并且該值具有兩種可能類型之一。

任何 Monad 的接口都可以這樣描述:

interface Either<L, R> {

? ? <T> Either<T, R> mapLeft(Function<L, T> mapper);

? ? <T> Either<T, R> flatMapLeft(Function<L, Either<T, R>> mapper);

? ? <T> Either<L, T> mapLeft(Function<T, R> mapper);

? ? <T> Either<L, T> flatMapLeft(Function<R, Either<L, T>> mapper);

}


該接口相當(dāng)冗長(zhǎng),因?yàn)樗谧笥抑捣矫媸菍?duì)稱的。對(duì)于更窄的用例,當(dāng)我們需要傳遞成功或錯(cuò)誤時(shí),這意味著我們需要就某種約定達(dá)成一致——哪種類型(第一種或第二種)將保存錯(cuò)誤,哪種將保存值。

在這種情況下,Either 的對(duì)稱性質(zhì)使其更容易出錯(cuò),因?yàn)楹苋菀谉o意中交換代碼中的錯(cuò)誤和成功值。

雖然這個(gè)問題很可能會(huì)被編譯器捕獲,但最好為這個(gè)特定用例量身定制。如果我們修復(fù)其中一種類型,就可以做到這一點(diǎn)。顯然,修復(fù)錯(cuò)誤類型更方便,因?yàn)?Java 程序員已經(jīng)習(xí)慣于從單個(gè) Throwable 類型派生所有錯(cuò)誤和異常。

Result Monad — 專門用于錯(cuò)誤處理和傳播的 Either Monad

所以,讓我們假設(shè)所有錯(cuò)誤都實(shí)現(xiàn)相同的接口,我們稱之為失敗?,F(xiàn)在我們可以簡(jiǎn)化和減少接口:

interface Result<T> {

? ? <R> Result<R> map(Function<T, R> mapper);

? ? <R> Result<R> flatMap(Function<T, Result<R>> mapper);

}


Result Monad API 看起來與 Maybe Monad 的 API 非常相似。

使用這個(gè) Monad,我們可以重寫前面的例子:

? ? public Result<UserProfileResponse> getUserProfileHandler(final User.Id userId) {

? ? ? ? return resultUserService.findById(userId).flatMap(user -> resultUserProfileService.findById(userId)

? ? ? ? ? ? ? ? .map(profile -> UserProfileResponse.of(user, profile)));

}


好吧,它與上面的示例基本相同,唯一的變化是 Monad — Result 而不是 Optional。與前面的例子不同,我們有關(guān)于錯(cuò)誤的完整信息,所以我們可以在上層做一些事情。但是,盡管完整的錯(cuò)誤處理代碼仍然很簡(jiǎn)單并且專注于業(yè)務(wù)邏輯。

“承諾是一個(gè)很重要的詞。它要么成就了什么,要么破壞了什么?!?/strong>

我想展示的下一個(gè) Monad 將是 Promise Monad。

必須承認(rèn),對(duì)于 Promise 是否是 monad,我還沒有找到權(quán)威的答案。不同的作者對(duì)此有不同的看法。我純粹是從實(shí)用的角度來看它的:它的外觀和行為與其他 monad 非常相似,所以我認(rèn)為它們是一個(gè) monad。

Promise Monad 代表一個(gè)(可能還不可用的)值。從某種意義上說,它與 Maybe Monad 非常相似。

Promise Monad 可用于表示譬如對(duì)外部服務(wù)或數(shù)據(jù)庫(kù)的請(qǐng)求結(jié)果、文件讀取或?qū)懭氲?。基本上它可以表示任何需?I/O 和時(shí)間來執(zhí)行它的東西。Promise 支持與我們?cè)谄渌?Monad 中觀察到的相同的思維方式——“如果/當(dāng)價(jià)值可用時(shí)做某事”。

請(qǐng)注意,由于無法預(yù)測(cè)操作是否成功,因此讓 Promise 表示的不是 value 本身而是 Result 內(nèi)部帶有 value 是很方便的。

要了解它是如何工作的,讓我們看一下下面的示例:

...

public interface ArticleService {

? ? // Returns list of articles for specified topics posted by specified users

? ? Promise<Collection<Article>> userFeed(final Collection<Topic.Id> topics, final Collection<User.Id> users);

}

...

public interface TopicService {

? ? // Returns list of topics created by user

? ? Promise<Collection<Topic>> topicsByUser(final User.Id userId, final Order order);

}

...

public class UserTopicHandler {

? ? private final ArticleService articleService;

? ? private final TopicService topicService;

? ? public UserTopicHandler(final ArticleService articleService, final TopicService topicService) {

? ? ? ? this.articleService = articleService;

? ? ? ? this.topicService = topicService;

? ? }

? ? public Promise<Collection<Article>> userTopicHandler(final User.Id userId) {

? ? ? ? return topicService.topicsByUser(userId, Order.ANY)

? ? ? ? ? ? ? ? .flatMap(topicsList -> articleService.articlesByUserTopics(userId, topicsList.map(Topic::id)));

? ? }

}


為了提供整個(gè)上下文,我包含了兩個(gè)必要的接口,但實(shí)際上有趣的部分是 userTopicHandler() 方法。盡管這種方法的簡(jiǎn)單性令人懷疑:

  • 調(diào)用 TopicService 并檢索由提供的用戶創(chuàng)建的主題列表

  • 成功獲取主題列表后,該方法提取主題 ID,然后調(diào)用 ArticleService,獲取用戶為指定主題創(chuàng)建的文章列表

  • 執(zhí)行端到端的錯(cuò)誤處理

后記

Monads 是非常強(qiáng)大和方便的工具。使用“當(dāng)價(jià)值可用時(shí)做”的思維方式編寫代碼需要一些時(shí)間來習(xí)慣,但是一旦你開始使用它,它將讓你的生活變得更加簡(jiǎn)單。它允許將大量的心理開銷卸載給編譯器,并使許多錯(cuò)誤在編譯時(shí)而不是在運(yùn)行時(shí)變得不可能或可檢測(cè)到。


Java 設(shè)計(jì)模式 Monads 的美麗世界的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
江门市| 吉首市| 大安市| 闽侯县| 会昌县| 龙泉市| 阳城县| 西藏| 溧水县| 洛宁县| 松江区| 乐亭县| 湟中县| 洛阳市| 吕梁市| 都匀市| 莆田市| 黄龙县| 夏津县| 澜沧| 耒阳市| 翁牛特旗| 申扎县| 上杭县| 扶风县| 兰西县| 金川县| 偏关县| 荆门市| 阿巴嘎旗| 林西县| 鹤峰县| 霍城县| 德兴市| 麻江县| 城步| 建阳市| 公安县| 龙里县| 台中市| 杭锦后旗|