<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          由淺入深理解 IOC 和 DI

          共 15327字,需瀏覽 31分鐘

           ·

          2020-09-06 15:35

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          ? 作者?|??踏雪彡尋梅?

          來源 |? urlify.cn/A3Ub2u

          66套java從入門到精通實戰(zhàn)課程分享?

          開閉原則 OCP(Open Closed Principle)

          • 對擴展開放,對修改封閉。

            • 修改一處代碼可能會引起其他地方的?bug,最好的方式就是新增業(yè)務模塊/類代替原來的業(yè)務模塊/類,使出現(xiàn)?bug?的幾率變小。

          • 必須滿足此原則的代碼才能算作好的可維護的代碼。

          面向抽象編程

          • 只有面向抽象編程,才能夠逐步實現(xiàn)開閉原則。

            • 統(tǒng)一方法的調(diào)用。

            • 統(tǒng)一對象的實例化。

            • 面臨的兩個問題:

          • 可實現(xiàn)面向抽象編程的語法:

            • 接口(interface)

            • 抽象類(abstract)

          • 只有有了接口和抽象類的概念,多態(tài)性才能夠得到很好的支持。

          • 面向抽象編程的目的: 實現(xiàn)可維護的代碼,實現(xiàn)開閉原則。

            • 面向抽象 -> OCP -> 可維護的代碼

          逐步理解實現(xiàn) IOC 和 DI 的過程(LOL Demo 示例)

          比較尷尬的編寫程序添加需求/更改需求的做法

          • 程序示例:

            • 各英雄類


            • /**
              *


              * Camille 英雄
              *


              *
              * @author 踏雪彡尋梅
              * @version 1.0
              * @date 2020/7/28 - 10:21
              * @since JDK1.8
              */

              public?class?Camille?{
              ????public?void?q() {
              ????????System.out.println("Camille Q");
              ????}

              ????public?void?w() {
              ????????System.out.println("Camille W");
              ????}

              ????public?void?e() {
              ????????System.out.println("Camille E");
              ????}

              ????public?void?r() {
              ????????System.out.println("Camille R");
              ????}
              }

              /**
              ?*


              ?* Diana 英雄
              ?*


              ?*
              ?* @author 踏雪彡尋梅
              ?* @version 1.0
              ?* @date 2020/7/28 - 10:00
              ?* @since JDK1.8
              ?*/

              public?class?Diana?{
              ????public?void?q() {
              ????????System.out.println("Diana Q");
              ????}

              ????public?void?w() {
              ????????System.out.println("Diana W");
              ????}

              ????public?void?e() {
              ????????System.out.println("Diana E");
              ????}

              ????public?void?r() {
              ????????System.out.println("Diana R");
              ????}
              }

              /**
              ?*


              ?* Irelia 英雄
              ?*


              ?*
              ?* @author 踏雪彡尋梅
              ?* @version 1.0
              ?* @date 2020/7/28 - 10:16
              ?* @since JDK1.8
              ?*/

              public?class?Irelia?{
              ????public?void?q() {
              ????????System.out.println("Irelia Q");
              ????}

              ????public?void?w() {
              ????????System.out.println("Irelia W");
              ????}

              ????public?void?e() {
              ????????System.out.println("Irelia E");
              ????}

              ????public?void?r() {
              ????????System.out.println("Irelia R");
              ????}
              }
            • 選擇英雄釋放技能 main 函數(shù)


              /**
              ?*


              ?* 傳統(tǒng)編寫程序添加需求/更改需求的做法
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?1.0
              ?* @date?2020/7/28 - 10:01
              ?* @since?JDK1.8
              ?*/

              public?class?Main?{
              ????public?static?void?main(String[] args)?{
              ????????// 選擇英雄
              ????????String name = Main.getPlayerInput();
              ????????// 新增英雄時需要改此處代碼
              ????????switch?(name) {
              ????????????case?"Diana":
              ????????????????Diana diana = new?Diana();
              ????????????????diana.r();
              ????????????????break;
              ????????????case?"Irelia":
              ????????????????Irelia irelia = new?Irelia();
              ????????????????irelia.r();
              ????????????????break;
              ????????????case?"Camille":
              ????????????????Camille camille = new?Camille();
              ????????????????camille.r();
              ????????????????break;
              ????????????default:
              ????????????????break;
              ????????}
              ????}

              ????private?static?String getPlayerInput()?{
              ????????Scanner scanner = new?Scanner(System.in);
              ????????System.out.println("請輸入一個英雄的名稱: ");
              ????????return?scanner.nextLine();
              ????}
              }
          • 從上面的代碼,可以看出以下幾點:

            • 當增加新的英雄時,需要修改?switch?處的代碼,增加新的?case

            • 各個?case?中的代碼都存在著?new?一個某某英雄,并且調(diào)用了釋放技能的方法。

            • 在真實項目中,大量存在著這樣的?new?是不好的,因為真實項目中類和類的依賴是非常之多的,這個類依賴那個類,那個類又依賴了另一個類。

            • 如果大量存在著這樣的?new?操作,代碼間的耦合度將變得非常高,當某個類的需求產(chǎn)生變化的時候,一旦修改代碼,其他依賴這個類的地方就很有可能引起很多?bug,同時依賴的地方也可能需要修改大量的代碼。

            • 通過上面例子也可以看出,在創(chuàng)建實例對象之后,會調(diào)用這個對象的方法,上面的例子只是簡單地調(diào)用了一個方法,而在真實項目中,依賴的類可能需要調(diào)用它的很多方法。

            • 所以一旦依賴的這個類的代碼產(chǎn)生了變化,比如某某方法不用了,依賴的地方就需要刪除這個調(diào)用,而依賴這個類的類很可能有許多個,就需要更改很多地方的代碼,可見耦合度之高,這也就是為什么這種代碼一旦修改,就很可能出現(xiàn)多個?bug?的原因。

            • 所以,對于這種代碼,是需要優(yōu)化和改良的,不能依賴的太過具體,而是要依賴抽象,即面向抽象編程,下面就一步步演進這個過程,達到逐步理解?IOC?和?DI?的目的。

          使用 interface 接口統(tǒng)一方法的調(diào)用

          • 程序示例:

            • 英雄技能接口類


            • /**
              ?*


              ?* 英雄技能接口類
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?2.0
              ?* @date?2020/7/28 - 10:31
              ?* @since?JDK1.8
              ?*/

              public?interface?ISkill?{
              ????void?q();

              ????void?w();

              ????void?e();

              ????void?r();
              }
            • 各英雄類


            • /**
              ?*


              ?* Camille 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?2.0
              ?* @date?2020/7/28 - 10:21
              ?* @since?JDK1.8
              ?*/

              public?class?Camille?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Camille Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Camille W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Camille E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Camille R");
              ????}
              }

              /**
              ?*


              ?* Diana 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?2.0
              ?* @date?2020/7/28 - 10:00
              ?* @since?JDK1.8
              ?*/

              public?class?Diana?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Diana Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Diana W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Diana E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Diana R");
              ????}
              }

              /**
              ?*


              ?* Irelia 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?2.0
              ?* @date?2020/7/28 - 10:16
              ?* @since?JDK1.8
              ?*/

              public?class?Irelia?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Irelia Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Irelia W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Irelia E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Irelia R");
              ????}
              }
            • 選擇英雄釋放技能 main 函數(shù)


            • /**
              ?*


              ?* 使用 interface 統(tǒng)一方法的調(diào)用
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?2.0
              ?* @date?2020/7/28 - 10:29
              ?* @since?JDK1.8
              ?*/

              public?class?Main?{
              ????public?static?void?main(String[] args)?throws?Exception {
              ????????ISkill iSkill;

              ????????// 選擇英雄
              ????????String name = Main.getPlayerInput();
              ????????// 新增英雄時也需要改此處代碼
              ????????// 這個 switch 提取成一個方法(例如工廠模式)之后,這里的代碼就會變得簡單
              ????????// 只有一段代碼不負責對象的實例化,即沒有 new 的出現(xiàn),才能保持代碼的相對穩(wěn)定,才能逐步實現(xiàn) OCP
              ????????switch?(name) {
              ????????????case?"Diana":
              ????????????????iSkill = new?Diana();
              ????????????????break;
              ????????????case?"Irelia":
              ????????????????iSkill = new?Irelia();
              ????????????????break;
              ????????????case?"Camille":
              ????????????????iSkill = new?Camille();
              ????????????????break;
              ????????????default:
              ????????????????throw?new?Exception();
              ????????}
              ????????// 調(diào)用技能,現(xiàn)在這個版本使用接口統(tǒng)一了方法的調(diào)用,但還不能統(tǒng)一對象的實例化
              ????????// 統(tǒng)一了方法的調(diào)用是意義非常重大的
              ????????// 真實項目中,方法的調(diào)用可能非常的多或者復雜,這種情況下把方法的調(diào)用統(tǒng)一起來,集中在一個接口的方法上面,這個意義非常重大
              ????????iSkill.r();
              ????}

              ????private?static?String getPlayerInput()?{
              ????????Scanner scanner = new?Scanner(System.in);
              ????????System.out.println("請輸入一個英雄的名稱: ");
              ????????return?scanner.nextLine();
              ????}
              }
          • 從以上代碼示例可得出以下幾點:

            • 實質(zhì): 一段代碼如果要保持穩(wěn)定,就不應該負責對象的實例化。

            • 如果各個類中有大量的實例化對象的過程,那么一旦產(chǎn)生變化,影響將非常大。

            • 所以僅僅達到統(tǒng)一方法的調(diào)用還不足夠,還需要達到統(tǒng)一對象的實例化。

            • 統(tǒng)一了方法的調(diào)用是意義非常重大的。

            • 抽象的難點在于將?new?對象這個操作變得更加的抽象,而不是具體。

            • 真實項目中,方法的調(diào)用可能非常的多或者復雜,這種情況下把方法的調(diào)用統(tǒng)一起來,集中在一個接口的方法上面,這個意義非常重大。

            • 單純的?interface?可以統(tǒng)一方法的調(diào)用,但是它不能統(tǒng)一對象的實例化。

            • 面向?qū)ο蠛芏鄷r候都是在做兩件事情: 實例化對象,調(diào)用方法(完成業(yè)務邏輯)。

            • 由以上幾點可得出只有一段代碼不負責對象的實例化,即沒有?new?的出現(xiàn),才能保持代碼的相對穩(wěn)定,才能逐步實現(xiàn)?OCP。(表象)

            • 當然,對象實例化是不可能消除的,我們需要把對象實例化的過程轉(zhuǎn)移到其他的代碼片段里,即把所有這些對象實例化的過程全部隔離到一個地方,這樣子除了這個地方外的其他地方的代碼就會變得非常穩(wěn)定(最簡單的方式為使用工廠模式,接下來的版本將演示這個過程)。

          使用工廠模式把對象實例化的過程隔離

          • 三種子模式:

            • 對工廠的一種抽象。

            • 對生產(chǎn)的對象的一種抽象。

            • 簡單工廠模式

            • 普通工廠模式

            • 抽象工廠模式

          • 使用簡單工廠模式把對象實例化的過程轉(zhuǎn)移到其他的代碼片段里:

            • 程序示例:

            • 英雄技能接口類


            • /**
              ?*


              ?* 英雄技能接口類
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 10:31
              ?* @since?JDK1.8
              ?*/

              public?interface?ISkill?{
              ????void?q();

              ????void?w();

              ????void?e();

              ????void?r();
              }
            • 各英雄類


            • /**
              ?*


              ?* Camille 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 10:21
              ?* @since?JDK1.8
              ?*/

              public?class?Camille?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Camille Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Camille W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Camille E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Camille R");
              ????}
              }

              /**
              ?*


              ?* Diana 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 10:00
              ?* @since?JDK1.8
              ?*/

              public?class?Diana?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Diana Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Diana W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Diana E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Diana R");
              ????}
              }

              /**
              ?*


              ?* Irelia 英雄
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 10:16
              ?* @since?JDK1.8
              ?*/

              public?class?Irelia?implements?ISkill?{
              ????@Override
              ????public?void?q()?{
              ????????System.out.println("Irelia Q");
              ????}

              ????@Override
              ????public?void?w()?{
              ????????System.out.println("Irelia W");
              ????}

              ????@Override
              ????public?void?e()?{
              ????????System.out.println("Irelia E");
              ????}

              ????@Override
              ????public?void?r()?{
              ????????System.out.println("Irelia R");
              ????}
              }
            • 生產(chǎn)英雄的工廠類


            • /**
              ?*


              ?* 英雄工廠類,生產(chǎn)或?qū)嵗⑿垲?把對象實例化的過程隔離
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 21:11
              ?* @since?JDK1.8
              ?*/

              public?class?HeroFactory?{
              ????/**
              ?????* 簡單工廠實例化英雄類
              ?????*
              ?????* @param?name 英雄名稱
              ?????* @return?返回英雄名稱對應的實例
              ?????*/

              ????public?static?ISkill getHero(String name) throws Exception?{
              ????????ISkill iSkill;

              ????????// 變化是導致代碼不穩(wěn)定的本質(zhì)原因
              ????????// 所有的變化最終其實都要交給不同的對象去處理,當業(yè)務或用戶的輸入有了變化的時候,必須要創(chuàng)建不同的對象去響應這些變化
              ????????// 這里的變化: 用戶的輸入,選擇英雄導致的不穩(wěn)定,根據(jù)用戶的輸入實例化不同的對象
              ????????// 也例如改動程序使用的數(shù)據(jù)庫,從 MySQL 更改為 Oracle
              ????????// 如何消除這個變化?
              ????????// 思考:
              ????????// 1. 這里是用戶只能夠輸入一個字符串,把輸入的字符串轉(zhuǎn)換為一個對象
              ????????// 2. 但是如果用戶能夠直接輸入一個對象傳給程序,這個 switch 就可以被干掉(使用反射解決,把輸入的字符串轉(zhuǎn)換為一個對象)
              ????????switch?(name) {
              ????????????case?"Diana":
              ????????????????iSkill = new?Diana();
              ????????????????break;
              ????????????case?"Irelia":
              ????????????????iSkill = new?Irelia();
              ????????????????break;
              ????????????case?"Camille":
              ????????????????iSkill = new?Camille();
              ????????????????break;
              ????????????default:
              ????????????????throw?new?Exception();
              ????????}

              ????????return?iSkill;
              ????}
              }
            • 選擇英雄釋放技能 main 函數(shù)


            • /**
              ?*


              ?* 使用簡單工廠模式把對象實例化的過程轉(zhuǎn)移到其他的代碼片段里(IOC 的雛形)
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?3.0
              ?* @date?2020/7/28 - 21:06
              ?* @since?JDK1.8
              ?*/

              public?class?Main?{
              ????public?static?void?main(String[] args)?throws?Exception {
              ????????// 選擇英雄
              ????????String name = Main.getPlayerInput();
              ????????// 調(diào)用工廠方法,這里把 new 的操作干掉了,這里的代碼已經(jīng)相對穩(wěn)定了,新增英雄時這里的代碼不需要再更改,只需要更改工廠方法的代碼
              ????????// 對于 main 方法而言,它實現(xiàn)了 OCP,而工廠方法中的代碼還沒有實現(xiàn) OCP
              ????????// 雖然這里的代碼已經(jīng)相對穩(wěn)定了,但是還引用著 HeroFactory 工廠類,對于這行代碼,還不是非常穩(wěn)定,還存在著可能更換修改的可能
              ????????// 例如說: HeroFactory 的 getHero 方法是個實例方法,那么 HeroFactory 也需要 new 出來,這種情況下會存在著修改代碼的可能
              ????????// 如果業(yè)務邏輯足夠復雜,可能存在很多這種工廠類,這樣看起來對于工廠類而言,需求變更時還是需要改動很多代碼
              ????????// 當然也可以使用抽象工廠將工廠抽象化使這里變得穩(wěn)定起來,但這里不演示了,這里只是演示一個如何隔離變化的過程,所以假設這里是穩(wěn)定的,是一個超級工廠,能夠生產(chǎn)項目的各種對象
              ????????// 當假設有一個超級的工廠之后,這個工廠可以兼容整個項目的工廠,把整個項目的所有的變動都封裝到一起,從而保證除了這個超級工廠之外的代碼都是穩(wěn)定的,這樣這個工廠就有了意義
              ????????// 其實 IOC 也就是相當于一個非常大的容器一樣,把所有的變化都集中到了一個地方,寫其他的代碼就會變得非常容易,不再需要在整個項目中到處更改代碼,如果出現(xiàn)了變化,只需要讓容器去負責改變即可
              ????????// spring ioc 中的 ApplicationContext 就類似于這個超級工廠,通過 ApplicationContext 可以獲取各種各樣的對象,不過 ApplicationContext 在 spring 中給的是一個接口,即抽象工廠模式
              ????????// 需要注意的是: 生產(chǎn)對象只是 IOC 的一部分,不是 IOC 的全部
              ????????ISkill iSkill = HeroFactory.getHero(name);
              ????????// 調(diào)用技能
              ????????iSkill.r();
              ????}

              ????private?static?String getPlayerInput()?{
              ????????Scanner scanner = new?Scanner(System.in);
              ????????System.out.println("請輸入一個英雄的名稱: ");
              ????????return?scanner.nextLine();
              ????}
              }
          • 從以上例子可得出以下幾點:

            • 其實?IOC?就是將這些不穩(wěn)定(變化)給封裝、隔離到了一塊,保證其他地方的代碼是穩(wěn)定的。

            • 變化是導致代碼不穩(wěn)定的本質(zhì)原因。

            • 那么如何消除這些變化呢?

            • 在上面的示例中,用戶只能輸入一個字符串,然后在工廠方法內(nèi)去判斷用戶的輸入的變化,創(chuàng)建不同的對象去響應這些變化。

            • 同時,如果有新增的英雄,勢必要工廠方法中的?switch?代碼。

            • 那么如果有這么一個機制,可以實現(xiàn)用戶的輸入輸入進來就是一個對象,然后就創(chuàng)建這個對象進行響應,而不是像上面的判斷字符串,那么就可以干掉?switch,使這里的代碼變得更加簡單,更加穩(wěn)定。

            • 對于這種機制,也就是反射機制,下面的版本將演示這個過程。

            • 用戶的輸入、用戶的選擇、用戶的操作造成的變化。

            • 軟件自身的業(yè)務需求或技術(shù)選擇有了變化。

            • 對于技術(shù)選擇的改變,例如從使用?MySQL?更換到?Oracle,如果將這個變化提取到配置文件中,那么配置文件的變化是允許的,并不違反?OCP

            • 配置文件是屬于系統(tǒng)外部的,而不屬于代碼本身。(這里的配置文件也可以理解為用戶的輸入,把需求的變化隔離到了配置文件中)

            • 注意事項:

            • 變化有這么兩大類變化:

            • 所有的變化最終其實都要交給不同的對象去處理,當業(yè)務或用戶的輸入有了變化的時候,必須要創(chuàng)建不同的對象去響應這些變化。

            • 代碼中總是會存在不穩(wěn)定,要盡可能地隔離這些不穩(wěn)定,保證其他的代碼是穩(wěn)定的。隔離不穩(wěn)定其實就是在隔離變化。

          使用反射隔離工廠中的變化,讓用戶直接輸入一個對象

          • 對于這個版本,只有工廠類發(fā)生了變動,所以只展示工廠類的代碼和 main 函數(shù)的代碼

          • 程序示例:

            • 生產(chǎn)英雄的工廠類


            • /**
              ?*


              ?* 英雄工廠類,生產(chǎn)或?qū)嵗⑿垲?把對象實例化的過程隔離
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?4.0
              ?* @date?2020/7/28 - 21:11
              ?* @since?JDK1.8
              ?*/

              public?class?HeroFactory?{
              ????/**
              ?????* 簡單工廠實例化英雄類
              ?????*
              ?????* @param?name 英雄名稱
              ?????* @return?返回英雄名稱對應的實例
              ?????*/

              ????public?static?ISkill getHero(String name)?throws?Exception {
              ????????// 使用反射隔離工廠中的變化,讓用戶直接輸入一個對象,即把用戶輸入的字符串轉(zhuǎn)換為一個對象
              ????????// 反射的作用: 動態(tài)的創(chuàng)建對象
              ????????// 根據(jù)輸入的英雄名稱獲取元類
              ????????// 類是對象的抽象,描述對象
              ????????// 元類是類的抽象,是對類的描述
              ????????// 需要注意在名稱前加上包路徑 cn.xilikeli.lol.v4.hero.
              ????????name = "cn.xilikeli.lol.v4.hero."?+ name;
              ????????Class classA = Class.forName(name);
              ????????// 通過元類實例化對應的實例對象
              ????????// 注意點: java8 之后 newInstance 已經(jīng)廢棄
              ????????// 新版本中使用 classA.getDeclaredConstructor().newInstance()
              ????????Object obj = classA.newInstance();
              ????????// 強制轉(zhuǎn)型返回
              ????????return?(ISkill) obj;
              ????}
              }
            • 選擇英雄釋放技能 main 函數(shù)


            • /**
              ?*


              ?* 使用反射隔離工廠中的變化,讓用戶直接輸入一個對象,即把用戶輸入的字符串轉(zhuǎn)換為一個對象
              ?*


              ?*
              ?* @author?踏雪彡尋梅
              ?* @version?4.0
              ?* @date?2020/7/28 - 22:26
              ?* @since?JDK1.8
              ?*/

              public?class?Main?{
              ????public?static?void?main(String[] args)?throws?Exception {
              ????????// 選擇英雄
              ????????String name = Main.getPlayerInput();
              ????????ISkill iSkill = HeroFactory.getHero(name);
              ????????// 調(diào)用技能
              ????????iSkill.r();
              ????}

              ????private?static?String getPlayerInput()?{
              ????????Scanner scanner = new?Scanner(System.in);
              ????????System.out.println("請輸入一個英雄的名稱: ");
              ????????return?scanner.nextLine();
              ????}
              }
          • 從以上示例可得出以下幾點:

            • 因為現(xiàn)在的實現(xiàn)使用起來還不方便,是一個正向的思維。每次創(chuàng)建一個對象都需要引入這個工廠類調(diào)用其方法。也就是說工廠的方式在實現(xiàn)的邏輯中是正向的創(chuàng)建對象,而?IOC?是反向的,是容器根據(jù)需求主動注入的。

            • 那么有什么方法可以讓工廠類不出現(xiàn),直接可以拿到需要的對象?

            • 這就是?IOC?和?DI?需要做的事情。IOC?和?DI?的雛形至此也就出來了。

            • 如果需要的對象可以由一個什么東西例如某個容器中將其傳進來,這個需要的對象就可以不需要使用工廠類創(chuàng)建了,此時代碼變得更加簡單、更加穩(wěn)定。

            • 這種形式就是?IOC?和?DI?的體現(xiàn),把對象的控制權(quán)交給了容器,即控制反轉(zhuǎn);獲取對象時只需要直接使用它即可,容器會自動把這個對象創(chuàng)建好給傳入進來,即依賴注入。

            • 在前面也談到了配置文件,其實?IOC?簡單理解就是?工廠模式 + 反射機制 + 配置文件?組成了一個大的容器,我們需要什么就配置什么,容器就會創(chuàng)建對象,把創(chuàng)建好的對象提供給我們,這個過程中我們沒有感受到創(chuàng)建對象的正向過程,而是感受到使用的對象是容器給我們的,這是一個反向的過程,也就是控制權(quán)由我們反轉(zhuǎn)到了容器中,容器掌控著創(chuàng)建對象的權(quán)利,即控制反轉(zhuǎn)。

            • 現(xiàn)在這個版本并沒有運用到任何?IOC?和?DI?的原理,只是讓代碼變得非常穩(wěn)定。

            • 現(xiàn)在這種形式是正向思維,雖然現(xiàn)在是實現(xiàn)了需要什么就可以返回什么,但是現(xiàn)在拿到對象的方式依然是需要什么然后去調(diào)用什么類下面的什么方法得到什么的方式。

            • 而?IOC?是控制反轉(zhuǎn),現(xiàn)在這里并沒有反轉(zhuǎn),同時也沒有注入,現(xiàn)在只是實現(xiàn)了?OCP

            • 現(xiàn)在這種形式,每次輸入都進行一次反射是性能比較低的,因為頻繁地反射會使性能變低。

            • 而?Spring?中取到或?qū)嵗粋€對象之后,會把這個對象放到它的緩存中去,下次要再取或創(chuàng)建相同的對象的時候,不會進行反射,而是從緩存中拿(和?DI?有關(guān)系)。

            • 在使用了反射機制之后,已經(jīng)消除了所有的變化,不管輸入的是什么,代碼都不需要再做更改了,代碼已經(jīng)變得非常穩(wěn)定了。

            • Spring?內(nèi)部其實也是使用了類似現(xiàn)在這種工廠模式 + 反射的機制,但是要比現(xiàn)在實現(xiàn)的這種形式更加地完善更加地聰明。

            • 需要注意的是:?現(xiàn)在這種工廠模式 + 反射的機制還不是?IOC?和?DI

            • 那么,問題來了,現(xiàn)在已經(jīng)實現(xiàn)了?OCP,那么還需要?IOC?和?DI?干嘛?

            • 到了此處,也可以隱隱約約地明白了?IOC?和?DI?到底是個什么東西了,接下來再對這兩個東西解釋一下,以便理解地更加深刻。

          IOC/DI/DIP

          • DIP(Dependency Inversion Principle,依賴倒置)

            • 高層: 抽象,抽象也就是站在更高的角度概括具體。

            • 低層: 具體的實現(xiàn)。

            • 高層模塊不應該依賴低層模塊,兩者都應該依賴抽象。

            • 抽象不應該依賴細節(jié)。

            • 細節(jié)應該依賴抽象。

            • 倒置: 正常編寫代碼時可能會?new?一個對象,即依賴了一個具體;而倒置就是不依賴具體而是反過來依賴一個接口(抽象)。

          • DI

            • 容器其實是在裝配這一個個的對象。

            • 即各個類依賴了各個類,他們彼此之間的裝配不是由他們在代碼中?new?出來的。而是全部交給容器,由容器來裝配。

            • 當把裝配的過程交給了容器了之后,我們在編寫類時的只需要負責類的編寫就行了,而不需要關(guān)心如何裝配。由容器來決定某個類依賴的對象到底是哪一個對象,由它把對象注入到類里。

            • 同時在編寫類的時候,也不需要關(guān)心依賴的對象,因為依賴的不是一個具體的對象,而是一個抽象,例如接口。最終實例化的是這個接口的哪一個實現(xiàn)類,編寫類的時候是不需要關(guān)心的,只需要關(guān)心接口即可,對于實例化哪個實現(xiàn)類則由容器來決定。這就保證了我們在編寫一個個類的時候類是獨立的,保證了代碼的穩(wěn)定性。保證了系統(tǒng)的耦合度是非常低的。即面向抽象的重要性,面向接口去編程。

            • 容器在創(chuàng)建一個對象實例的時候可以把這個對象依賴的對象實例注入進去。相當于我們自己寫代碼的時候?new?對象時把一個對象傳進去或賦值以及可以調(diào)用?set?方法傳入一個對象或賦值。

            • 屬性注入


            • public?class?A?{
              ??private?IC ic;

              ??// 屬性注入,容器在實例化 A 的時候會 set 一個 ic 進來
              ??public?void?setIc(IC ic)?{
              ????this.ic = ic;
              ??}
              }
            • 構(gòu)造注入


            • public?class?A?{
              ??private?IC ic;

              ??// 構(gòu)造注入,容器在實例化 A 的時候會給構(gòu)造函數(shù)傳一個 ic 進來
              ??public?A(IC ic)?{
              ????this.ic = ic;
              ??}
              }
            • 接口注入(用的較少)

            • 對象與對象之間的相互作用必定是要產(chǎn)生依賴的,這個是不可避免的,關(guān)鍵是產(chǎn)生依賴的方式是多種多樣的,比如?new?的方式,不過這個方法不好,因為是一個具體實例化的過程,如果依賴的類的代碼改變了,使用這個依賴的類的地方就會變得不穩(wěn)定。

            • 更好的方式: 不再使用?new?的方式,而是讓容器(容器可以理解為在系統(tǒng)的最上層)把需要依賴的類的抽象(例如接口)的實現(xiàn)給注入進來,雖然也產(chǎn)生了依賴,但是依賴的形式是不同的,是注入進來的,并且注入進來的類是抽象的實現(xiàn),依賴只是依賴抽象的接口,相比?new?來說產(chǎn)生的依賴不這么具體。

            • 依賴注入的幾種形式:

            • 依賴注入的原理

            • 依賴注入在更高角度的意義

          • IOC

            • 整個程序運行的控制權(quán)是誰的?

            • IOC 舉例

            • 如果需求固定不變,就沒有什么問題。

            • 但是如果存在著變化,就會有問題。一旦產(chǎn)生了變化,程序員就要去更改控制代碼(變化指的不是新增的業(yè)務代碼,而是指控制代碼,控制代碼: 例如原來?new?了一個什么,要改成?new?一個新的什么)。

            • 如果此時反轉(zhuǎn)過來,程序員不再控制這些控制代碼,而是交給別人控制,所有除了控制代碼之外的代碼都是非常穩(wěn)定的。也就是反過來是用戶在控制代碼,也可以理解為產(chǎn)品經(jīng)理來控制整個應用程序。

            • 例如: 原來用的?MySQL,產(chǎn)品經(jīng)理改成了?Oracle,程序員肯定要負責新增代碼實現(xiàn)?Oracle?的功能,但是至于應用程序用的是原來的?MySQL?還是新加的?Oralce,現(xiàn)在不再由程序員去控制,而是產(chǎn)品經(jīng)理去控制,控制權(quán)由產(chǎn)品經(jīng)理決定,即控制反轉(zhuǎn)。

            • 其實本質(zhì)上還是由程序員決定的。

            • 只負責生產(chǎn)一個個積木,不再負責積木的搭建。

            • 由玩具/用戶負責使用生產(chǎn)的這些一個個積木,搭建出各種各樣的形狀。

            • 原來的話可能是直接把積木給組裝好,如果用戶說不想要這個組裝好的積木,就需要廠家來改(控制);但現(xiàn)在不一樣了,因為生產(chǎn)的只是一個個的積木(可以理解為類),至于怎么去組裝它,則是玩家和用戶來構(gòu)建了(用哪些類交給產(chǎn)品經(jīng)理或其他人去決定)。

            • 積木生產(chǎn)廠家(程序員)

            • 當在一個類(這里用?A?表示)中使用?new?來創(chuàng)建一個需要的對象時,主控類為?A?類

            • 如果應用了?DI?之后,有了容器的概念,此時主控方為容器,主控的地方不再是?A?類了,這個其實就是實現(xiàn)了控制反轉(zhuǎn),全部交給了容器去控制。

            • IOC?本身概念非常抽象和模糊,只是展示了一種思想,并沒有給出具體的實現(xiàn)。

            • 而?DI?可以看做?IOC?的一個具體的實現(xiàn)。

            • 從?DI?的角度理解?IOC

            • IOC 的奧義



          粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

          ???

          ?長按上方鋒哥微信二維碼?2 秒
          備注「1234」即可獲取資料以及
          可以進入java1234官方微信群



          感謝點贊支持下哈?

          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  好看的一区二区 | 日韩十八禁网站 | 影音先锋成人影院 | 日本黄色视频免费网站 | 69天堂|