什么是接口(中級篇)——接口在設計模式中的使用(二)


還記得我的軟件工程老師是這么說的:軟件應該往高內聚,低耦合的方向進行設計。

當時,還身為一個初學者的我,不太明白老師的這句話——既然面向對象提供給了我們”繼承“這種高耦合的概念,那為何我們還要低耦合高內聚呢?難道放着繼承的概念不用,而改為面向過程嗎?

帶着這一疑問,我請教了我的老師,他給我的回答是:通過接口來分隔分離邏輯,就可以達到低耦合的效果。

 

我們來回顧一下前一篇所學習的"控制反轉"設計思想,其實質就是上面所說的"通過接口來分離邏輯"。

 

但以上一篇的示例來說,達到這個目標,還有一些欠缺,首先讓我們來回顧一下上一篇的代碼內容

/// <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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM