不詩意的女程序媛不是好廚師~
轉載請註明出處,From李詩雨—https://blog.csdn.net/cjm2484836553/article/details/104449190
《依賴注入 初相見》
上篇我們學習了註解的基本知識,今天我們再來學習一下依賴注入。
當讓我們也不是一下子就來說依賴注入,而是秉着循序漸進的原則,一點一點的理解。
那就讓我們開始吧~
1.什麼是依賴(Dependency)?
通俗點來講 依賴 就是一種需要。
依賴是類與類之間的連接,依賴關係表示一個類依賴於另一個類的定義。
【舉個栗子】:
例如一個人(Person)可以買車(Car)和房子(House),那Person類就依賴於Car類和House類。
public static void main(String[] args) {
Person person = new Person();
person.buy(new House());
person.buy(new Car());
}
static class Person {
//表示依賴House
public void buy(House house) {
System.out.println("買了套房");
}
//表示依賴Car
public void buy(Car car) {
System.out.println("買了輛車");
}
}
static class House {
}
static class Car {
}
嗯吶,瞭解了依賴是什麼,接下來繼續下一個概念:依賴倒置 ~
2.什麼是 依賴倒置 ?
2.0 順序依賴
有時候理解一個東西,通過反面來理解也不失爲一種好的方法。
那依賴倒置的反面就是不依賴倒置了,就是順序依賴了。
什麼是順序依賴呢?
比如說:
人的出行依賴於車子,那如果出行由自行車變成了汽車,人對應的類就要改變;如果又變成了火車,人對應的類又要改變。
人依賴於車子,車子一變,人就要跟着改變,這種就叫順序的依賴。
public class Person {
private Bike mBike;
private Car mCar;
private Train mTrain;
public Person() {
mBike = new Bike();
//mCar = new Car();// ①變---改爲汽車
//mTrain = new Train();//②變---改爲火車
}
public void goOut() {
System.out.println("依賴於車子要出門了~");
mBike.drive();
//mCar.drive(); //①跟着變
//mTrain.drive(); //②跟着變
}
public static void main(String... args) {
Person person = new Person();
person.goOut();
}
}
好的,瞭解了順序的依賴,那依賴的倒置就是反過來嘍,那就讓我再來繼續看看吧~
2.1依賴倒置的定義
依賴倒置是面向對象設計領域的一種軟件設計原則。
依賴倒置原則(Dependence Inversion Principle,簡稱DIP)
- 核心思想:高層模塊不應該依賴底層模塊,二者都該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象;
- 說明:高層模塊就是調用端,低層模塊就是具體實現類。抽象就是指接口或抽象類。細節就是實現類。
- 通俗來講: 依賴倒置原則的本質就是通過抽象(接口或抽象類)使個各類或模塊的實現彼此獨立,互不影響,實現模塊間的鬆耦合。
- 問題描述: 類A直接依賴類B,假如要將類A改爲依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責複雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
- 解決方案: 將類A修改爲依賴接口interface,類B和類C各自實現接口interface,類A通過接口interface間接與類B或者類C發生聯繫,則會大大降低修改類A的機率。
- 好處:依賴倒置的好處在小型項目中很難體現出來。但在大中型項目中可以減少需求變化引起的工作量。使並行開發更友好。
我們現在知道了依賴倒置原則的核心是:
- 上層模塊不應該依賴底層模塊,它們都應該依賴於抽象。
- 抽象不應該依賴於細節,細節應該依賴於抽象。
那什麼是上層模塊和底層模塊 ,什麼又是抽象和細節呢?
我們來繼續往下看~
2.2上層模塊和底層模塊
就拿一個公司來說吧,它一定有架構的設計、有職能的劃分。按照職能的重要性,自然而然就有了上下之分。並且,隨着模塊的粒度劃分不同這種上層與底層模塊會進行變動,也許某一模塊相對於另外一模塊它是底層,但是相對於其他模塊它又可能是上層。
如圖,公司管理層就是上層,CEO 是整個事業羣的上層,那麼 CEO 職能之下就是底層。
然後,我們再以事業羣爲整個體系劃分模塊,各個部門經理以上部分是上層,那麼之下的組織都可以稱爲底層。
由此,我們可以看到,在一個特定體系中,上層模塊與底層模塊可以按照決策能力高低爲準繩進行劃分。
那麼,映射到我們軟件實際開發中,一般我們也會將軟件進行模塊劃分,比如業務層、邏輯層和數據層。
業務層中是軟件真正要進行的操作,也就是做什麼。
邏輯層是軟件現階段爲了業務層的需求提供的實現細節,也就是怎麼做。
數據層指業務層和邏輯層所需要的數據模型。
因此,如前面所總結,按照決策能力的高低進行模塊劃分。業務層自然就處於上層模塊,邏輯層和數據層自然就歸類爲底層。
2.3抽象和具體
抽象如其名字一樣,是一件很抽象的事物。
抽象往往是相對於具體而言的,具體也可以被稱爲細節。
比如:
1.交通工具是抽象,而公交車、單車、火車等就是具體了。
2.表演是抽象,而唱歌、跳舞、小品等就是具體。
由此可見,抽象可以是物也可以是行爲。
在我們實際的軟件開發中,抽象有兩種形式:接口 & 抽象類。
/**
* Driveable 是接口,所以它是抽象
*/
public interface Driveable {
void drive();
}
/**
* 而 Bike 實現了接口,它被稱爲具體。
*/
public class Bike implements Driveable {
@Override
public void drive() {
System.out.println("Bike drive");
}
}
2.4依賴倒置的好處
在上述的人與車子的例子中,只要車子的類型一變,Person類就要跟着改動。
那有沒有一種方法能讓 Person 的變動少一點呢?
因爲畢竟我們寫的是最基礎的演示代碼,如果工程大了,代碼複雜了,Person 面對需求變動時改動的地方會更多。
而依賴倒置原則正好適用於解決這類情況。
下面,我們嘗試運用依賴倒置原則對代碼進行改造。
我們再次回顧下它的定義。
上層模塊不應該依賴底層模塊,它們都應該依賴於抽象。
抽象不應該依賴於細節,細節應該依賴於抽象。
首先是上層模塊和底層模塊的拆分。
按照決策能力高低或者重要性劃分,Person 屬於上層模塊,Bike、Car 和 Train 屬於底層模塊。
上層模塊不應該依賴於底層模塊。
public class Person {
//======順序的依賴======
//private Bike mBike;
//private Car mCar;
//private Train mTrain;
//=====依賴倒置=====
private Driveable mDriveable;
public Person() {
//======順序的依賴======
//mBike = new Bike();
//mCar = new Car();// ①變---改爲汽車
//mTrain = new Train();//②變---改爲火車
//=====依賴倒置=====
mDriveable = new Train(); //依賴倒置,只需要改這裏就可以了,其他地方不用修改了
}
public void goOut() {
System.out.println("依賴於車子要出門了~");
//======順序的依賴======
//mBike.drive();
//mCar.drive(); //①跟着變
//mTrain.drive(); //②跟着變
//=====依賴倒置=====
mDriveable.drive();
}
public static void main(String... args) {
Person person = new Person();
person.goOut();
}
}
可以看到,依賴倒置實質上是面向接口編程的體現。
好的,通過上面的講解相信你已經漸漸體會到什麼是依賴倒置了。
但是,在編碼的過程中,我們還是發現它的一個不足,那就是它不符合開閉原則。
每次我們都要修改Person類的內部,那我們能不能再不修改Person類內部的情況下,來實現同樣的功能呢?
答案當時是肯定的,所以接下來我們再來學習一下 控制反轉。
3.什麼是控制反轉?
控制反轉( IoC )是 Inversion of Control的縮寫,意思就是對於控制權的反轉。
上面的實例中,Person自己掌控着內部 mDriveable 的實例化。
現在,我們可以更改一種方式。將 mDriveable 的實例化移到 Person 外面。
public class Person2 {
private Driveable mDriveable;
public Person2(Driveable driveable) {
this.mDriveable = driveable;
}
public void goOut() {
System.out.println("出門啦");
mDriveable.drive();
}
public static void main(String... args) {
Person2 person = new Person2(new Car());//變爲了在Person的外部進行改變
person.goOut();
}
}
就這樣無論出行方式怎麼變化,Person 這個類都不需要更改代碼了。
在上面代碼中,Person 把內部依賴的創建權力移交給了 Person2這個類中的 main() 方法。也就是說 Person 只關心依賴提供的功能,但並不關心依賴的創建。
這種思想其實就是 IoC,IoC 是一種新的設計模式,它對上層模塊與底層模塊進行了更進一步的解耦。控制反轉的意思是反轉了上層模塊對於底層模塊的依賴控制。
好了,學習了這麼多的概念,現在我們終於有了一定的基礎來學習依賴注入了~
4.什麼是依賴注入
依賴注入,也經常被簡稱爲 DI,其實在上一節中,我們已經見到了它的身影。它是一種實現 IoC 的手段。什麼意思呢?
爲了不因爲依賴實現的變動而去修改 Person,也就是說以可能在 Driveable 實現類的改變下不改動 Person 這個類的代碼,儘可能減少兩者之間的耦合。我們需要採用上一節介紹的 IoC 模式來進行改寫代碼。
這個需要我們移交出對於依賴實例化的控制權,那麼依賴怎麼辦?Person 無法實例化依賴了,它就需要在外部(IoC 容器)賦值給它,這個賦值的動作有個專門的術語叫做注入(injection),需要注意的是在 IoC 概念中,這個注入依賴的地方被稱爲 IoC 容器,但在依賴注入概念中,一般被稱爲注射器 (injector)。
表達通俗一點就是:我不想自己實例化依賴,你(injector)創建它們,然後在合適的時候注入給我。
實現依賴注入有 3 種方式:
- 構造函數中注入
- setter 方式注入
- 接口注入
/**
* 接口方式注入
* 接口的存在,表明了一種依賴配置的能力。
*/
public interface DepedencySetter {
void set(Driveable driveable);
}
public class Person2 implements DepedencySetter {
//接口方式注入
@Override
public void set(Driveable driveable) {
this.mDriveable = mDriveable;
}
private Driveable mDriveable;
//構造函數注入
public Person2(Driveable driveable) {
this.mDriveable = driveable;
}
//setter 方式注入
public void setDriveable(Driveable mDriveable) {
this.mDriveable = mDriveable;
}
public void goOut() {
System.out.println("出門啦");
mDriveable.drive();
}
public static void main(String... args) {
Person2 person = new Person2(new Car());
person.goOut();
}
}
所以,依賴注入大家要記住的重點就是 : 依賴注入是實現控制反轉的手段。
積累點滴,做好自己~