由淺入深理解 IOC 和 DI
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??踏雪彡尋梅?
來源 |? urlify.cn/A3Ub2u
開閉原則 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?的角度理解?IOCIOC 的奧義
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
