功能Java示例 第4部分–首选不变性
這是稱為“ Functional Java by Example”的系列文章的第4部分。
在上一部分中,我們討論了一些副作用,并且我想進一步詳細說明如何通過將不可變性引入代碼中來防止以意外的方式操縱數據。
如果您是第一次來,最好是從頭開始閱讀。
它有助于了解我們從何處開始以及如何在整個系列中繼續前進。
這些都是這些部分:
- 第1部分–從命令式到聲明式
- 第2部分–講故事
- 第3部分–不要使用異常來控制流程
- 第4部分–首選不變性
- 第5部分–將I / O移到外部
- 第6部分–用作參數
- 第7部分–將失敗也視為數據
- 第8部分–更多純函數
我將在每篇文章發表時更新鏈接。 如果您通過內容聯合組織來閱讀本文,請查看我博客上的原始文章。
每次代碼也被推送到這個GitHub項目 。
純功能
關于我們之前討論的內容的小結。
- 函數式編程鼓勵使用無副作用的方法(或:函數),以使代碼更易于理解且更易于推理 。 如果某個方法僅接受某些輸入并每次都返回相同的輸出(這使其成為一個純函數),則各種優化都可以在后臺進行,例如通過編譯器或緩存,并行化等。
- 我們可以再次用純函數(計算出的值)替換純函數,這稱為參考透明度 。
這是上一部分重構后當前的內容:
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->createResource(doc).thenAccept { resource ->updateToProcessed(doc, resource)}.exceptionally { e ->updateToFailed(doc, e)}}}private CompletableFuture<Resource> createResource(doc) {webservice.create(doc)}private boolean isImportant(doc) {doc.type == 'important'}private void updateToProcessed(doc, resource) {doc.apiId = resource.iddoc.status = 'processed'documentDb.update(doc)}private void updateToFailed(doc, e) {doc.status = 'failed'doc.error = e.messagedocumentDb.update(doc)}}我們的updateToProcessed和updateToFailed是“不純的”-它們都將更新現有文檔in 。 從Java的返回類型void可以看出,這意味著:什么都不會出來 。 下沉Kong。
private void updateToProcessed(doc, resource) {doc.apiId = resource.iddoc.status = 'processed'documentDb.update(doc) }private void updateToFailed(doc, e) {doc.status = 'failed'doc.error = e.messagedocumentDb.update(doc) }這些類型的方法都圍繞您的典型代碼庫。 因此,隨著代碼庫的增長,在將數據傳遞給這些方法之一之后,往往很難對數據的狀態進行推理。
請考慮以下情形:
def newDocs = [new Doc(title: 'Groovy', status: 'new'),new Doc(title: 'Ruby', status: 'new') ]feedHandler.handle(newDocs)println "My new docs: " + newDocs // My new docs: // [Doc(title: Groovy, status: processed), // Doc(title: Ruby, status: processed)] // WHAT? My new documents aren't that 'new' anymore罪魁禍首一直在破壞我文件的地位; 首先,它們是“新的”,其次不是。 那不行! 一定是該死的FeedHandler。 誰創作的東西? 為什么會觸碰我的數據?
考慮另一種情況,即有多個參與者處理您的業務。
def favoriteDocs = [new Doc(title: 'Haskell'),new Doc(title: 'OCaml'),new Doc(title: 'Scala') ]archiver.backup(favoriteDocs)feedHandler.handle(favoriteDocs)mangleService.update(favoriteDocs)userDao.merge(favoriteDocs, true)println "My favorites: " + favoriteDocs // My favorites: [] // WHAT? Empty collection? Where are my favorites????我們從一組項目開始,然后在4種方法中發現我們的數據不見了。
在每個人都可以改變任何事物的世界中,很難在任何給定時間推斷任何狀態。
它本身甚至還不是“全局狀態”,任何擁有(引用到)數據的人都可以清除傳遞給方法的集合,并可以更改變量。
首選不變性
那是什么 如果對象在實例化后不更改其狀態,則該對象是不可變的。
看起來合理吧?
圖片來源: 應對并適應持續變化
關于如何使用您的特定語言進行處理,這里有大量資源。 例如,Java默認不支持不變性。 我必須做些工作。
如果有第三方在處理過程中發生問題并更改數據(例如清除我的收藏夾),則可以通過將我的收藏夾傳遞到不可修改的包裝中來快速清除麻煩制造者,例如
def data = [... ]// somewhere inside 3rd-party code data.clear()// back in my code: // data is empty *snif*預防故障:
def data = Collections.unmodifiableCollection([])// somewhere inside 3rd-party code data.clear() // HAHAA, throws UnsupportedOperationException在您自己的代碼庫中,我們可以通過最小化可變數據結構來防止意外的副作用(例如,在某處更改我的數據)。
在大多數FP語言中,例如Haskell , OCaml和Scala ,默認情況下 ,語言本身會促進不變性 。 雖然不是真正的FP語言,但使用ES6編寫不可變JavaScript也趨于成為一種好習慣。
首先只讀
使用到目前為止所學的原理,并努力防止意外的副作用,我們希望確保實例化實例后, 不能對Doc類進行任何更改 –甚至不包括updateToProcessed / updateToFailed方法。
這是我們當前的課程:
class Doc {String title, type, apiId, status, error }Groovy不需要進行使Java類變為不可變的所有手動工作,而是借助Immutable -annotation進行了搶救。
當放置在類上時,Groovy編譯器進行了一些增強,因此創建后再也沒有人可以更新其狀態。
@Immutable class Doc {String title, type, apiId, status, error }該對象實際上變為“只讀”,并且任何嘗試更新屬性的操作都將導致恰當命名的ReadOnlyPropertyException
private void updateToProcessed(doc, resource) {doc.apiId = resource.id // BOOM! // throws groovy.lang.ReadOnlyPropertyException: // Cannot set readonly property: apiId... }private void updateToFailed(doc, e) {doc.status = 'failed' // BOOM! // throws groovy.lang.ReadOnlyPropertyException: // Cannot set readonly property: status... }但是,等等,這是否意味著updateToProcessed / updateToFailed方法實際上將無法將文檔status更新為“已處理”或“失敗”?
吉普,這就是不變性帶給我們的。 如何修復邏輯?
復制第二
Haskell關于“不可變數據”的指南為我們提供了如何進行操作的建議:
純功能程序通常在不可變數據上運行。 代替更改現有值,而是創建更改的副本并保留原始副本。 由于結構的未更改部分無法修改,因此它們通常可以在舊副本和新副本之間共享,從而節省了內存。
答:我們克隆它!
我們沒有更新的原始數據,我們應該做的一個副本-原來不是我們的,應保持不變。 我們的Immutable -annotation支持一個名為copyWith的參數。
@Immutable(copyWith = true) class Doc {String title, type, apiId, status, error }因此,我們將更改方法以制作狀態更改 (以及api id和錯誤消息) 的原始副本,并返回此副本 。
(總是返回Groovy方法中的最后一條語句,不需要顯式的return關鍵字)
private Doc setToProcessed(doc, resource) {doc.copyWith(status: 'processed',apiId: resource.id) }private Doc setToFailed(doc, e) {doc.copyWith(status: 'failed',error: e.message) }數據庫邏輯也已上移,將返回的副本存儲起來。
我們已經控制了我們的狀態!
現在就這樣
如果您以Java程序員的身份擔心過多的對象實例化對性能的影響,請在此處發表一篇令人放心的文章 。
作為參考,這是重構代碼的完整版本。
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->createResource(doc).thenAccept { resource ->documentDb.update(setToProcessed(doc, resource))}.exceptionally { e ->documentDb.update(setToFailed(doc, e))}}}private CompletableFuture<Resource> createResource(doc) {webservice.create(doc)}private boolean isImportant(doc) {doc.type == 'important'}private Doc setToProcessed(doc, resource) {doc.copyWith(status: 'processed',apiId: resource.id)}private Doc setToFailed(doc, e) {doc.copyWith(status: 'failed',error: e.message)}}翻譯自: https://www.javacodegeeks.com/2018/06/functional-java-part-4-immutability.html
總結
以上是生活随笔為你收集整理的功能Java示例 第4部分–首选不变性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 孔子简介资料大全 有关孔子的简介
- 下一篇: servlet 异常处理_Servlet