系統
此系統的一般理想特徵是:- 最小的複雜性- 應避免過於複雜的項目。最主要的是簡單和清晰(最好=簡單);
- 易於維護- 創建應用程式時,您必須記住它將需要支援(即使不是您),因此程式碼應該清晰明了;
- 弱耦合是程式不同部分之間的連接數量最少(最大限度地利用OOP原則);
- 可重用性-設計一個能夠在其他應用程式中重複使用其片段的系統;
- 可移植性-系統必須能夠輕鬆適應另一個環境;
- 單一風格-在不同的片段中以單一風格設計一個系統;
- 可擴展性(scalability) ——在不違反其基本結構的情況下改進系統(如果您新增或變更某些片段,這不應影響其餘部分)。
系統設計階段
- 軟體系統- 設計通用形式的應用程式。
- 分離為子系統/套件- 定義邏輯上可分離的部分並定義它們之間的交互規則。
- 將子系統劃分為類別- 將系統的各個部分劃分為特定的類別和接口,並定義它們之間的交互作用。
- 將類別劃分為方法是根據該類別的任務,對類別的必要方法進行完整的定義。方法設計 - 各個方法功能的詳細定義。
系統設計的主要原則和概念
延遲初始化習慣 應用程式在使用對象之前不會花時間創建對象,這加快了初始化過程並減少了垃圾收集器負載。但你不應該做得太過分,因為這可能會導致違反模組化。將所有設計步驟轉移到特定部分(例如 main )或像工廠一樣工作的類別可能是值得的。好的程式碼的特點之一是沒有頻繁重複的樣板程式碼。通常,此類程式碼放置在單獨的類別中,以便可以在正確的時間呼叫它。 AOP 另外,我想提一下面向方面的程式設計。這是透過引入端到端邏輯進行編程,即將重複的程式碼放入類別(方面)中,並在達到某些條件時調用。例如,當存取某個名稱的方法或存取某個類型的變數時。有時方面可能會令人困惑,因為不能立即清楚從哪裡調用程式碼,但儘管如此,這是一個非常有用的功能。特別是在快取或日誌記錄時:我們添加此功能而不向常規類別添加額外的邏輯。您可以在此處閱讀有關 OAP 的更多資訊。 Kent Beck 提出的設計簡單架構的 4 條規則- 表現力-需要明確表達類別的目的,這是透過正確的命名、小尺寸和遵守單一責任原則來實現的(我們將在下面更詳細地討論它)。
- 最少的類別和方法- 如果您希望將類別分解為盡可能小且單向的,您可能會走得太遠(反模式 - 霰彈槍法)。這項原則要求保持系統緊湊,不要走得太遠,為每個噴嚏創建一個類別。
- 缺乏重複- 令人困惑的額外代碼是糟糕的系統設計的標誌,並被移動到一個單獨的地方。
- 執行所有測試- 通過所有測試的系統是受控的,因為任何更改都可能導致測試失敗,這可以向我們表明方法內部邏輯的更改也會導致預期行為的更改。
介面
也許創建一個適當的類別的最重要的階段之一是創建一個適當的接口,它將代表一個隱藏類別的實現細節的良好抽象,同時將代表一組彼此明顯一致的方法。讓我們仔細看看 SOLID 原則之一 -介面隔離:客戶端(類別)不應該實作他們不會使用的不必要的方法。也就是說,如果我們正在談論使用最少數量的方法構建接口,這些方法旨在執行該接口的唯一任務(對我來說,這與單一職責非常相似),那麼最好創建幾個較小的方法一個而不是一個臃腫的界面。幸運的是,一個類別可以實現多個接口,就像繼承的情況一樣。您還需要記住介面的正確命名:名稱應盡可能準確地反映其任務。當然,它越短,引起的混亂就越少。通常在介面層級編寫文檔註釋,這反過來又幫助我們詳細描述該方法應該做什麼、它需要什麼參數以及它將返回什麼。班級
讓我們看看類別的內部組織。或者更確切地說,是構造類別時應該遵循的一些觀點和規則。通常,一個類別應以變數列表開頭,並按特定順序排列:- 公共靜態常數;
- 私有靜態常數;
- 私有實例變數。
班級規模
現在我想談談班級規模。 讓我們記住 SOLID 的原則之一——單一職責。 單一責任-單一責任原則。它規定每個物件只有一個目標(職責),其所有方法的邏輯都是為了確保這一目標。也就是說,基於此,我們應該避免大型、臃腫的類別(這本質上是一種反模式——「神聖物件」),如果一個類別中有很多不同的、異構邏輯的方法,我們需要思考關於將其分成幾個邏輯部分(類)。反過來,這將提高程式碼的可讀性,因為如果我們知道給定類別的大致用途,我們就不需要太多時間來理解方法的用途。您還需要留意類別名稱:它應該反映它包含的邏輯。比方說,如果我們有一個類,其名稱有 20 多個單詞,我們需要考慮重構。每個有自尊的類別都不應該有這麼多的內在變數。事實上,每個方法都與其中一個或多個方法一起使用,這會導致類別內更大的耦合(這正是它應該的樣子,因為類別應該作為一個整體)。結果,增加班級的連貫性會導致班級的連貫性減少,當然,我們的班級數量也會增加。對某些人來說,這很煩人;他們需要更多地去上課,看看特定的大型任務是如何運作的。除此之外,每個類別都是一個小模組,應該盡可能地與其他模組連接。這種隔離減少了在類別中添加額外邏輯時需要進行的更改數量。物件
封裝
這裡我們先講一下OOP的原則之一──封裝。因此,隱藏實作並不意味著在變數之間創建一個方法層(不假思索地限制透過單一方法、getter 和 setter 的訪問,這不好,因為整個封裝點都遺失了)。隱藏存取的目的是形成抽象,也就是說,類別提供了我們處理資料的通用具體方法。但用戶不需要確切地知道我們如何處理這些數據——它可以工作,這很好。德墨忒耳定律
您也可以考慮德米特法則:它是一小組規則,有助於管理類別和方法層級的複雜性。因此,假設我們有一個物件Car
並且它有一個方法 - move(Object arg1, Object arg2)
。根據迪米特定律,該方法僅限於呼叫:
- 物件本身的方法
Car
(換句話說,就是 this); - 在 中建立的物件的方法
move
; - 作為參數傳遞的物件的方法 -
arg1
,arg2
; - 內部物件的方法
Car
(同this)。
資料結構
資料結構是相關元素的集合。當將物件視為資料結構時,它是由方法處理的一組資料元素,隱含地暗示其存在。也就是說,它是一個對象,其目的是儲存和操作(處理)儲存的資料。與常規物件的主要區別在於,物件是一組對隱含存在的資料元素進行操作的方法。你明白嗎?在常規物件中,主要方面是方法,內部變數旨在其正確操作,但在資料結構中則相反:方法支援並幫助處理儲存元素,這是這裡的主要元素。一種資料結構是資料傳輸物件(DTO)。這是一個具有公共變數的類,沒有方法(或只有讀/寫方法),可以在使用資料庫、解析來自套接字的訊息等時傳遞資料。通常,此類物件中的資料不會長期存儲,而是會被保存。幾乎立即轉換為我們的應用程式所使用的實體。反過來,實體也是一種資料結構,但其目的是參與應用程式不同層級的業務邏輯,而 DTO 是將資料傳輸到應用程式或從應用程式傳輸資料。範例 DTO:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
一切似乎都很清楚,但在這裡我們了解了混合體的存在。 混合物件是包含處理重要邏輯、儲存內部元素和存取方法(取得/設定)的方法的物件。此類物件很混亂且很難添加新方法。您不應該使用它們,因為尚不清楚它們的用途 - 儲存元素或執行某種邏輯。您可以在此處了解可能的物件類型。
創建變數的原則
讓我們思考一下變量,或者更確切地說,思考一下創建變量的原則是什麼:- 理想情況下,您應該在使用變數之前立即聲明並初始化它(而不是創建它並忘記它)。
- 只要有可能,將變數宣告為final,以防止其值在初始化後發生變更。
- 不要忘記計數器變數(通常我們在某種循環中使用它們
for
,也就是說,我們不能忘記重置它們,否則它會破壞我們的整個邏輯)。 - 您應該嘗試在建構函式中初始化變數。
- 如果要選擇使用帶有引用還是不帶引用的物件(
new SomeObject()
),請選擇不帶引用( ),因為該物件一旦使用,就會在下次垃圾回收時被刪除,不會浪費資源。 - 使變數的生命週期盡可能短(變數的創建和最後一次訪問之間的距離)。
- 在循環之前立即初始化循環中使用的變量,而不是在包含循環的方法的開頭初始化。
- 始終從最有限的範圍開始,僅在必要時擴展它(您應該嘗試使變數盡可能本地化)。
- 每個變數僅用於一個目的。
- 避免具有隱藏含義的變數(該變數在兩個任務之間左右為難,這意味著它的類型不適合解決其中之一)。
方法
讓我們直接轉向邏輯的實現,即方法。-
第一條規則是緊湊性。理想情況下,一個方法不應超過 20 行,因此,如果公共方法顯著“膨脹”,則需要考慮將分離的邏輯移至私有方法中。
-
第二條規則是命令
if
、else
命令while
等中的區塊不應高度嵌套:這會顯著降低程式碼的可讀性。理想情況下,嵌套不應超過兩個區塊{}
。還建議使這些塊中的程式碼緊湊且簡單。
-
第三條規則是一個方法必須只執行一個動作。也就是說,如果一個方法執行複雜、多樣的邏輯,我們將其分成子方法。因此,該方法本身將是一個外觀,其目的是以正確的順序呼叫所有其他操作。
但是,如果操作看起來太簡單而無法建立單獨的方法呢?是的,有時這看起來就像用大砲射麻雀,但小方法可以帶來很多好處:
- 更容易閱讀程式碼;
- 在開發過程中,方法往往會變得更加複雜,如果方法最初很簡單,那麼使其功能複雜化就會更容易一些;
- 隱藏實作細節;
- 促進程式碼重用;
- 更高的代碼可靠性。
-
向下的規則是,程式碼應該從上到下閱讀:越低,邏輯深度越深,反之,越高,方法越抽象。例如,開關命令非常不緊湊且不受歡迎,但如果您不能不使用開關,則應嘗試將其盡可能移到最低級別的方法中。
-
方法參數- 有多少是理想的?理想情況下,根本沒有))但這真的會發生嗎?但是,您應該嘗試盡可能少地使用它們,因為數量越少,該方法就越容易使用,也越容易測試。如果有疑問,請嘗試猜測使用具有大量輸入參數的方法的所有場景。
-
另外,我想強調以布林標誌作為輸入參數的方法,因為這自然意味著該方法實作多個操作(如果為 true,則執行一個操作,如果為 false,則執行另一個操作)。正如我上面所寫,這不好,應該盡量避免。
-
如果一個方法有大量傳入參數(極值是7,但在2-3之後你應該考慮),你需要將一些參數分組到一個單獨的物件中。
-
如果有多個相似的方法(重載),則必須以相同的順序傳遞相似的參數:這會增加可讀性和可用性。
-
當你向方法傳遞參數時,你必須確保它們都會被使用,否則參數有什麼用呢?把它從介面上剪下來就可以了。
-
try/catch
它本質上看起來不太好,所以一個好的舉措是將其移動到一個中間的單獨方法(處理異常的方法)中:public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }
GO TO FULL VERSION