C#軟件設計——小話設計模式原則之:單一職責原則SRP

前言:上篇C#軟件設計——小話設計模式原則之:依賴倒置原則DIP簡單介紹了下依賴倒置的由來以及使用,中間插了兩篇WebApi的文章,這篇仍是迴歸正題,繼續來寫寫設計模式另外一個重要的原則:單一職責原則。html

軟件設計原則系列文章索引設計模式

1、原理介紹

一、官方定義

單一職責原則,英文縮寫SRP,全稱Single Responsibility Principle。post

原始定義:There should never be more than one reason for a class to change。url

官方翻譯:應該有且僅有一個緣由引發類的變動。簡單點說,一個類,最好只負責一件事,只有一個引發它變化的緣由。spa

二、本身理解

2.一、原理解釋

上面的定義不難理解,引發類變化的緣由不能多於一個。也就是說每個類只負責本身的事情,此所謂單一職責翻譯

咱們知道,在OOP裏面,高內聚、低耦合是軟件設計追求的目標,而單一職責原則能夠看作是高內聚、低耦合的引伸,將職責定義爲引發變化的緣由,以提升內聚性,以此來減小引發變化的緣由。職責過多,可能引發變化的緣由就越多,這將是致使職責依賴,相互之間就產生影響,從而極大的損傷其內聚性和耦合度。單一職責一般意味着單一的功能,所以不要爲類實現過多的功能點,以保證明體只有一個引發它變化的緣由。設計

不論是從官方定義,仍是對「單一職責」名稱的解釋,都能很好的理解單一職責原則的意義。其實在軟件設計中,要真正用好單一職責原則並不簡單,由於遵循這一原則最關鍵的地方在於職責的劃分,博主的理解是職責的劃分是根據需求定的,同一個類(接口)的設計,在不一樣的需求裏面,可能職責的劃分並不同,爲何這麼說呢?咱們來看下面的例子。code

2、場景示例

關於單一職責原則的原理,咱們就不作過多的解釋了。重點是職責的劃分!重點是職責的劃分!重點是職責的劃分!重要的事情說三遍。下面根據一個示例場景來看看如何劃分職責。htm

假定如今有以下場景:國際手機運營商那裏定義了生產手機必需要實現的接口,接口裏面定義了一些手機的屬性和行爲,手機生產商若是要生成手機,必需要實現這些接口。blog

一、初始設計——初稿

咱們首先以手機做爲單一職責去設計接口,方案以下。

   /// <summary>
    /// 充電電源
    /// </summary>
    public class ElectricSource
    {    }
  public interface IMobilePhone
    {
        //運行內存
        string RAM { get; set; }

        //手機存儲內存
        string ROM { get; set; }

        //CPU主頻
        string CPU { get; set; }

        //屏幕大小
        int Size { get; set; }

        //手機充電接口
        void Charging(ElectricSource oElectricsource);

        //打電話
        void RingUp();

        //接電話
        void ReceiveUp();

        //上網
        void SurfInternet();
    }

而後咱們的手機生產商去實現這些接口

//具體的手機示例
    public class MobilePhone:IMobilePhone
    {
        public string RAM
        {
            get {throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public string ROM
        {
            get{throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public string CPU
        {
            get{ throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public int Size
        {
            get{throw new NotImplementedException();}
            set{throw new NotImplementedException();}
        }

        public void Charging(ElectricSource oElectricsource)
       {
            throw new NotImplementedException();
        }

        public void RingUp()
        {
            throw new NotImplementedException();
        }

        public void ReceiveUp()
        {
            throw new NotImplementedException();
        }

        public void SurfInternet()
        {
            throw new NotImplementedException();
        }
    }

這種設計有沒有問題呢?這是一個頗有爭議的話題。單一職責原則要求一個接口或類只有一個緣由引發變化,也就是一個接口或類只有一個職責,它就負責一件事情,原則上來講,咱們以手機做爲單一職責去設計,也是有必定的道理的,由於咱們接口裏面都是定義的手機相關屬性和行爲,引發接口變化的緣由只多是手機的屬性或者行爲發生變化,從這方面考慮,這種設計是有它的合理性的,若是你能保證需求不會變化或者變化的可能性比較小,那麼這種設計就是合理的。但實際狀況咱們知道,現代科技突飛猛進,科技的進步促使着人們不斷在手機原有基礎上增長新的屬性和功能。好比有一天,咱們給手機增長了攝像頭,那麼須要新增一個像素的屬性,咱們的接口和實現就得改吧,又有一天,咱們增長移動辦公的功能,那麼咱們的接口實現是否是也得改。因爲上面的設計沒有細化到必定的粒度,致使任何一個細小的改動都會引發從上到下的變化,有一種「牽一髮而動全身」的感受。因此須要細化粒度,下面來看看咱們如何變動設計。

二、二次設計——變動

 咱們將接口細化

  //手機屬性接口
    public interface IMobilePhoneProperty
    {
        //運行內存
        string RAM { get; set; }

        //手機存儲內存
        string ROM { get; set; }

        //CPU主頻
        string CPU { get; set; }

        //屏幕大小
        int Size { get; set; }

        //攝像頭像素
        string Pixel { get; set; }
    }

    //手機功能接口
    public interface IMobilePhoneFunction
    {
        //手機充電接口
        void Charging(ElectricSource oElectricsource);

        //打電話
        void RingUp();

        //接電話
        void ReceiveUp();

        //上網
        void SurfInternet();

        //移動辦公
        void MobileOA();
    }

實現類

    //手機屬性實現類
    public class MobileProperty:IMobilePhoneProperty
    {

        public string RAM
        {
            get{ throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public string ROM
        {
            get{ throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public string CPU
        {
            get{ throw new NotImplementedException();}
            set{throw new NotImplementedException();}
        }

        public int Size
        {
            get{throw new NotImplementedException();}
            set{throw new NotImplementedException();}
        }

        public string Pixel
        {
            get{throw new NotImplementedException();}
            set{throw new NotImplementedException();}
        }
    }

    //手機功能實現類
    public class MobileFunction:IMobilePhoneFunction
    {

        public void Charging(ElectricSource oElectricsource)
        {
            throw new NotImplementedException();
        }

        public void RingUp()
        {
            throw new NotImplementedException();
        }

        public void ReceiveUp()
        {
            throw new NotImplementedException();
        }

        public void SurfInternet()
        {
            throw new NotImplementedException();
        }

        public void MobileOA()
        {
            throw new NotImplementedException();
        }
    }

    //具體的手機實例
    public class HuaweiMobile
    {
        private IMobilePhoneProperty m_Property;
        private IMobilePhoneFunction m_Func;
        public HuaweiMobile(IMobilePhoneProperty oProperty, IMobilePhoneFunction oFunc)
        {
            m_Property = oProperty;
            m_Func = oFunc;
        }
    }

對於上面題的問題,這種設計可以比較方便的解決,若是是增長屬性,只須要修改IMobilePhoneProperty和MobileProperty便可;若是是增長功能,只須要修改IMobilePhoneFunction和MobileFunction便可。貌似完勝第一種解決方案。那麼是否這種解決方案就完美了呢?答案仍是看狀況。原則上,咱們將手機的屬性和功能分開了,使得職責更加明確,全部的屬性都由IMobilePhoneProperty接口負責,全部的功能都由IMobilePhoneFunction接口負責,若是是需求的粒度僅僅到了屬性和功能這一級,這種設計確實是比較好的。反之,若是粒度再細小一些呢,那咱們這種職責劃分是否完美呢?好比咱們普通的老人機只須要一些最基礎的功能,好比它只須要充電、打電話、接電話的功能,可是按照上面的設計,它也要實現IMobilePhoneFunction接口,某一天,咱們增長了一個新的功能玩遊戲,那麼咱們就須要在接口上面增長一個方法PlayGame()。但是咱們老人機根本用不着實現這個功能,但是因爲它實現了該接口,它的內部實現也得從新去寫。從這點來講,以上的設計仍是存在它的問題。那麼,咱們如何繼續細化接口粒度呢?

三、最終設計——成型

 接口細化粒度設計以下

  //手機基礎屬性接口
    public interface IMobilePhoneBaseProperty
    {
        //運行內存
        string RAM { get; set; }

        //手機存儲內存
        string ROM { get; set; }

        //CPU主頻
        string CPU { get; set; }

        //屏幕大小
        int Size { get; set; }
    }

    //手機擴展屬性接口
    public interface IMobilePhoneExtentionProperty
    {
        //攝像頭像素
        string Pixel { get; set; }
    }

    //手機基礎功能接口
    public interface IMobilePhoneBaseFunc
    {
        //手機充電接口
        void Charging(ElectricSource oElectricsource);

        //打電話
        void RingUp();

        //接電話
        void ReceiveUp();
    }

    //手機擴展功能接口
    public interface IMobilePhoneExtentionFunc
    {
        //上網
        void SurfInternet();

        //移動辦公
        void MobileOA();

        //玩遊戲
        void PlayGame();
    }

實現類和上面相似

//手機基礎屬性實現
    public class MobilePhoneBaseProperty : IMobilePhoneBaseProperty
    {

        public string RAM
        {
            get{throw new NotImplementedException();}
            set{throw new NotImplementedException();}
        }

        public string ROM
        {
            get{throw new NotImplementedException();}
            set {throw new NotImplementedException();}
        }

        public string CPU
        {
            get{throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }

        public int Size
        {
            get{ throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }
    }

    //手機擴展屬性實現
    public class MobilePhoneExtentionProperty : IMobilePhoneExtentionProperty
    {

        public string Pixel
        {
            get{ throw new NotImplementedException();}
            set{ throw new NotImplementedException();}
        }
    }

    //手機基礎功能實現
    public class MobilePhoneBaseFunc : IMobilePhoneBaseFunc
    {
        public void Charging(ElectricSource oElectricsource)
        {
            throw new NotImplementedException();
        }

        public void RingUp()
        {
            throw new NotImplementedException();
        }

        public void ReceiveUp()
        {
            throw new NotImplementedException();
        }
    }

    //手機擴展功能實現
    public class MobilePhoneExtentionFunc : IMobilePhoneExtentionFunc
    {

        public void SurfInternet()
        {
            throw new NotImplementedException();
        }

        public void MobileOA()
        {
            throw new NotImplementedException();
        }

        public void PlayGame()
        {
            throw new NotImplementedException();
        }
    }

此種設計能解決上述問題,細分到此粒度,這種方案基本算比較完善了。能不能算完美?這個得另說。接口的粒度要設計到哪一步,取決於需求的變動程度,或者說取決於需求的複雜度。

3、總結

以上經過一個應用場景簡單介紹了下單一職責原則的使用,上面三種設計,沒有最合理,只有最合適。理解單一職責原則,最重要的就是理解職責的劃分,職責劃分的粒度取決於需求的粒度,最後又回到了那句話:沒有最好的設計,只有最適合的設計。歡迎園友拍磚斧正。若是園友們以爲本文對你有幫助,請幫忙推薦,博主將繼續努力~~

相關文章
相關標籤/搜索