C#設(shè)計模式:六大原則(下)

四叫编、 接口隔離原則(Interface Segregation Principe,ISP)

類的依賴關(guān)系應(yīng)建立在最小接口上,不要都塞在一起时迫。即客戶端不應(yīng)該依賴它不需要的接口。

??根據(jù)上面的定義可以看出谓晌,對接口的建立要最小化掠拳,而不是依賴所有功能都塞在一起的大而全的接口。換種說法就是纸肉,方法盡量要細(xì)化溺欧,要少。當(dāng)然柏肪,也不要拆分成一個一個的姐刁,而是要把一些功能緊密綁的方法封裝起來,不要暴露太多細(xì)節(jié)预吆。哇龙填,這是不單一職責(zé)嗎?不對拐叉,它們的審視角度是不同的岩遗,一個是接口的依賴,要求接口的方法盡量要少凤瘦;一個是職責(zé)的分離宿礁,類和接口的職責(zé)要單一。接口隔離原則就是要求只提供盡可能小的接口蔬芥,需要高內(nèi)聚梆靖,不需要的行為要隱藏起來。當(dāng)然笔诵,拆分接口時返吻,也需要滿足單一職責(zé)原則。
??來看一個手機(jī)的例子吧乎婿,手機(jī)在例子的世界還是很火爆的测僵,現(xiàn)在是至少人手一部了。看圖4.1

圖4.1 使用手機(jī)的類圖

??定義了一個手機(jī)的接口捍靠,手機(jī)可以打電話沐旨,發(fā)短信,上網(wǎng)榨婆,玩游戲等磁携,然后使用了一個場景類People來使用手機(jī)×挤纾看起來是不是很完美谊迄,但仔細(xì)想想,ICellphone 這個接口有沒有最優(yōu)設(shè)計拖吼,是不是功能有點太多了鳞上,當(dāng)然,單從單一職責(zé)上考濾是沒有問題的吊档,加上接口隔離原則的話篙议,就不同了。時間回到手機(jī)剛誕生的年代怠硼,那時候的手機(jī)是不是只能打個電話鬼贱,發(fā)個短信。手機(jī)在迭代的過程中香璃,才出現(xiàn)了各種功能这难,上網(wǎng),玩游戲葡秒,在線支付等姻乓,當(dāng)今平板的出現(xiàn),除了上網(wǎng)等高級功能外眯牧,基礎(chǔ)的打電話蹋岩,發(fā)短信功能反而沒有了。這樣功能都封裝在一起学少,是不是就過度了剪个,而且都暴露到了使用場景中,不就有問題了嗎版确。那我們根據(jù)接口隔離原則重新修改一下類圖扣囊,如圖4.2所示

圖4.2 修改后的使用手機(jī)的類圖

??這樣,不管以后使用什么手機(jī)绒疗,都可保持接口的穩(wěn)定侵歇。需要什么,就繼承什么吓蘑,如果是平板狠持,就不需要電話和短信井仰,保持了接口的最小化。來看一下代碼實現(xiàn):

/// <summary>
/// 基礎(chǔ)手機(jī)功能
/// 打電話、發(fā)短信
/// </summary>
public interface IBaseCellphone
{
    void Call();
    void Text();
}

/// <summary>
/// 上網(wǎng)玩游戲的功能
/// </summary>
public interface IOnlineGameCellphone
{
    void Online();
    void PlayGame();
}

/// <summary>
/// 現(xiàn)代手機(jī)蠢终,即可打電話,短信抵知,還可以上網(wǎng)玩游戲
/// </summary>
public class Cellphone : IBaseCellphone, IOnlineGameCellphone
{
    public void Call()
    {
        Console.WriteLine("打電話");
    }

    public void Text()
    {
        Console.WriteLine("發(fā)短信");
    }

    public void Online()
    {
        Console.WriteLine("手機(jī)已連網(wǎng)");
    }

    public void PlayGame()
    {
        Console.WriteLine("開始玩游戲");
    }
}

場景中使用

/// <summary>
/// 現(xiàn)實生活中的場景叁熔,使用手機(jī)
/// </summary>
public class People
{
    public int Id { get; set; }
    public string Name { get; set; }

    /// <summary>
    /// 一些人只使用手機(jī)的基本功能
    /// </summary>
    /// <param name="phone"></param>
    public void UsePhone(IBaseCellphone cellphone)
    {
        Console.WriteLine("我是 {0},我只用基礎(chǔ)的功能", this.Name);
        cellphone.Call();
        cellphone.Text();
    }

    /// <summary>
    /// 只想上網(wǎng)玩游戲
    /// </summary>
    /// <param name="cellphone"></param>
    public void PlayOnlineGame(IOnlineGameCellphone cellphone)
    {
        Console.WriteLine("我是 {0}沸移,我只想上網(wǎng)玩游戲", this.Name);
        cellphone.Online();
        cellphone.PlayGame();
    }
}

五痪伦、迪米特法則(Law of Demeter,LOD)

一個對象應(yīng)盡可能少的了解其它對象

??迪米特法則也稱最少知識原則(Least Knowledge Principle雹锣,LKP)网沾,名字雖不同,規(guī)則確是同樣的蕊爵。說的都是類與類之間的關(guān)系辉哥,一個類在使用其它類時,不需要知道其細(xì)節(jié)攒射,知道的越少越好醋旦,你內(nèi)部如何復(fù)雜多變,都是你自己的事情会放,我不關(guān)心饲齐,我只關(guān)心調(diào)用方法,取得結(jié)果咧最,其它的一概不關(guān)心捂人。迪米特法則的指導(dǎo)思想就是使類與類之間保持松耦合的關(guān)系。主要包括如下:

  1. 不跟非直接的朋友說話
  2. 只是內(nèi)部服務(wù)的屬性矢沿,要封閉到內(nèi)部
  3. C#中[Serializable]特性滥搭,盡量少用,牽涉到序列化的問題

還是用實例來說話吧咨察,有一個關(guān)于學(xué)校的日常故事论熙,老師讓班長多關(guān)注下同學(xué)們的學(xué)習(xí)情況,有沒有好好聽課摄狱,認(rèn)真寫作業(yè)脓诡。讓我們來用程序?qū)崿F(xiàn)一下,先看類圖媒役,如圖5.1所示:


圖5.1 老師讓班長檢查學(xué)生學(xué)習(xí)情況

??分別定義了老師祝谚,班長,學(xué)生三個角色酣衷,Teacher通過Command方法讓Monitor檢查學(xué)生的學(xué)習(xí)情況交惯,Monitor通過CheckLearn方法檢查學(xué)生,來看下代碼實現(xiàn):

/// <summary>
/// 學(xué)生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
}

/// <summary>
/// 班長
/// </summary>
public class Monitor
{
    public void CheckLearn(List<Student> students)
    {
        students.ForEach(item =>
        {
            //檢查有沒有好好學(xué)習(xí)
            var learn = new Random(Guid.NewGuid().GetHashCode()).Next(0, 2) == 1 ? "有" : "沒有";
            Console.WriteLine($"{item.Name},{learn}好好學(xué)習(xí)");
        });
    }
}

/// <summary>
/// 老師
/// </summary>
public class Teacher
{
    public void Command(Monitor monitor)
    {
        //初始化學(xué)生
        var list = new List<Student>
        {
            new Student {Id = 1, Name = "張三"},
            new Student {Id = 2, Name = "李四"},
            new Student {Id = 3, Name = "王五"},
            new Student {Id = 4, Name = "趙六"},
            new Student {Id = 5, Name = "陳七"}
        };

        //班長檢查學(xué)習(xí)情況
        monitor.CheckLearn(list);
    }
}

場景中調(diào)用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var teacher = new Teacher();
            teacher.Command(new Monitor());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

運行結(jié)果:
張三席爽,沒有好好學(xué)習(xí)
李四意荤,有好好學(xué)習(xí)
王五,有好好學(xué)習(xí)
趙六只锻,有好好學(xué)習(xí)
陳七玖像,沒有好好學(xué)習(xí)

?? 整個過程都實現(xiàn)了,貌似沒有什么問題齐饮,但回過頭來想想捐寥,老師有幾個直接的朋友類,這里面其實就只有一個祖驱,那就是班長握恳,直接告訴的班長。那老師也依賴了學(xué)生啊捺僻,不也是朋友類嗎乡洼?迪米特法則告訴我們,像這種方法體內(nèi)的類不屬于朋友類陵像,一個類只和朋友說話就珠,班長才直接和學(xué)生有交流。Command中出現(xiàn)List<Student>列表醒颖,會影響程序的穩(wěn)定性妻怎。同時,我們看班長檢查學(xué)習(xí)情況泞歉,只需要起檢查作用逼侦,學(xué)生學(xué)習(xí)是自己的事情,比如他要認(rèn)真聽課腰耙,好好寫作業(yè)榛丢。有沒有好好學(xué)習(xí)也要有個標(biāo)準(zhǔn),不能主觀來判斷挺庞,上面實例中處理的也比較簡單晰赞,我們來深入下,當(dāng)然选侨,也不是很深入掖鱼,再加上上面朋友類的問題,來重構(gòu)一下程序援制,修改一下類圖戏挡,如圖5.2所示:


圖5.2 修改后的檢查學(xué)習(xí)情況類圖

斷開了非直接的朋友,學(xué)生的學(xué)習(xí)情況也內(nèi)聚到自身晨仑,具體看一下代碼實現(xiàn):

/// <summary>
/// 學(xué)生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    private readonly Random _random = new Random(Guid.NewGuid().GetHashCode());

    private int _score;  //只是內(nèi)部服務(wù)的屬性褐墅,要封閉到內(nèi)部
    private const int GoodScore = 180;

    public void Learn()
    {
        this.Lesson();
        this.Homework();
        var learnString = this._score > GoodScore ? "有" : "沒有";
        Console.WriteLine($"{this.Name}拆檬,{learnString}好好學(xué)習(xí)");
    }

    /// <summary>
    /// 聽課
    /// 內(nèi)部方法,盡量減少公開的方法和屬性
    /// </summary>
    private void Lesson()
    {
        this._score += _random.Next(150);
    }

    /// <summary>
    /// 寫作業(yè)
    /// </summary>
    private void Homework()
    {
        this._score += _random.Next(150);
    }
}

/// <summary>
/// 班長
/// </summary>
public class Monitor
{
    public List<Student> StudentList { get; set; }

    public void CheckLearn()
    {
        StudentList.ForEach(item =>
        {
            //只關(guān)心調(diào)用方法妥凳,不需要知道細(xì)節(jié)
            item.Learn();
        });
    }
}

/// <summary>
/// 老師
/// </summary>
public class Teacher
{
    public void Command(Monitor monitor)
    {
        //斷開了對Student的依賴竟贯,減少依賴,不跟非直接的朋友說話
        //班長檢查學(xué)習(xí)情況
        monitor.CheckLearn();
    }
}

場景中調(diào)用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            //初始化學(xué)生
            var studentList = new List<Student>
            {
                new Student {Id = 1, Name = "張三"},
                new Student {Id = 2, Name = "李四"},
                new Student {Id = 3, Name = "王五"},
                new Student {Id = 4, Name = "趙六"},
                new Student {Id = 5, Name = "陳七"}
            };

            var teacher = new Teacher();
            teacher.Command(new Monitor {StudentList = studentList});
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

六逝钥、開閉原則(Open Closed Principle澄耍,OCP)

一個軟件實體應(yīng)當(dāng)對擴(kuò)展開放,對修改封閉

?? 一個軟件或系統(tǒng)在開發(fā)的過程中晌缘,以及上線生產(chǎn)后,隨著時間的推移痢站,都會產(chǎn)生變化磷箕,這是鐵定的事實。那我們在設(shè)計時就要盡量適應(yīng)這些變化阵难,來提高系統(tǒng)的穩(wěn)定性和靈活性岳枷,開閉原則就是說一個軟件實體應(yīng)該通過擴(kuò)展來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化呜叫。開閉原則指導(dǎo)我們?nèi)绾谓⒁粋€穩(wěn)定的空繁、靈活的系統(tǒng)
?? 開閉原則是最基礎(chǔ)的原則朱庆,也是最重要的面向?qū)ο笤O(shè)計原則盛泡,在面向?qū)ο蟮拈_發(fā)時,都會提到的原則娱颊。開閉原則就是一個目標(biāo)傲诵,其它五大原則都是實現(xiàn)手段,它就像一個口號箱硕,你們都要奔著這個口號來拴竹。要滿足開閉原則,就需要對系統(tǒng)進(jìn)行抽象化設(shè)計剧罩,抽象化是開閉原則的關(guān)鍵栓拜,換句說法就是對系統(tǒng)進(jìn)行抽象約束。
?? 每個人心中惠昔,都有一個武俠夢幕与,夢想到有朝一日,能成為一個俠客舰罚。如今纽门,各種武俠類游戲也層出不窮,好多人都喜歡玩营罢,畢竟有個圓夢的地方了赏陵。游戲中饼齿,有各種門派,如少林蝙搔,武當(dāng)缕溉,玩家選擇各自喜歡的門派,玩的美滋滋的吃型。我們來實現(xiàn)下這個過程证鸥,類圖如6.1所示

圖6.1 玩武俠游戲的類圖

?? 上圖中,Player可以玩游戲了勤晚,武當(dāng)枉层、少林都能玩,可是游戲是會迭代的赐写,有一天上了一個新版本鸟蜡,加入了一個新的門派,不但要修改Player類挺邀,還要更改場景中的處理揉忘,違反了開閉原則。重構(gòu)一下端铛,如類圖6.2所示

圖6.2 重構(gòu)后的玩武俠游戲的類圖

?? 增加了一個門派接口泣矛,各門派需要實現(xiàn)接口。Player只依賴接口禾蚕,通過場景類來確定玩家玩什么門派您朽,來看下代碼實現(xiàn):

public interface IFaction
{
    void Characteristic();
}

public class WuDang : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("武當(dāng)派是攻擊系的,內(nèi)功遠(yuǎn)程群攻夕膀,很6虚倒。");
    }
}

public class ShaoLin : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("少林派是防御系的,內(nèi)功防御产舞,你們都打不疼我魂奥。");
    }
}

public class Player
{
    public void PlayFaction(IFaction faction)
    {
        Console.WriteLine($"玩家開始玩游戲,選擇的是{faction.GetType().Name}");
        faction.Characteristic();
    }
}

場景中調(diào)用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var player = new Player();

            //玩家玩武當(dāng)
            player.PlayFaction(new WuDang());

            //玩家玩少森
            player.PlayFaction(new ShaoLin());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

很簡單易猫,運行結(jié)果就不展示了耻煤。玩什么,就調(diào)用什么准颓,如果新增一個門派峨眉哈蝇,代碼如下所示:

public class EMei : IFaction
{
    public void Characteristic()
    {
        Console.WriteLine("峨眉派是治愈系的,醫(yī)術(shù)很高攘已,有起死回生之力炮赦。");
    }
}

場景中調(diào)用:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var player = new Player();

            //新門派上線了,我要試試
            player.PlayFaction(new EMei());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

?? 看到了吧样勃,是不是很容易吠勘,加一個門派性芬,只在場景中很少的改動,就新增了一個門派剧防,都是通過擴(kuò)展來實現(xiàn)的植锉,原有的類都未進(jìn)行改動過。這就是開閉原則峭拘,對擴(kuò)展開放俊庇,對修改封閉。

?? 至此鸡挠,六大原則就說完了辉饱,寫出來還真是挺不容易的,比想象中的要困難的多拣展,如有什么錯誤或問題討論鞋囊,多多指正和交流。后面的23種設(shè)計模式才是真槍實彈瞎惫,堅持!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末译株,一起剝皮案震驚了整個濱河市瓜喇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歉糜,老刑警劉巖乘寒,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匪补,居然都是意外死亡伞辛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門夯缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚤氏,“玉大人,你說我怎么就攤上這事踊兜「捅酰” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵捏境,是天一觀的道長于游。 經(jīng)常有香客問我,道長垫言,這世上最難降的妖魔是什么贰剥? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮筷频,結(jié)果婚禮上蚌成,老公的妹妹穿的比我還像新娘前痘。我一直安慰自己,他們只是感情好笑陈,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布际度。 她就那樣靜靜地躺著,像睡著了一般涵妥。 火紅的嫁衣襯著肌膚如雪乖菱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天蓬网,我揣著相機(jī)與錄音窒所,去河邊找鬼。 笑死帆锋,一個胖子當(dāng)著我的面吹牛吵取,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锯厢,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼皮官,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了实辑?” 一聲冷哼從身側(cè)響起捺氢,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剪撬,沒想到半個月后摄乒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡残黑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年馍佑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梨水。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拭荤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疫诽,到底是詐尸還是另有隱情穷劈,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布踊沸,位于F島的核電站歇终,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逼龟。R本人自食惡果不足惜评凝,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腺律。 院中可真熱鬧奕短,春花似錦宜肉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至日杈,卻和暖如春遣铝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莉擒。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工酿炸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涨冀。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓填硕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鹿鳖。 傳聞我的和親對象是個殘疾皇子扁眯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計原則時需要注意以下幾點:a) 高內(nèi)聚、低耦合和單一職能的“沖突”實際上翅帜,這兩者...
    彥幀閱讀 3,734評論 0 14
  • 設(shè)計模式六大原則 設(shè)計模式六大原則(1):單一職責(zé)原則 定義:不要存在多于一個導(dǎo)致類變更的原因恋拍。通俗的說,即一個類...
    viva158閱讀 763評論 0 1
  • 參考資料:菜鳥教程之設(shè)計模式 設(shè)計模式概述 設(shè)計模式(Design pattern)代表了最佳的實踐藕甩,通常被有經(jīng)驗...
    Steven1997閱讀 1,165評論 1 12
  • 轉(zhuǎn)載自 設(shè)計模式六大原則[http://www.uml.org.cn/sjms/201211023.asp#3] ...
    廚子閱讀 1,086評論 2 5
  • TCP 編程 個人計算機(jī)或者服務(wù)器上通常會運行多個應(yīng)用程序, 但是我們只需要一條網(wǎng)線就能連接互聯(lián)網(wǎng), 去訪問互聯(lián)網(wǎng)...
    江洋林瀾閱讀 301評論 0 0