JavaRush /Java Blog /Random-TW /喝咖啡休息#85。我透過艱難的方式學到了三個 Java 課程。如何在程式碼中使用 SOLID 原則

喝咖啡休息#85。我透過艱難的方式學到了三個 Java 課程。如何在程式碼中使用 SOLID 原則

在 Random-TW 群組發布

我艱難學到的三個 Java 教訓

來源:Medium 學習 Java 很困難。我從錯誤中學到了教訓。現在你也可以從我的錯誤和痛苦經歷中學習,而你不一定需要擁有這些。 喝咖啡休息#85。 我透過艱難的方式學到了三個 Java 課程。 如何在程式碼中使用 SOLID 原則 - 1

1. Lambda 可能會帶來麻煩。

Lambda 表達式通常超過 4 行程式碼,並且比預期的要大。這會給工作記憶帶來負擔。您需要從 lambda 更改變數嗎?你不能那樣做。為什麼?如果 lambda 可以存取呼叫站點變量,則可能會出現線程問題。因此您無法更改 lambda 中的變數。但 lambda 中的快樂路徑運作得很好。運行時失敗後,您將收到以下回應:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
追蹤 lambda 堆疊追蹤很困難。這些名稱令人困惑且難以追蹤和調試。更多 lambda = 更多堆疊追蹤。調試 lambda 的最佳方法是什麼?使用中間結果。
map(elem -> {
 int result = elem.getResult();
 return result;
});
另一個好方法是使用先進的 IntelliJ 調試技術。使用 TAB 選擇要偵錯的程式碼並將其與中間結果結合。 「當我們停在包含 lambda 的行時,如果我們按 F7(步入),那麼 IntelliJ 會突出顯示需要調試的片段。我們可以使用 Tab 切換要調試的區塊,一旦我們決定,請再次按 F7。” 如何從 lambda 存取呼叫站點變數?您只能存取最終或實際最終變數。您需要圍繞呼叫位置變數建立一個包裝器。使用AtomicType或使用您自己的類型。您可以使用 lambda 變更已建立的變數包裝器。如何解決堆疊追蹤問題?使用命名函數。這樣,您可以快速找到負責的程式碼、檢查邏輯並解決問題。使用命名函數來去除神秘的堆疊追蹤。相同的 lambda 是否重複?將其放在命名函數中。您將有一個參考點。每個 lambda 都有一個產生的函數,這使得追蹤變得困難。
lambda$yourNamedFunction
lambda$0
命名函數解決不同的問題。大 lambda。命名函數分解大型 lambda,創建更小的程式碼片段,並建立可插入函數。
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. 列表的問題

您需要使用清單(Lists)。您需要一個 HashMap來儲存資料。對於角色,您將需要一個 TreeMap。這樣的例子還在繼續。您無法避免使用集合。如何列清單?您需要什麼樣的清單?它應該是不可變的還是可變的?所有這些答案都會影響程式碼的未來。提前選擇正確的列表,這樣您就不會後悔。 Arrays::asList建立一個「端對端」清單。你不能用這份清單做什麼?您無法調整它的大小。他是不可改變的。你在這裡能做什麼?指定元素、排序或其他不影響大小的操作。請小心使用Arrays::asList,因為它的大小是不可變的,但它的內容卻不是。 new ArrayList()建立一個新的「可變」清單。建立的清單支援哪些操作?就是這樣,這就是要小心的原因。使用new ArrayList()從不可變清單建立可變清單。 List::of建立一個「不可變」集合。其大小和內容在一定條件下不變。如果內容是原始數據,例如int,則列表是不可變的。看一下下面的例子。
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### 使用可變內容測試 listOF ### a123
您需要建立不可變物件並將它們插入到List::of中。但是,List::of不提供任何不變性保證。 List::of提供不變性、可靠性和可讀性。知道何時使用可變結構以及何時使用不可變結構。不應變更的參數清單應位於不可變清單中。可變列表可以是可變列表。了解創建可靠程式碼所需的集合。

3.註解會減慢你的速度

你使用註解嗎?你了解他們嗎?你知道他們在做什麼嗎?如果您認為Logged註解適用於每種方法,那麼您就錯了。我使用Logged來記錄方法參數。令我驚訝的是,它不起作用。
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
這段程式碼有什麼問題?這裡有很多配置摘要。你會多次遇到這種情況。配置與常規代碼混合。本身並不壞,但它吸引了你的眼球。需要註解來減少樣板程式碼。您不需要為每個端點編寫日誌記錄邏輯。無需設定事務,使用@Transactional。註釋透過提取程式碼來減少模式。這裡沒有明顯的贏家,因為兩者都在遊戲中。我仍然使用 XML 和註釋。當您發現重複模式時,最好將邏輯移至註解中。例如,日誌記錄是一個很好的註解選項。寓意:不要過度使用註釋,也不要忘記 XML。

獎勵:您可能會遇到可選問題

您將使用Optional中的orElse。當您不傳遞orElse常數時,會發生不良行為。您應該意識到這一點,以防止將來出現問題。讓我們來看幾個例子。當getValue(x)傳回值時,執行getValue(y)如果getValue(x)傳回非空可選值,則執行orElse中的方法。
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
使用orElseGet。它不會執行非空Options的程式碼。
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

結論

學習Java很難。你不可能在 24 小時內學會 Java。磨練你的技能。花時間學習並在工作中表現出色。

如何在程式碼中使用 SOLID 原則

來源:Cleanthecode 編寫可靠的程式碼需要堅實的原則。在某些時候,我們都必須學習如何程式設計。老實說。我們很愚蠢。我們的程式碼是相同的。感謝上帝,我們有固體。 喝咖啡休息#85。 我透過艱難的方式學到了三個 Java 課程。 如何在程式碼中使用 SOLID 原則 - 2

堅實的原則

那麼如何寫 SOLID 程式碼呢?其實很簡單。您只需要遵循以下五個規則:
  • 單一責任原則
  • 開閉原則
  • 里氏替換原理
  • 介面分離原理
  • 依賴倒置原則
不用擔心!這些原則比看起來簡單得多!

單一責任原則

羅伯特·C·馬丁 (Robert C. Martin) 在他的書中這樣描述這一原則:“一個類別應該只有一個改變的理由。” 我們一起來看兩個例子。

1. 不該做什麼

我們有一個名為User的類,它允許使用者執行以下操作:
  • 註冊帳戶
  • 登入
  • 首次登入時收到通知
這個類別現在有幾個職責。如果註冊過程發生變化,User類別也會發生變化。如果登入流程或通知流程發生變化,也會發生同樣的情況。這意味著該類別已超載。他有太多的責任。解決此問題的最簡單方法是將責任轉移給您的類,以便User類僅負責組合類。如果流程發生變化,您就會有一個明確的、單獨的類別需要更改。

2. 做什麼

想像一個應該向新使用者顯示通知的類別FirstUseNotification。它將由三個功能組成:
  • 檢查通知是否已經顯示
  • 顯示通知
  • 將通知標記為已顯示
這個類別有多個改變的理由嗎?不。該類別有一個明確的功能 - 顯示新用戶的通知。這意味著班級有一個改變的理由。也就是說,如果這個目標改變。所以,這個類別並沒有違反單一責任原則。當然,有一些事情可能會發生變化:通知標記為已讀的方式可能會發生變化,或者通知的顯示方式可能會發生變化。然而,由於課程的目的是明確且基本的,所以這很好。

開閉原則

開閉原則是 Bertrand Meyer 提出的:“軟體物件(類別、模組、函數等)應該對擴展開放,但對修改關閉。” 這個原理其實很簡單。您必須編寫程式碼,以便可以在不更改原始程式碼的情況下向其中添加新功能。這有助於防止出現需要更改依賴已修改類別的類別的情況。然而,這項原則的實施卻困難得多。邁耶建議使用繼承。但這會帶來牢固的聯繫。我們將在介面分離原則和依賴倒置原則中討論這一點。於是Martin想出了一個更好的方法:使用多態性。此方法不使用傳統的繼承,而是使用抽象基底類別。這樣,繼承規範可以被重複使用,而無需實作。介面可以編寫一次,然後關閉以進行更改。新函數必須實作該介面並擴展它。

里氏替換原理

這項原則是由圖靈獎得主芭芭拉·利斯科夫(Barbara Liskov)發明的,她因對程式語言和軟體方法論的貢獻而獲得了圖靈獎。在她的文章中,她將自己的原則定義如下:“程式中的物件應該可以用其子類型的實例替換,而不影響程式的正確執行。” 讓我們以程式設計師的身份來看看這個原則。想像我們有一個正方形。它可能是一個矩形,這聽起來很合乎邏輯,因為正方形是矩形的特殊形狀。這就是里氏替換原理可以發揮作用的地方。無論您希望在程式碼中的何處看到矩形,也有可能出現正方形。現在假設您的矩形有SetWidthSetHeight方法。這意味著廣場也需要這些手段。不幸的是,這沒有任何意義。這意味著這裡違反了里氏替換原則。

介面分離原理

與所有原則一樣,介面分離的原則比看起來簡單得多:“許多特定於客戶端的介面比一個通用介面更好。” 與單一責任原則一樣,目標是減少副作用和所需的更改數量。當然,沒有人故意寫這樣的程式碼。但很容易遇到。還記得前面原理中的平方嗎?現在想像我們決定實施我們的計劃:我們從一個矩形生成一個正方形。現在我們強迫正方形實作setWidthsetHeight,這可能不會做任何事情。如果他們這樣做,我們可能會破壞一些東西,因為寬度和高度不是我們所期望的。對我們來說幸運的是,這意味著我們不再違反里氏替換原則,因為我們現在允許在任何使用矩形的地方使用正方形。然而,這產生了一個新問題:我們現在違反了分離介面的原則。我們強制衍生類別實作它選擇不使用的功能。

依賴倒置原則

最後一個原則很簡單:高層模組應該是可重用的,並且不應該受到低層模組變更的影響。
  • A. 高層模組不應該依賴低層模組。兩者都必須依賴抽象(例如介面)。
  • B. 抽像不應依賴細節。細節(具體實作)必須取決於抽象。
這可以透過實現分離高階模組和低階模組的抽象來實現。該原理的名稱表明依賴的方向發生變化,但事實並非如此。它只是透過在它們之間引入抽象來分離依賴關係。結果,您將獲得兩個依賴項:
  • 高層模組,取決於抽象
  • 依賴相同抽象的低階模組
這看起來可能很困難,但事實上,如果你正確應用開/閉原則和里氏替換原則,它就會自動發生。就這樣!您現在已經了解了支撐 SOLID 的五個核心原則。遵循這五個原則,您可以使您的程式碼令人驚嘆!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION