0 設(shè)計(jì)模式基礎(chǔ)

0.0 設(shè)計(jì)模式的定義

先來(lái)看一下設(shè)計(jì)模式常見的書面定義:

  • 設(shè)計(jì)模式是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過(guò)分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。

  • 設(shè)計(jì)模式是指在軟件開發(fā)中,經(jīng)過(guò)驗(yàn)證的,用于解決在特定環(huán)境下,重復(fù)出現(xiàn)的、特定問(wèn)題的解決方案。

設(shè)計(jì)模式更多的是一種實(shí)際應(yīng)用中經(jīng)驗(yàn)的基類和總結(jié),并得到了多數(shù)人的認(rèn)可和驗(yàn)證,經(jīng)過(guò)更規(guī)范的整理和分類及命名,成為了一種眾所周知的知識(shí)體系。

0.1 設(shè)計(jì)模式的分類

一般情況下說(shuō)到的設(shè)計(jì)模式都是指Gof著作中所講述的23中經(jīng)典設(shè)計(jì)模式。

  • 創(chuàng)建型模式:?jiǎn)卫J?、抽象工廠模式、建造者模式、工廠模式、原型模式。

  • 結(jié)構(gòu)型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。

  • 行為型模式:模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態(tài)模式、策略模式、職責(zé)鏈模式(責(zé)任鏈模式)、訪問(wèn)者模式。

1 面向接口編程

上學(xué)的時(shí)候經(jīng)常跟室友一起玩兒War3,我一直比較鐘情于暗夜精靈族,暗夜精靈有4個(gè)灰常厲害的英雄,每個(gè)英雄都會(huì)有4個(gè)技能。剛開始學(xué)習(xí)面向?qū)ο笏枷氲臅r(shí)候,我已經(jīng)知道“英雄”可以定義成一個(gè)類,假設(shè)每個(gè)英雄出場(chǎng)時(shí)候都要秀一下自己華麗的四個(gè)技能,我們?cè)跇?gòu)造函數(shù)中傳遞英雄名稱,構(gòu)造不同的英雄對(duì)象。

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 英雄類/// </summary>public class Hero
{    private readonly string _name; // 英雄名稱

    /// <summary>
    /// 構(gòu)造函數(shù)    /// </summary>
    /// <param name="name">英雄名稱</param>
    public Hero(string name)
    {
        _name = name;
    }    /// <summary>
    /// 秀出自己的技能    /// </summary>
    public void ShowSkills()
    {        switch (_name)
        {            case "DH":
                Console.WriteLine("我是惡魔獵手,我會(huì)法力燃燒、獻(xiàn)祭、閃避和變身。");                break;            case "WD":
                Console.WriteLine("我是守望者,我會(huì)暗影突襲、刀陣、閃爍和復(fù)仇天神。");                break;            case "KOG":
                Console.WriteLine("我是叢林守護(hù)者,我會(huì)纏繞根須、荊棘光環(huán)、自然之力和寧?kù)o。");                break;            case "POM":
                Console.WriteLine("我是月亮女祭司,我會(huì)召喚貓頭鷹,灼熱之箭,強(qiáng)擊光環(huán)和流星雨。");                break;            default:                break;
        }
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

英雄輪流秀出的自己的技能(排名不分先后)

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

class Program
{    static void Main(string[] args)
    {
        Hero dh = new Hero("DH");
        dh.ShowSkills();

        Hero wd = new Hero("WD");
        wd.ShowSkills();

        Hero kog = new Hero("KOG");
        kog.ShowSkills();

        Hero pom = new Hero("POM");
        pom.ShowSkills();
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

輸出結(jié)果

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

上面的代碼存在的問(wèn)題主要有2個(gè):

  • 如果某天我不再玩兒暗夜精靈了,改玩兒別的種族,Hero類中的ShowSkills方法就要完全修改一遍。

  • 秀出自己的技能只是一個(gè)最基本的行為,英雄還具有攻擊、移動(dòng)、釋放技能等行為,每個(gè)英雄攻擊力、移動(dòng)速度、釋放技能耗藍(lán)卻又都各不相同。

這里就涉及到了面向接口編程的重要原則。

通常接口用來(lái)定義實(shí)現(xiàn)類的外觀,提取實(shí)現(xiàn)類共有的行為定義。接口類似于一種契約,根據(jù)外部應(yīng)用的需要,約定了實(shí)現(xiàn)類應(yīng)該實(shí)現(xiàn)的功能,而具體內(nèi)部如何實(shí)現(xiàn),應(yīng)由具體的實(shí)現(xiàn)類控制,同時(shí)具體的實(shí)現(xiàn)類除了要實(shí)現(xiàn)接口規(guī)定的行為外,還可以根據(jù)需要實(shí)現(xiàn)自己獨(dú)有的行為,也就是說(shuō)實(shí)現(xiàn)類的功能應(yīng)包含但不僅限于接口定義的功能。

由于外部調(diào)用和內(nèi)部實(shí)現(xiàn)被接口隔離開了,外部調(diào)用只通過(guò)接口調(diào)用,也就是說(shuō)只要接口不變,內(nèi)部具體實(shí)現(xiàn)的變化就不會(huì)對(duì)外部調(diào)用產(chǎn)生任何影響。這樣使用接口的好處就很明顯了,當(dāng)我們?cè)黾悠渌⑿鄣臅r(shí)候只需要增加一個(gè)實(shí)現(xiàn)英雄接口的具體實(shí)現(xiàn)類既可,對(duì)原有已實(shí)現(xiàn)的不分不會(huì)造成任何影響。提現(xiàn)了接口“封裝隔離”的思想。

定義英雄接口:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 英雄接口定義/// </summary>public interface IHero
{    /// <summary>
    /// 秀技能    /// </summary>
    void ShowSkills();
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

不同英雄的具體實(shí)現(xiàn):

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 惡魔獵手/// </summary>public class DH : IHero
{    /// <summary>
    /// 秀出自己的技能    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是惡魔獵手,我會(huì)法力燃燒、獻(xiàn)祭、閃避和變身。");
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 守望者/// </summary>public class WD : IHero
{    /// <summary>
    /// 秀出自己的技能    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是守望者,我會(huì)暗影突襲、刀陣、閃爍和復(fù)仇天神。");
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 叢林守護(hù)者/// </summary>public class KOG : IHero
{    /// <summary>
    /// 秀出自己的技能    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是叢林守護(hù)者,我會(huì)纏繞根須、荊棘光環(huán)、自然之力和寧?kù)o。");
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 月亮女祭司/// </summary>public class POM : IHero
{    /// <summary>
    /// 秀出自己的技能    /// </summary>
    public void ShowSkills()
    {
        Console.WriteLine("我是月亮女祭司,我會(huì)召喚貓頭鷹,灼熱之箭,強(qiáng)擊光環(huán)和流星雨。");
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

修改外部調(diào)用部分,同樣每個(gè)英雄都正確的秀出了自己的技能。

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

IHero dh = new DH();
dh.ShowSkills();

IHero wd = new WD();
wd.ShowSkills();

IHero kog = new KOG();
kog.ShowSkills();

IHero pom = new POM();
pom.ShowSkills();

Console.ReadLine();

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

到這里我們就會(huì)發(fā)現(xiàn)了新的問(wèn)題,上面說(shuō)過(guò)接口的作用的就是封裝隔離,外部調(diào)用只知道接口的存在,不應(yīng)該依賴于具體的實(shí)現(xiàn)類。而接口又是沒(méi)有辦法通過(guò)new關(guān)鍵字進(jìn)行實(shí)現(xiàn)話的,如何解決這個(gè)矛盾?

2 簡(jiǎn)單工廠模式

簡(jiǎn)單工廠模式并不包含在上述23種經(jīng)典設(shè)計(jì)模式之中,也有人說(shuō)簡(jiǎn)單工廠并不能算得上一個(gè)設(shè)計(jì)模式。不管怎么說(shuō),簡(jiǎn)單工廠卻是實(shí)實(shí)在在的簡(jiǎn)單易用,還記得當(dāng)年還在學(xué)校的時(shí)候,由于沒(méi)有經(jīng)過(guò)實(shí)際項(xiàng)目的歷練,在編碼經(jīng)驗(yàn)不足的情況下,去試著了解設(shè)計(jì)模式的時(shí)候,大部分設(shè)計(jì)模式是無(wú)法深入理解的,也就只有看到簡(jiǎn)單工廠模式的時(shí)候會(huì)驚呼:“原來(lái)還可以這樣”抑或“原來(lái)我一直用的就是簡(jiǎn)單工廠”。

簡(jiǎn)單來(lái)說(shuō),簡(jiǎn)單工廠就是通過(guò)定義一個(gè)工廠類,這個(gè)工廠類提供了一個(gè)創(chuàng)建具體實(shí)例的功能,外部調(diào)用只需要告訴工廠需要什么類型的實(shí)例,工廠負(fù)責(zé)創(chuàng)建這個(gè)實(shí)例,外部調(diào)用無(wú)需關(guān)心其具體實(shí)現(xiàn),從而達(dá)到真正的接口隔離的目的。

簡(jiǎn)單工廠類中創(chuàng)建具體實(shí)例的方法一般定義為靜態(tài)方法,從而可以避免在外部調(diào)用的時(shí)候再new簡(jiǎn)單工廠的對(duì)象,因此簡(jiǎn)單工廠模式一般也被成為靜態(tài)工廠模式。

我們定義一個(gè)簡(jiǎn)單工廠類。

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

/// <summary>/// 簡(jiǎn)單工廠類/// </summary>public class Factory
{    /// <summary>
    /// 創(chuàng)建英雄的靜態(tài)方法    /// </summary>
    /// <param name="heroName">英雄名稱</param>
    /// <returns></returns>
    public static IHero CreateHero(string heroName)
    {        switch (heroName)
        {            case "DH":                return new DH();            case "WD":                return new WD();            case "KOG":                return new KOG();            case "POM":                return new POM();            default:                return null;
        }
    }
}

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

然后外部調(diào)用的時(shí)候通過(guò)簡(jiǎn)單工廠方法創(chuàng)建各個(gè)英雄的實(shí)例。

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

IHero dh = Factory.CreateHero("DH");
dh.ShowSkills();

IHero wd = Factory.CreateHero("WD");
wd.ShowSkills();

IHero kog = Factory.CreateHero("KOG");
kog.ShowSkills();

IHero pom =  Factory.CreateHero("POM");
pom.ShowSkills();

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開發(fā),動(dòng)畫培訓(xùn)

初識(shí)簡(jiǎn)單工廠模式的時(shí)候,最大的疑問(wèn)就是,這個(gè)簡(jiǎn)單工廠類,無(wú)非就是把原來(lái)在外部調(diào)用時(shí)創(chuàng)建具體英雄類實(shí)例的代碼挪了過(guò)去,在簡(jiǎn)單工廠類里面還是一樣需要通過(guò)new指定具體的類來(lái)進(jìn)行接口的實(shí)例化啊,而且還白白多了一個(gè)簡(jiǎn)單工廠類,意義何在?

其實(shí)簡(jiǎn)單工廠類最大的意義還是起到接口隔離的作用,看到隔離這個(gè)詞,就肯定有隔離的雙方(封裝體),我們多增加一個(gè)簡(jiǎn)單工廠類,表面上看是多了一個(gè)類,并沒(méi)有減少任何代碼,也沒(méi)有對(duì)代碼進(jìn)行大的更改,只是一個(gè)英雄實(shí)例化代碼的位置移動(dòng),簡(jiǎn)單工廠模式的精髓恰恰就是這個(gè)具體類實(shí)例化代碼位置的移動(dòng),我們知道,使用接口的目的就是不讓外部調(diào)用知道封裝體內(nèi)部的實(shí)現(xiàn),在使用簡(jiǎn)單工廠類之前,我們創(chuàng)建英雄實(shí)例的代碼很明顯的是位于外部調(diào)用部分的,這樣其實(shí)就是沒(méi)有隔離,由于簡(jiǎn)單工廠類位于封裝體內(nèi)部的,工廠類是可以知道具體的實(shí)現(xiàn)細(xì)節(jié)的。使用簡(jiǎn)單工廠類后,相當(dāng)于這個(gè)封裝提對(duì)外只公開了一個(gè)IHero接口及一個(gè)工廠類創(chuàng)建英雄的方法給外部調(diào)用,這樣隔離就很明確了,只是一段代碼位置的移動(dòng),從設(shè)計(jì)上來(lái)講,已經(jīng)發(fā)生了本質(zhì)的變化。

3 簡(jiǎn)單工廠的幾點(diǎn)建議

  • 工廠方法靜化

     簡(jiǎn)單工廠類中創(chuàng)建實(shí)例的方法,應(yīng)為靜態(tài)方法。

  • 實(shí)例創(chuàng)建配置文件化

     實(shí)例創(chuàng)建應(yīng)盡量通過(guò)配置文件及反射機(jī)制,動(dòng)態(tài)創(chuàng)建,達(dá)到能根據(jù)某個(gè)值,自動(dòng)判斷并創(chuàng)建對(duì)應(yīng)類的實(shí)例的目的,這樣就可以將龐大的swith語(yǔ)句塊消除,同時(shí),實(shí)例化部分的修改,只需要修改配置文件即可。

  • 簡(jiǎn)單工廠模塊化

     一個(gè)簡(jiǎn)單工廠可以定義多個(gè)創(chuàng)建實(shí)例的靜態(tài)方法,建議按照不同的功能模塊,創(chuàng)建不同的工廠類。因?yàn)楹?jiǎn)單工廠類是一個(gè)模塊封裝提的一部分。

4 簡(jiǎn)單工廠模式的優(yōu)點(diǎn)

  • 封裝

   能夠非常簡(jiǎn)單快捷的實(shí)現(xiàn)模塊的組件化,組件通過(guò)對(duì)外公開接口,實(shí)現(xiàn)面向接口編程

  • 解耦

    實(shí)現(xiàn)了外部調(diào)用和具體實(shí)現(xiàn)的解耦,增強(qiáng)了系統(tǒng)的健壯性和易維護(hù)性。