軟體設計一直是開發周期中最重要的階段,在設計彈性和靈活的體系結構的花費的時間越多,在将來出現變更時就越節省時間。需求總是變化的,如果不定期添加或維護功能,軟體将出現為遺留問題,并且變更成本是根據系統的結構和體系結構來确定的。在本文中,我們将讨論有助于建立易于維護和可擴充的軟體的關鍵設計原則。
假設老闆要求你寫一個将word文檔轉換成PDF的程式。這個任務看起來很簡單,隻需找到一個可靠的庫,它可以将word文檔轉換成PDF,并把它內建到你的程式中。在做了一些研究之後,你最終決定使用 Aspose.words 架構并建立了以下類:
代碼:PDFConverter.java
生活很簡單,一切都很順利!!
需求總是變化
幾個月後,一些使用者要求支援也 excel 文檔,是以你又做了一些研究,決定使用ascell.cell 。然後你找到你原來的類,并添加了一個名為 documentType 的新字段,并修改了你的方法,代碼如下:
該代碼可以為新使用者正常正常,而且仍然可以按照預期的方式為現有的使用者工作,但是一些糟糕的設計氣味開始出現在代碼中,這樣做是不完美的,當再一個新的文檔類型時,我們将無法輕松修改這個類。
1.代碼重複:正如你所看到的,在if/else塊存在類似的代碼,如果有一天再添加不同的擴充,那麼将會出現大量的重複。如果我們決定傳回一個檔案而不是一個 byte[] 那麼就必須在所有的塊中做相同的修改。
2.剛性:所有的轉換算法都是在同一種方法中進行耦合的,是以如果你改變了一些算法,其他的算法也會随之受到影響。
3.固定:上面的方法直接依賴于documentType字段,假如一些使用者在調用convertToPDF()之前忘記了設定這個字段,那将得不到預期的結果,我們也不能在任何其他項目中重用該方法,因為它依賴于字段。
4.進階子產品與架構之間的耦合:如果将來我們決定用更可靠的方式替換 Aspose 架構,那麼最終修将會改整個 PDFConverter 類,并且會有許多使用者受到影響。
正确的方式
通常情況下,并不是所有的開發人員都能夠預見未來的變化。是以,他們中的大多數人将會像我們第一次實作的那樣,完全實作程式,但是在第一次改變之後,情況就會變得很明顯,将來會發生類似的變化。是以,好的開發人員将會為了盡可能減少将來變更的成本使用正确的方式,而不是用if / else塊實作。是以我們在暴露的工具(PDFConverter)和低級轉換算法之間建立一個抽象層,并将每個算法移動到一個單獨的類中,如下所示:
代碼:Converter.java
代碼:ExcelPDFConverter.java
代碼:WordPDFConverter.java
當調用convertToPDF()時,我們強制使用者決定應該使用哪種轉換算法。
1.關注點分離(高内聚/低耦合): 現在 PDFConverter 類對程式中使用的轉換算法一無所知,它主要關注的是為使用者提供各種轉換特性,而關心轉換是如何進行的。現在,我們可以随時替換底層轉換架構,隻要我們能夠傳回預期的結果,就不會人會知道。
2.單一職責: 建立抽象層并将每個動态行為移到單獨的類之後,我們實際上删除了 convertToPDF() 方法在以前初始設計中的的多重職責,現在它隻有一個職責,就是将使用者的請求委托給抽象的轉換層。此外,轉換器接口的每個實作類現在都有一個單一的責任,即将某些文檔類型轉換為PDF。是以,每個元件都有一個被修改的理由,是以沒有回歸。
3.打開/關閉程式: 我們的程式現在對擴充開放,并且對修改關閉,當我們在未來想要支援一些新的文檔類型時,隻需要從 Converter 接口建立一個新的實作類,并且不需要修改 PDFConverter 工具,因為現在我們的工具依賴于抽象。
以下是建構應用程式架構時要遵循的最佳設計實踐:
1.将程式劃分為幾個子產品,并在每個子產品的頂部添加一個抽象層。
2.有利于抽象實作:一定要依賴抽象層,這将有利于程式将來的擴充,抽象應該應用于程式的動态部分(最有可能經常改變的部分),不一定在所有的部分,因為在過度使用的情況下是你的代碼變得非常複雜。
3.确定程式的不同方面,并将它們與保持不變的部分分開。
4.不要重複自己:永遠把重複的功能在一些工具類中,并使其通過整個程式通路,這會使你的修改變得容易得多。
5.通過抽象層隐藏低級實作:低級子產品有很高的可能性會定期更改,是以将其與進階子產品分開。
6.每個類/方法/子產品應該有一個理由去改變,是以為了減少回歸,總是給每一個類單一的責任。
7.關注點分離:每個子產品都知道其他子產品做什麼,但是它自己不知道該怎麼做。
作者簡介:
HUSSEINTEREK: programmergate.com的創始人,對軟體工程和所有與java相關的東西都充滿激情。