午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

C#軟件設(shè)計(jì)

 昵稱10504424 2016-02-23

前言:很久之前就想動(dòng)筆總結(jié)下關(guān)于軟件設(shè)計(jì)的一些原則,或者說是設(shè)計(jì)模式的一些原則,奈何被各種bootstrap組件所吸引,一直抽不開身。群里面有朋友問博主是否改行做前端了,呵呵,其實(shí)博主是想做“全戰(zhàn)”,即各方便都有戰(zhàn)斗力。關(guān)于設(shè)計(jì)模式,作為程序猿的我們肯定都不陌生。博主的理解,所謂設(shè)計(jì)模式就是前人總結(jié)下來的一些對于某些特定使用場景非常適用的優(yōu)秀的設(shè)計(jì)思路,“前人栽樹,后人乘涼”,作為后來者的我們就有福了,當(dāng)我們遇到類似的應(yīng)用場景的時(shí)候就可以直接使用了。關(guān)于設(shè)計(jì)模式的原則,博主將會(huì)在接下來的幾篇里面根據(jù)自己的理解一一介紹,此篇就先來看看設(shè)計(jì)模式的設(shè)計(jì)原則之——依賴倒置原則。

一、原理介紹

1、官方定義

依賴倒置原則,英文縮寫DIP,全稱Dependence Inversion Principle。

原始定義:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。

官方翻譯:高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。

2、自己理解

2.1、原理解釋

上面的定義不難理解,主要包含兩次意思:

1)高層模塊不應(yīng)該直接依賴于底層模塊的具體實(shí)現(xiàn),而應(yīng)該依賴于底層的抽象。換言之,模塊間的依賴是通過抽象發(fā)生,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的。

2)接口和抽象類不應(yīng)該依賴于實(shí)現(xiàn)類,而實(shí)現(xiàn)類依賴接口或抽象類。這一點(diǎn)其實(shí)不用多說,很好理解,“面向接口編程”思想正是這點(diǎn)的最好體現(xiàn)。

2.2、被“倒置”的依賴

相比傳統(tǒng)的軟件設(shè)計(jì)架構(gòu),比如我們常說的經(jīng)典的三層架構(gòu),UI層依賴于BLL層,BLL層依賴于DAL層。由于每一層都是依賴于下層的實(shí)現(xiàn),這樣當(dāng)某一層的結(jié)構(gòu)發(fā)生變化時(shí),它的上層就不得不也要發(fā)生改變,比如我們DAL里面邏輯發(fā)生了變化,可能會(huì)導(dǎo)致BLL和UI層都隨之發(fā)生變化,這種架構(gòu)是非?;闹嚨模『?,這個(gè)時(shí)候如果我們換一種設(shè)計(jì)思路,高層模塊不直接依賴低層的實(shí)現(xiàn),而是依賴于低層模塊的抽象,具體表現(xiàn)為我們增加一個(gè)IBLL層,里面定義業(yè)務(wù)邏輯的接口,UI層依賴于IBLL層,BLL層實(shí)現(xiàn)IBLL里面的接口,所以具體的業(yè)務(wù)邏輯則定義在BLL里面,這個(gè)時(shí)候如果我們BLL里面的邏輯發(fā)生變化,只要接口的行為不變,上層UI里面就不用發(fā)生任何變化。

在經(jīng)典的三層里面,高層模塊直接依賴低層模塊的實(shí)現(xiàn),當(dāng)我們將高層模塊依賴于底層模塊的抽象時(shí),就好像依賴“倒置”了。這就是依賴倒置的由來。通過依賴倒置,可以使得架構(gòu)更加穩(wěn)定、更加靈活、更好應(yīng)對需求變化。

2.3、依賴倒置的目的

上面說了,在三層架構(gòu)里面增加一個(gè)接口層能實(shí)現(xiàn)依賴倒置,它的目的就是降低層與層之間的耦合,使得設(shè)計(jì)更加靈活。從這點(diǎn)上來說,依賴倒置原則也是“松耦合”設(shè)計(jì)的很好體現(xiàn)。

二、場景示例

 文章最開始的時(shí)候說了,依賴倒置是設(shè)計(jì)模式的設(shè)計(jì)原則之一,那么在我們那么多的設(shè)計(jì)模式中,哪些設(shè)計(jì)模式遵循了依賴倒置的原則呢?這個(gè)就多了,比如我們常見的工廠方法模式。下面博主就結(jié)合一個(gè)使用場景來說說依賴倒置原則如何能夠使得設(shè)計(jì)更加靈活。

場景描述:還記得在C#基礎(chǔ)系列——一場風(fēng)花雪月的邂逅:接口和抽象類這篇里面介紹過設(shè)備的采集的例子,這篇繼續(xù)以這個(gè)使用場景來說明。設(shè)備有很多類型,每種設(shè)備都有登錄和采集兩個(gè)方法,通過DeviceService這個(gè)服務(wù)去啟動(dòng)設(shè)備的采集,最開始我們只有MML和TL2這兩種類型的設(shè)備,那么來看看我們的設(shè)計(jì)代碼。

代碼示例:

復(fù)制代碼
  //MML類型的設(shè)備
    public class DeviceMML
    {
        public void Login()
        {
            Console.WriteLine("MML設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("MML設(shè)備采集");
            return true;
        }
    }

    //TL2類型設(shè)備
    public class DeviceTL2
    {
        public void Login()
        {
            Console.WriteLine("TL2設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TL2設(shè)備采集");
            return true;
        }
    }

    //設(shè)備采集的服務(wù)
    public class DeviceService
    {
        private DeviceMML MML = null;
        private DeviceTL2 TL2 = null;
        private string m_type = null;
        //構(gòu)造函數(shù)里面通過類型來判斷是哪種類型的設(shè)備
        public DeviceService(string type)
        {
            m_type = type;
            if (type == "0")
            {
                MML = new DeviceMML();
            }
            else if (type == "1")
            {
                TL2 = new DeviceTL2();
            }
        }

        public void LoginDevice()
        {
            if (m_type == "0")
            {
                MML.Login();
            }
            else if (m_type == "1")
            {
                TL2.Login();
            }
        }

        public bool DeviceSpider()
        {
            if (m_type == "0")
            {
                return MML.Spider();
            }
            else if (m_type == "1")
            {
                return TL2.Spider();
            }
            else
            {
                return true;
            }
        }
    }
復(fù)制代碼

在Main函數(shù)里面調(diào)用

復(fù)制代碼
   class Program
    {

        static void Main(string[] args)
        {
            var oSpider = new DeviceService("1");
            oSpider.LoginDevice();
            var bRes = oSpider.DeviceSpider();
            
            Console.ReadKey();
        }
復(fù)制代碼

上述代碼經(jīng)過開發(fā)、調(diào)試、部署、上線??梢哉_\(yùn)行,貌似一切都OK。

日復(fù)一日、年復(fù)一年。后來公司又來兩種新的設(shè)備TELNET和TL5類型設(shè)備。于是程序猿們又有得忙了,加班,趕進(jìn)度!于是代碼變成了這樣:

復(fù)制代碼
   //MML類型的設(shè)備
    public class DeviceMML
    {
        public void Login()
        {
            Console.WriteLine("MML設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("MML設(shè)備采集");
            return true;
        }
    }

    //TL2類型設(shè)備
    public class DeviceTL2
    {
        public void Login()
        {
            Console.WriteLine("TL2設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TL2設(shè)備采集");
            return true;
        }
    }

    //TELNET類型設(shè)備
    public class DeviceTELNET
    {
        public void Login()
        {
            Console.WriteLine("TELNET設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TELNET設(shè)備采集");
            return true;
        }
    }

    //TL5類型設(shè)備
    public class DeviceTL5
    {
        public void Login()
        {
            Console.WriteLine("TL5設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TL5設(shè)備采集");
            return true;
        }
    }


    //設(shè)備采集的服務(wù)
    public class DeviceService
    {
        private DeviceMML MML = null;
        private DeviceTL2 TL2 = null;
        private DeviceTELNET TELNET = null;
        private DeviceTL5 TL5 = null;
        private string m_type = null;
        //構(gòu)造函數(shù)里面通過類型來判斷是哪種類型的設(shè)備
        public DeviceService(string type)
        {
            m_type = type;
            if (type == "0")
            {
                MML = new DeviceMML();
            }
            else if (type == "1")
            {
                TL2 = new DeviceTL2();
            }
            else if (type == "2")
            {
                TELNET = new DeviceTELNET();
            }
            else if (type == "3")
            {
                TL5 = new DeviceTL5();
            }
        }

        public void LoginDevice()
        {
            if (m_type == "0")
            {
                MML.Login();
            }
            else if (m_type == "1")
            {
                TL2.Login();
            }
            else if (m_type == "2")
            {
                TELNET.Login();
            }
            else if (m_type == "3")
            {
                TL5.Login();
            }
        }

        public bool DeviceSpider()
        {
            if (m_type == "0")
            {
                return MML.Spider();
            }
            else if (m_type == "1")
            {
                return TL2.Spider();
            }
            else if (m_type == "2")
            {
                return TELNET.Spider();
            }
            else if (m_type == "3")
            {
                return TL5.Spider();
            }
            else
            {
                return true;
            }
        }
    }
復(fù)制代碼

比如我們想啟動(dòng)TL5類型設(shè)備的采集,這樣調(diào)用可以實(shí)現(xiàn):

復(fù)制代碼
        static void Main(string[] args)
        {
            var oSpider = new DeviceService("3");
            oSpider.LoginDevice();
            var bRes = oSpider.DeviceSpider();
         
            Console.ReadKey();
        }
復(fù)制代碼

花了九年二虎之力,總算是可以實(shí)現(xiàn)了??墒怯诌^了段時(shí)間,又有新的設(shè)備類型呢?是不是又要加班,又要改。這樣下去,感覺這就是一個(gè)無底洞,再加上時(shí)間越久,項(xiàng)目所經(jīng)歷的開發(fā)人員越容易發(fā)生變化,這個(gè)時(shí)候再改,那維護(hù)的成本堪比開發(fā)一個(gè)新的項(xiàng)目。并且,隨著設(shè)備類型的增多,代碼里面充斥著大量的if...else,這樣的爛代碼簡直讓人無法直視。

基于這種情況,如果我們當(dāng)初設(shè)計(jì)這個(gè)系統(tǒng)的時(shí)候考慮了依賴倒置,那么效果可能截然不同。我們來看看依賴倒置如何解決以上問題的呢?

復(fù)制代碼
    //定義一個(gè)統(tǒng)一接口用于依賴
    public interface IDevice
    {
        void Login();
        bool Spider();
    }

    //MML類型的設(shè)備
    public class DeviceMML : IDevice
    {
        public void Login()
        {
            Console.WriteLine("MML設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("MML設(shè)備采集");
            return true;
        }
    }

    //TL2類型設(shè)備
    public class DeviceTL2 : IDevice
    {
        public void Login()
        {
            Console.WriteLine("TL2設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TL2設(shè)備采集");
            return true;
        }
    }

    //TELNET類型設(shè)備
    public class DeviceTELNET : IDevice
    {
        public void Login()
        {
            Console.WriteLine("TELNET設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TELNET設(shè)備采集");
            return true;
        }
    }

    //TL5類型設(shè)備
    public class DeviceTL5 : IDevice
    {
        public void Login()
        {
            Console.WriteLine("TL5設(shè)備登錄");
        }

        public bool Spider()
        {
            Console.WriteLine("TL5設(shè)備采集");
            return true;
        }
    }


    //設(shè)備采集的服務(wù)
    public class DeviceService
    {
        private IDevice m_device;
        public DeviceService(IDevice oDevice)
        {
            m_device = oDevice;
        }

        public void LoginDevice()
        {
            m_device.Login();
        }

        public bool DeviceSpider()
        {
            return m_device.Spider();
        }
    }
復(fù)制代碼

調(diào)用

復(fù)制代碼
     static void Main(string[] args)
        {
            var oSpider = new DeviceService(new DeviceTL5());
            oSpider.Login();
            var bRes = oSpider.Spider();

            Console.ReadKey();
        }
復(fù)制代碼

代碼說明:上述解決方案中,我們定義了一個(gè)IDevice接口,用于上層服務(wù)的依賴,也就是說,上層服務(wù)(這里指DeviceService)僅僅依賴IDevice接口,對于具體的實(shí)現(xiàn)類我們是不管的,只要接口的行為不發(fā)生變化,增加新的設(shè)備類型后,上層服務(wù)不用做任何的修改。這樣設(shè)計(jì)降低了層與層之間的耦合,能很好地適應(yīng)需求的變化,大大提高了代碼的可維護(hù)性。呵呵,看著是不是有點(diǎn)眼熟?是不是有點(diǎn)像某個(gè)設(shè)計(jì)模式?其實(shí)設(shè)計(jì)模式的設(shè)計(jì)原理正是基于此。

三、使用Unity實(shí)現(xiàn)依賴倒置

上面說了那么多,都是在講依賴倒置的好處,那么在我們的項(xiàng)目中究竟如何具體實(shí)現(xiàn)和使用呢?

在介紹依賴倒置具體如何使用之前,我們需要引入IOC容器相關(guān)的概念,我們先來看看它們之間的關(guān)系。

依賴倒置原則(DIP):一種軟件架構(gòu)設(shè)計(jì)的原則(抽象概念)。

控制反轉(zhuǎn)(IoC):一種反轉(zhuǎn)流、依賴和接口的方式(DIP的具體實(shí)現(xiàn)方式)。這是一個(gè)有點(diǎn)不太好理解和解釋的概念,通俗地說,就是應(yīng)用程序本身不負(fù)責(zé)依賴對象的創(chuàng)建和維護(hù),而是將它交給一個(gè)外部容器(比如Unity)來負(fù)責(zé),這樣控制權(quán)就由應(yīng)用程序轉(zhuǎn)移到了外部IoC 容器,即控制權(quán)實(shí)現(xiàn)了所謂的反轉(zhuǎn)。例如在類型A中需要使用類型B的實(shí)例,而B 實(shí)例的創(chuàng)建并不由A 來負(fù)責(zé),而是通過外部容器來創(chuàng)建。

依賴注入(DI):IoC的一種實(shí)現(xiàn)方式,用來反轉(zhuǎn)依賴(IoC的具體實(shí)現(xiàn)方式)。園子里面很多博文里面說IOC也叫DI,其實(shí)根據(jù)博主的理解,DI應(yīng)該是IOC的具體實(shí)現(xiàn)方式,比如我們?nèi)绾螌?shí)現(xiàn)控制反轉(zhuǎn),答案就是通過依賴注入去實(shí)現(xiàn)。

IoC容器:依賴注入的框架,用來映射依賴,管理對象創(chuàng)建和生存周期(DI框架),自動(dòng)創(chuàng)建、維護(hù)依賴對象。

這些名詞是不是有點(diǎn)熟呢?博主之前介紹過MEF,之前使用MEF做過依賴注入,詳見C#進(jìn)階系列——MEF實(shí)現(xiàn)設(shè)計(jì)上的“松耦合”(一)。其實(shí)嚴(yán)格來講,MEF不能算一種正式的IOC容器,因?yàn)樗闹饕饔眠€是用于應(yīng)用程序擴(kuò)展,避免生成脆弱的硬依賴項(xiàng),而不是依賴注入。根據(jù)博主的了解以及使用經(jīng)歷,常用的IOC容器有:

當(dāng)然,還有其他的IOC容器這里就不一一列舉。Spring.net是從Java的Spring框架移植過來的,功能之強(qiáng)大我們就不多說了,可是自從它宣布不再更新,博主在使用它的時(shí)候就非常慎重了。下面博主還是就Unity這種IOC容器來看看依賴倒置的具體實(shí)現(xiàn)。

1、Unity引入

Unity如何引入?我們神奇的Nuget又派上用場了。最新的Unity版本已經(jīng)到了4.0.1。

安裝成功后主要引入了三個(gè)dll。

2、Unity常用API

復(fù)制代碼
UnityContainer.RegisterType<ITFrom,TTO>();

UnityContainer.RegisterType< ITFrom, TTO >();

UnityContainer.RegisterType< ITFrom, TTO >("keyName");

IEnumerable<T> databases = UnityContainer.ResolveAll<T>();

IT instance = UnityContainer.Resolve<IT>();

T instance = UnityContainer.Resolve<T>("keyName");

UnitContainer.RegisterInstance<T>("keyName",new T());

UnityContainer.BuildUp(existingInstance);

IUnityContainer childContainer1 = parentContainer.CreateChildContainer();
復(fù)制代碼

3、代碼注入方式示例

3.1、默認(rèn)注冊方式

仍然以上面的場景為例說明,我們注入DeviceMML這個(gè)實(shí)現(xiàn)類。

復(fù)制代碼
    class Program
    {
        private static IUnityContainer container = null;
        static void Main(string[] args)
        {
            RegisterContainer();
            var oSpider = container.Resolve<IDevice>();
            oSpider.Login();
            var bRes = oSpider.Spider();

            Console.ReadKey();
        }

        /// <summary>
        /// 代碼注入
        /// </summary>
        public static void RegisterContainer()
        {
            container = new UnityContainer();
            container.RegisterType<IDevice, DeviceMML>();  //默認(rèn)注冊方式,如果后面再次默認(rèn)注冊會(huì)覆蓋前面的
        }
    }
復(fù)制代碼

運(yùn)行結(jié)果

3.2、帶命名方式的注冊

上面默認(rèn)注入的方式中,我們只能注入一種具體的實(shí)例,如果我們需要同時(shí)注入多個(gè)類型的實(shí)例呢?看看我們的 RegisterType() 方法有多個(gè)重載。

復(fù)制代碼
   class Program
    {
        private static IUnityContainer container = null;
        static void Main(string[] args)
        {
            RegisterContainer();
            var oSpider = container.Resolve<IDevice>("TL5");
            oSpider.Login();
            var bRes = oSpider.Spider();

            Console.ReadKey();
        }

        /// <summary>
        /// 代碼注入
        /// </summary>
        public static void RegisterContainer()
        {
            container = new UnityContainer();
           container.RegisterType<IDevice, DeviceMML>("MML");  //默認(rèn)注冊(無命名),如果后面還有默認(rèn)注冊會(huì)覆蓋前面的
            container.RegisterType<IDevice, DeviceTELNET>("Telnet");  //命名注冊
            container.RegisterType<IDevice, DeviceTL2>("TL2");  //命名注冊
            container.RegisterType<IDevice, DeviceTL5>("TL5");  //命名注冊
        }
    }
復(fù)制代碼

運(yùn)行結(jié)果

4、配置文件注入方式示例

在App.config或者Web.config里面加入如下配置:

復(fù)制代碼
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  <unity>
    <!--容器-->
    <containers>
      <container name="Spider">
        <!--映射關(guān)系-->
        <register type="ESTM.Spider.IDevice,ESTM.Spider"  mapTo="ESTM.Spider.DeviceMML,ESTM.Spider" name="MML"></register>
        <register type="ESTM.Spider.IDevice,ESTM.Spider"  mapTo="ESTM.Spider.DeviceTELNET,ESTM.Spider" name="TELNET"></register>
        <register type="ESTM.Spider.IDevice,ESTM.Spider"  mapTo="ESTM.Spider.DeviceTL2,ESTM.Spider" name="TL2"></register>
        <register type="ESTM.Spider.IDevice,ESTM.Spider"  mapTo="ESTM.Spider.DeviceTL5,ESTM.Spider" name="TL5"></register>
      </container>
    </containers>
  </unity>
</configuration>
復(fù)制代碼

在代碼里面注冊配置文件:

復(fù)制代碼
namespace ESTM.Spider
{
    class Program
    {
        private static IUnityContainer container = null;
        static void Main(string[] args)
        {
            ContainerConfiguration();
            var oSpider = container.Resolve<IDevice>("TL5");
            oSpider.Login();
            var bRes = oSpider.Spider();

            Console.ReadKey();
        }

        /// <summary>
        /// 配置文件注入
        /// </summary>
        public static void ContainerConfiguration()
        {
            container = new UnityContainer();
            UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
            configuration.Configure(container, "Spider");
        }

    }
}
復(fù)制代碼

運(yùn)行結(jié)果:

代碼說明

(1)

<register type="ESTM.Spider.IDevice,ESTM.Spider"  mapTo="ESTM.Spider.DeviceMML,ESTM.Spider" name="MML"></register>

節(jié)點(diǎn)里面,type對象抽象,mapTo對象具體實(shí)例對象,name對象實(shí)例的別名。

(2)在app.config里面可以配置多個(gè) <container name="Spider"> 節(jié)點(diǎn),不同的name配置不同的依賴對象。

(3)配置文件注入的靈活之處在于解耦。為什么這么說呢?試想,如果我們的IDevice接口對應(yīng)著一個(gè)接口層,而DeviceMML、DeviceTELNET、DeviceTL2、DeviceTL5等實(shí)現(xiàn)類在另外一個(gè)實(shí)現(xiàn)層里面,我們的UI層(這里對應(yīng)控制臺(tái)程序這一層)只需要添加IDevice接口層的引用,不必添加實(shí)現(xiàn)層的引用,通過配置文件注入,在運(yùn)行的時(shí)候動(dòng)態(tài)將實(shí)現(xiàn)類注入到UI層里面來。這樣UI層就對實(shí)現(xiàn)層實(shí)現(xiàn)了解耦,實(shí)現(xiàn)層里面的具體邏輯變化時(shí),UI層里面不必做任何更改。

四、總結(jié)

 到此,依賴倒置原則的講解基本結(jié)束了。根據(jù)博主的理解,設(shè)計(jì)模式的這些原則是設(shè)計(jì)模式的理論指導(dǎo),而設(shè)計(jì)模式則是這些理論的具體運(yùn)用。說一千道一萬,要想搞懂設(shè)計(jì)模式,必須先了解設(shè)計(jì)模式遵循的原則,無論是哪種設(shè)計(jì)模式都會(huì)遵循一種或者多種原則。當(dāng)然文章可能有理解不當(dāng)?shù)牡胤?,歡迎大牛們指出。博主將會(huì)在接下來的幾章繼續(xù)總結(jié)下其他設(shè)計(jì)原則,如果園友們覺得本文對你有幫助,請幫忙推薦,博主將繼續(xù)努力~~

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多