還記得我的軟件工程老師是這么說的:軟件應該往高內聚,低耦合的方向進行設計。
當時,還身為一個初學者的我,不太明白老師的這句話——既然面向對象提供給了我們”繼承“這種高耦合的概念,那為何我們還要低耦合高內聚呢?難道放着繼承的概念不用,而改為面向過程嗎?
帶着這一疑問,我請教了我的老師,他給我的回答是:通過接口來分隔分離邏輯,就可以達到低耦合的效果。
我們來回顧一下前一篇所學習的"控制反轉"設計思想,其實質就是上面所說的"通過接口來分離邏輯"。
但以上一篇的示例來說,達到這個目標,還有一些欠缺,首先讓我們來回顧一下上一篇的代碼內容
/// <summary> /// 這是一個負責登錄的接口類型 /// </summary> public interface ILoginChecker { /// <summary> /// 登錄,需要的參數是用戶名和密碼,需要返回一個bool類型表示登錄是否成功 /// </summary> /// <param name="loginName"></param> /// <param name="password"></param> /// <returns></returns> bool Login(string loginName, string password); } public class LoginWindow : Form { private ILoginChecker loginChecker; /// <summary> /// 通過構造函數決定使用哪個ILoginChecker來負責登錄流程 /// </summary> /// <param name="chcker"></param> public LoginWindow(ILoginChecker chcker) { this.loginChecker = chcker; } void LoginButton_Click(object sender, EventArgs e) { string loginName = null, password = null; //從控件上取值 //判斷空值 //等一切OK時 if (this.loginChecker.Login(loginName, password)) { //登錄成功 } else { //登錄失敗 } } }
肯定會有一些人帶着這樣的疑問:在代碼的某個地方一定寫着類型這樣的內容:
///實例化某個實現了ILoginChecker接口的類型 ILoginChecker checker = new XXXLoginChecker(); LoginWindow window = new LoginWindow(checker); window.Show();
那么隨着以后功能的更新,在不斷地修改這里的ILoginChecker checker = new XXXLoginChecker();
時間久了,記不得這段代碼寫在哪兒,也是很頭疼的一件事,最重要的是,依然沒達到”易維護“的效果,和之前相同,每次的須求改更,或是環境變化,都需要重寫這句話。
如果能有一個容器,能夠在運行時(注1)判斷使用哪個ILoginChecker類型,那就方便多了。
運行時:與"編譯時"相對應,比如我說定義一個變量Int32 i,這個i的類型就是Int32,這是在編譯時決定的,因為程序在由代碼編譯為程序時,已經可以確定i就是一個Int32類型了。
再比如說我定義一個變量Object obj,這個obj的類型雖然是Object,但是會根據實際的賦值,發生類型的變換,而賦的什么值給它,程序在編譯時是無法得知的,只有在運行到這里的時候才能知道,這就叫運行時。
我們來假設一個現實場景:
場景中,我們把ILoginChecker這個接口
1、門衛是由保安公司派出的
2、保安公司根據每個需求方的要求不同,配備不同的保安
3、保安公司擁有滿足各種需求的保安
這樣一來,我們就要再補充一個相當於是保安公司的類型了,用來創建ILoginChecker實例
所以我們起個名字叫LoginCheckerFactory
里面只有一個主要方法:CreateLoginChecker,根據我們指定的名稱,返回一個具體的IloginChecker實例,我們來看一下示例代碼:
class LoginCheckerFactory { public ILoginChecker CreateLoginChecker(string checkerName) { switch (checkerName) { case "Database": return new DatabaseLoginChecker(); //由數據庫完成的登錄驗證 case "WebService": return new WebServiceLoginChecker(); //由WebService完成的登錄驗證 case " TCP": return new TCPLoginChecker(); //由TCP通訊完成的登錄驗證 default: throw new ApplicationException("不存在這個名稱的Checker實例"); } } }
此時,你會發現,登錄的判定流程是依賴於一個字符串,Database或WebService或TCP。到了這一步,我想大部分人都明白,這個字符串只要寫在配置文件里,就大功告成了。
然后項目發布,在不同的運行環境中間,我只需要改一下配置文件,就能實現各種方式的登錄了。
對於更新與維護是同樣的便捷,你可以把不同版本的ILoginChecker都放在這個工廠里,然后根據外部的一些版本號來更改實例。
在BUG的修正中,你也不必直接修改類型本身,可以拷備一個出來,比如叫WebServiceLoginChecker2,這樣即保證了程序原有的可運行性,又可以進行BUG的修正,一旦出現了”動一發觸全身“的情況,也非常容易全身而退。
這樣的設計思想、模式,我們將其稱之為工廠模式。工廠模式還分為簡單工廠模式和抽象工廠模式,但是其最核心的思想,就是創建一個工廠,由工廠在運行時,進行動態的實例創建、返回。大大降底的類與類、模塊與模塊之間的耦合度,為更新與維護提供了非常好的隔離環境。
小結
通過第一、第二篇文章的學習,一種初步的構架思路已經產生。
1、分析當前方法要的主要事情
2、將可能存在變更的邏輯,建立接口,待以后實現
3、考慮到上述接口的實現多樣性,建立工廠類型,由工廠類型負責創建接口實例
利與弊的權衡
從做產品的角度考慮,一個好的基本構架是產品最核心的保障,有了這樣的保障,可以說,除非到你換語種的那一天,否則永遠不會存在(推倒重來)的那一天,因為你的每一個環節都被低耦合了,它們全部都可以被單獨替換。
從做項目的角度考慮,一個好的基本構架是項目中最耗時間、最耗成本的階段。在面向結果的項目負責人眼中,相對於可維護性進度才是最重要的,所以在做項目這種情況下,對使用架構應該進行一個速與質的權衡。
文章為作者原創,轉載請標明出處,謝謝 http://www.cnblogs.com/ShimizuShiori/p/4929300.html