摘要:軟件設(shè)計(jì)原則這篇文章主要討論如何以健壯的方式應(yīng)對(duì)變化的需求,從而保持良好的編程習(xí)慣。前言軟件設(shè)計(jì)是開發(fā)周期中最重要的一個(gè)環(huán)節(jié)。識(shí)別出系統(tǒng)會(huì)發(fā)生變化的部分,并將其和不變的部分分開。
軟件設(shè)計(jì)原則
這篇文章主要討論如何以健壯的方式應(yīng)對(duì)變化的需求,從而保持良好的編程習(xí)慣。
前言軟件設(shè)計(jì)是開發(fā)周期中最重要的一個(gè)環(huán)節(jié)。在實(shí)現(xiàn)彈性和靈活的設(shè)計(jì)上花的時(shí)間越多,未來在面對(duì)需求變更時(shí)節(jié)約的時(shí)間就越多。
需求總是在變化--如果沒有定期加入新功能,或是維護(hù)現(xiàn)有功能,軟件很快就會(huì)成為遺棄產(chǎn)物--而這些變化帶來的開銷是由系統(tǒng)的架構(gòu)和體系結(jié)構(gòu)決定的。在這篇文章中,我們將會(huì)討論一個(gè)關(guān)鍵的設(shè)計(jì)原則,該設(shè)計(jì)原則能幫助我們創(chuàng)建易于維護(hù)和擴(kuò)展的軟件。
一個(gè)實(shí)際場(chǎng)景假設(shè)你的老板讓你創(chuàng)建一個(gè)將Word文件轉(zhuǎn)化為PDF文件的應(yīng)用。這個(gè)任務(wù)看上去很簡單--你要做的就是找到一個(gè)可靠的將Word轉(zhuǎn)化為PDF的庫,并將這個(gè)庫插入到你的應(yīng)用中。在一番查找之后,假設(shè)你決定使用Aspose.words插件,并且新建了這樣一個(gè)類:
/** * A utility class which converts a word document to PDF * @author Hussein * */ public class PDFConverter { /** * 這個(gè)方法傳入一個(gè)待轉(zhuǎn)化的文檔作為參數(shù)并返回轉(zhuǎn)化后的文檔 * @param fileBytes * @throws Exception */ public byte[] convertToPDF(byte[] fileBytes) throws Exception { // 我們確定輸入總是一個(gè)WORD格式的文件,所以我們直接用aspose.words框架進(jìn)行轉(zhuǎn)化 InputStream input = new ByteArrayInputStream(fileBytes); com.aspose.words.Document wordDocument = new com.aspose.words.Document(input); ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream(); wordDocument.save(pdfDocument, SaveFormat.PDF); return pdfDocument.toByteArray(); } }
現(xiàn)在一切運(yùn)轉(zhuǎn)正常!生活多么美好!
需求當(dāng)然變更啦在幾個(gè)月以后,一些客戶要求還能夠支持轉(zhuǎn)換Excel文件。于是你經(jīng)過一番研究后,決定使用Aspose.cells插件。然后你回到了之前創(chuàng)建的那個(gè)類,添加了一個(gè)新的變量`documentType·,修改后的代碼如下:
public class PDFConverter { // 我們不想影響現(xiàn)有的功能 // 默認(rèn)情況下,這個(gè)類將WORD轉(zhuǎn)化為PDF // 當(dāng)用戶將該變量設(shè)為EXCEL時(shí),會(huì)將EXCEL轉(zhuǎn)化為PDF /** public String documentType = "WORD"; * 這個(gè)方法傳入一個(gè)待轉(zhuǎn)化的文檔作為參數(shù)并返回轉(zhuǎn)化后的文檔 * @param fileBytes * @throws Exception */ public byte[] convertToPDF(byte[] fileBytes) throws Exception { if (documentType.equalsIgnoreCase("WORD")) { InputStream input = new ByteArrayInputStream(fileBytes); com.aspose.words.Document wordDocument = new com.aspose.words.Document(input); ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream(); wordDocument.save(pdfDocument, SaveFormat.PDF); return pdfDocument.toByteArray(); } else { InputStream input = new ByteArrayInputStream(fileBytes); Workbook workbook = new Workbook(input); PdfSaveOptions saveOptions = new PdfSaveOptions(); saveOptions.setCompliance(PdfCompliance.PDF_A_1_B); ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream(); workbook.save(pdfDocument, saveOptions); return pdfDocument.toByteArray(); } } }
這段代碼對(duì)新客戶來說是完美的(現(xiàn)有的客戶也可以如期使用它),但是代碼中開始出現(xiàn)了壞味道。這意味著,我們的修改并不完美。當(dāng)出現(xiàn)新的文檔類型時(shí),我們不能簡單的修改這個(gè)類。
代碼的重復(fù):如你所見,在if/else塊中出現(xiàn)了相似的代碼。如果某天我們?cè)O(shè)法擴(kuò)展這段代碼,我們將會(huì)產(chǎn)生大量的重復(fù)代碼。除此以外,如果我們以后決定,比如,返回一個(gè)file而不是byte[],那么我們需要在所有的代碼快中進(jìn)行重復(fù)的修改。
僵硬:所有的轉(zhuǎn)化算法在同一個(gè)方法中高度耦合,所以當(dāng)你改變其中某個(gè)算法時(shí),很有可能會(huì)影響別的算法。
固定性:上面的方法直接依賴于documentType變量。一些用戶在使用方法converToPDF之前可能會(huì)忘記設(shè)置該變量,所以他們無法得到預(yù)期的結(jié)果。而且,因?yàn)檫@個(gè)方法依賴于該變量,我們無法在別的項(xiàng)目中重用該方法。
高層模塊額底層框架的耦合:如果我們后面出于某種原因,決定將Aspose框架換成另一個(gè)更可靠的框架,我們將會(huì)需要修改整個(gè)PDFConverter類,很多用戶將會(huì)受到影響。
正確的方式通常情況下,開發(fā)者無法預(yù)見未來的變化,因此初次開發(fā)時(shí)我們會(huì)將其實(shí)現(xiàn)成第一個(gè)class那樣。但是,在第一次變更后,就明確知道了未來可能會(huì)出現(xiàn)類似的變更。所以,優(yōu)秀的開發(fā)者會(huì)采取正確的實(shí)踐減少未來變更的開銷,而不是用if/else強(qiáng)行解決。所以,我們?cè)诠ぞ邔?PDFConverter)和底層的轉(zhuǎn)化算法之間,添加了一個(gè)抽象層,并將所有的算法移動(dòng)到多帶帶的類中,如下:
/** * 這個(gè)接口代表一個(gè)抽象算法,用于將任何類型的文檔轉(zhuǎn)化為PDF * @author Hussein */ public interface Converter { public byte[] convertToPDF(byte[] fileBytes) throws Exception; }
/** * 這個(gè)類包含將Excel文檔轉(zhuǎn)化為PDF的算法 * @author Hussein * */ public class ExcelPDFConverter implements Converter { public byte[] convertToPDF(byte[] fileBytes) throws Exception { InputStream input = new ByteArrayInputStream(fileBytes); Workbook workbook = new Workbook(input); PdfSaveOptions saveOptions = new PdfSaveOptions(); saveOptions.setCompliance(PdfCompliance.PDF_A_1_B); ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream(); workbook.save(pdfDocument, saveOptions); return pdfDocument.toByteArray(); }; }
/** * 這個(gè)類持有將Word文檔轉(zhuǎn)化為PDF的算法 * @author Hussein * */ public class WordPDFConverter implements Converter { @Override public byte[] convertToPDF(byte[] fileBytes) throws Exception { InputStream input = new ByteArrayInputStream(fileBytes); com.aspose.words.Document wordDocument = new com.aspose.words.Document(input); ByteArrayOutputStream pdfDocument = new ByteArrayOutputStream(); wordDocument.save(pdfDocument, SaveFormat.PDF); return pdfDocument.toByteArray(); } }
public class PDFConverter { /** * 這個(gè)方法接收待轉(zhuǎn)化文檔作為參數(shù)并且返回轉(zhuǎn)化后的文檔 * @param fileBytes * @throws Exception */ public byte[] convertToPDF(Converter converter, byte[] fileBytes) throws Exception { return converter.convertToPDF(fileBytes); } }
我們強(qiáng)迫用戶在調(diào)用convertToPDF()方法時(shí)決定轉(zhuǎn)化算法。
這樣做的好處?關(guān)注點(diǎn)分離(高內(nèi)聚/低耦合):PDFConverter類現(xiàn)在對(duì)應(yīng)用中使用的轉(zhuǎn)化算法一無所知。它只關(guān)注于想用戶提供各種轉(zhuǎn)化功能,而不去關(guān)心轉(zhuǎn)化是如何實(shí)現(xiàn)的。現(xiàn)在,只要能夠返回預(yù)期的結(jié)果,我們就能夠在沒有人注意到的情況話,替換底層的轉(zhuǎn)換框架。
單一職責(zé):在創(chuàng)建了抽象層,并將每個(gè)動(dòng)態(tài)的行為移動(dòng)到各個(gè)類之后,我們能夠刪除原始設(shè)計(jì)中convertToPDF()方法持有的多個(gè)職責(zé)。現(xiàn)在它只有一個(gè)職責(zé),就是將客戶的請(qǐng)求委托給抽象轉(zhuǎn)化層。除此以外,Converter接口的每個(gè)具體實(shí)現(xiàn)都只有將某種類型的文檔轉(zhuǎn)化為PDF這一個(gè)職責(zé)。因此,每個(gè)組件只可能因?yàn)閱蝹€(gè)原因被修改,不會(huì)相互影響。
開閉原則:我們的應(yīng)用現(xiàn)在對(duì)擴(kuò)展開放,對(duì)更改關(guān)閉。無論何時(shí)我們想要添加對(duì)某種文檔的支持,我們只需要?jiǎng)?chuàng)建Converter接口的一個(gè)新的具體類,然后這個(gè)新的類型就會(huì)立刻被支持,而無需修改PDFConverter工具類,因?yàn)樵摴ぞ哳惉F(xiàn)在依賴于抽象接口。
本文中學(xué)習(xí)到的設(shè)計(jì)原則當(dāng)你創(chuàng)建你自己系統(tǒng)的體系結(jié)構(gòu)時(shí),以下是一些最佳實(shí)踐:
將應(yīng)用拆分成幾個(gè)模塊,并且在每個(gè)模塊之上添加抽象層。
抽象優(yōu)先于實(shí)現(xiàn):確保總是依賴于抽象層。這會(huì)使你的應(yīng)用對(duì)未來的擴(kuò)展開放。抽象技術(shù)應(yīng)使用于系統(tǒng)的動(dòng)態(tài)部分(即最可能頻繁變化的部分)而不必使用于所有部分。濫用它會(huì)增加代碼的復(fù)雜度。
識(shí)別出系統(tǒng)會(huì)發(fā)生變化的部分,并將其和不變的部分分開。
不要重復(fù):將重復(fù)的功能放在工具類中,使其在整個(gè)應(yīng)用中都可以訪問。這將會(huì)使變更更簡單一些。
通過抽象機(jī)制隱藏低層實(shí)現(xiàn):低層的模塊有很大的可能會(huì)頻繁變更。所以將它們和高層模塊分開。
每個(gè)類/方法/模塊應(yīng)當(dāng)只有一個(gè)變更的理由,所以只給它們一個(gè)職責(zé)。
分離關(guān)注點(diǎn):每個(gè)模塊知道另一個(gè)模塊做什么,但無需知道它們?cè)趺醋觥?/p>
想要了解更多開發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號(hào)!將會(huì)不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70983.html
摘要:前言這周我準(zhǔn)備介紹一個(gè)有趣的但是很少使用的方法按照合約編程,又稱為合約編程,是一種軟件設(shè)計(jì)的方法。這些規(guī)則被稱為合約,可以比擬為商業(yè)合同中的條件和義務(wù)。通過將檢查和異常拋出指令包裝到方法中,人們可以很容易地實(shí)現(xiàn)合約式編程。 前言 這周我準(zhǔn)備介紹一個(gè)有趣的但是很少使用的方法 按照合約編程,又稱為合約編程,是一種軟件設(shè)計(jì)的方法。它規(guī)定了軟件設(shè)計(jì)師應(yīng)該為軟件組件定義正式,精確和可驗(yàn)證的接口規(guī)...
摘要:無需檢查的異常也是的子類。從低層拋出的需檢查異常強(qiáng)制要求調(diào)用方捕獲或是拋出該異常。當(dāng)前執(zhí)行的線程將會(huì)停止并報(bào)告該異常。單元測(cè)試允許我在使用中查看異常,并且作為一個(gè)可以被執(zhí)行的文檔來使用。不要捕獲最高層異常繼承的異常同樣是的子類。 前言 異常處理的問題之一是知道何時(shí)以及如何去使用它。我會(huì)討論一些異常處理的最佳實(shí)踐,也會(huì)總結(jié)最近在異常處理上的一些爭論。 作為程序員,我們想要寫高質(zhì)量的能夠解...
摘要:什么是為執(zhí)行字節(jié)碼提供一個(gè)運(yùn)行環(huán)境。它的實(shí)現(xiàn)主要包含三個(gè)部分,描述實(shí)現(xiàn)規(guī)格的文檔,具體實(shí)現(xiàn)和滿足要求的計(jì)算機(jī)程序以及實(shí)例具體執(zhí)行字節(jié)碼。該類先被轉(zhuǎn)化為一組字節(jié)碼并放入文件中。字節(jié)碼校驗(yàn)器通過字節(jié)碼校驗(yàn)器檢查格式并找出非法代碼。 什么是Java Development Kit (JDK)? JDK通常用來開發(fā)Java應(yīng)用和插件。基本上可以認(rèn)為是一個(gè)軟件開發(fā)環(huán)境。JDK包含Java Run...
摘要:讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。 前言 很多人都在討論數(shù)據(jù)的指數(shù)型增長,以及我們將會(huì)有比想象的還要大的數(shù)據(jù)量。但是,很少有人從數(shù)據(jù)庫的角度談?wù)撨@個(gè)問題。隨著數(shù)據(jù)量的暴漲,數(shù)據(jù)庫也需要隨之升級(jí)。這也是為什么既要了解如...
閱讀 746·2023-04-26 01:30
閱讀 3301·2021-11-24 10:32
閱讀 2179·2021-11-22 14:56
閱讀 1979·2021-11-18 10:07
閱讀 553·2019-08-29 17:14
閱讀 624·2019-08-26 12:21
閱讀 3103·2019-08-26 10:55
閱讀 2940·2019-08-23 18:09