可能很多人並不知道Dagger2是什麼,有什麼用,爲什麼這個開源庫會這麼的熱門。
所以,在使用Dagger2之前,我們先要了解一些設計模式,看完之後想必你會喜歡上這個庫。
一、依賴倒置原則
A. 高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象。
B. 抽象不應該依賴於具體實現,具體實現應該依賴於抽象。
對於依賴倒置原則,百度百科已經做了很詳細的講解 百科--依賴倒置原則
這裏我會再用一個更符合我們Android開發者的例子來說明。
產品經理要求我們寫一個下拉刷新功能,要和QQ的一模一樣。
goole百度無果(假設)之後,我們只能自己動手。我們可能會這麼寫:
Sample1:
/**
* Created by 13797 on 2016/8/19.
* 可下拉刷新的ListView
*/
public class RefreshListView extends ListView {
private QQHeader qqHeader;
public RefreshListView(Context context) {
this(context, null);
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
qqHeader = new QQHeader(context);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int y = (int) ev.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
qqHeader.onPress(y);
break;
case MotionEvent.ACTION_MOVE:
qqHeader.onMove(y);
break;
case MotionEvent.ACTION_UP:
qqHeader.onRelease();
break;
}
return super.onTouchEvent(ev);
}
}
/**
* Created by 13797 on 2016/8/19.
* <p/>
* 高仿QQ的下拉刷新頭部
*/
public class QQHeader extends LinearLayout {
private static final int REFRESH_HEIGHT = 200;
private int downY;
public QQHeader(Context context) {
super(context);
}
public void onPress(int y) {
// 手指按下,記錄當前手指位置
downY = y;
}
public void onMove(int y) {
// 手指移動,改變header的高度
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = y - downY;
setLayoutParams(lp);
}
public void onRelease() {
// 手指擡起,判斷header高度是否大於刷新高度,開始刷新或者回到頂部
if (getHeight() > REFRESH_HEIGHT) {
// 開始刷新
} else {
// 回到頂部
}
}
}
問題1:誰依賴誰?
RefreshListView依賴於QQHeader
因爲RefreshListView持有QQHeader的引用,並且需要QQHeader才能實現下拉刷新的功能。
可能有人不是很明白,那我再舉個例子。
小車和汽油,誰依賴誰?
很清晰的可以得出,小車依賴於汽油,而不是汽油依賴於小車。汽油還可以用到摩托車,飛機上……
而QQHeader同樣可以用到RecyclerView或者ScrollView上……
那麼我麼繼續往下說
下拉刷新功能寫的很不錯,產品經理覺得很滿意。
但是過了兩天,產品經理跟我們說:我還是覺得像美團那種帶動畫效果的比較好看。
然後我們又默默的去修改代碼, RefreshListView改成了這樣的:
Sample 2
/**
* Created by 13797 on 2016/8/19.
* 可下拉刷新的ListView
*/
public class RefreshListView extends ListView {
private MeituanHeader meituanHeader;
public RefreshListView(Context context) {
super(context);
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
meituanHeader = new MeituanHeader(context);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int y = (int) ev.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
meituanHeader.onPress(y);
break;
case MotionEvent.ACTION_MOVE:
meituanHeader.onMove(y);
break;
case MotionEvent.ACTION_UP:
meituanHeader.onRelease();
break;
}
return super.onTouchEvent(ev);
}
}
產品經理覺得很好看,興高采烈的找老闆去了,留下你怨念的眼神。
我們作爲一個有經驗的程序員,爲了防止產品經理又需要改需求,我們需要提高一下控件的擴展性了。
然後我們分析如下:
RefreshListView已經完成了必要事件分發邏輯,功能上是沒問題的。
但是由於RefreshListView依賴於具體的Header編程,因此在更換新的Header時都不得不修改RefreshListView的源碼,我們可以通過引入抽象的Header來解決該問題。
修改後如下:
Sample 3
/**
* Created by 13797 on 2016/8/19.
* 可下拉刷新的ListView
*/
public class RefreshListView extends ListView {
private RefreshHeader refreshHeader;
public RefreshListView(Context context) {
super(context);
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int y = (int) ev.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
refreshHeader.onPress(y);
break;
case MotionEvent.ACTION_MOVE:
refreshHeader.onMove(y);
break;
case MotionEvent.ACTION_UP:
refreshHeader.onRelease();
break;
}
return super.onTouchEvent(ev);
}
}
/**
* Created by 13797 on 2016/8/20.
*/
public interface RefreshHeader {
void onPress(int y);
void onMove(int y);
void onRelease();
}
/**
* Created by 13797 on 2016/8/19.
* 下拉刷新頭部
*/
public class QQHeader extends LinearLayout implements RefreshHeader {
private static final int REFRESH_HEIGHT = 200;
private int downY;
public QQHeader(Context context) {
super(context);
}
@Override
public void onPress(int y) {
// 手指按下,記錄當前手指位置
downY = y;
}
@Override
public void onMove(int y) {
// 手指移動,改變header的高度
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = y - downY;
setLayoutParams(lp);
}
@Override
public void onRelease() {
// 手指擡起,判斷header高度是否大於刷新高度,開始刷新或者回到頂部
if (getHeight() > REFRESH_HEIGHT) {
// 開始刷新
} else {
// 回到頂部
}
}
}
// 美團Header的就不貼出來了,都是僞代碼
public class MeituanHeader extends LinearLayout implements RefreshHeader {
}
可以看到,引入接口之後,RefreshListView針對RefreshHeader編程。在更換Header的時候我們只需要修改構造中refreshHeader
= new MeituanHeader(context);
這行代碼即可。
RefreshListView不依賴於QQHeader或者MeituanHeader,而是依賴於RefreshHeader這個抽象(接口)。
而QQHeader和MeituanHeader同樣依賴於RefreshHeader這個抽象。
這就是依賴倒置原則
抽象不應該依賴於具體實現,具體實現應該依賴於抽象。
抽象不應該依賴於具體,具體應該依賴於抽象。
既然瞭解了依賴倒置原則,那麼這裏就再說一點,爲什麼MVP模式中,View需要使用接口,而Presenter則不需要,或者說沒有必要?
根據依賴倒置原則,高層次的模塊不應該依賴於低層次的模塊,他們都應該依賴於抽象
MVP中,Presenter屬於高層次模塊,而View是低層次,所以View就需要實現接口了,就是這麼簡單……想象一下這種情況:
如果Presenter中拿到的是Activity對象,而不是一個接IView口。
那麼Presenter就可以隨意調用Activity中的方法了,如果真調用了activity的方法,Presenter和Activity就會產生很強的耦合,那麼你就無法單純的使用junit進行單元測試了。
還有就是,如果你想將Activity換成Fragment或者View時,你也需要大量的修改Presenter了。
二、依賴注入
大篇幅的說完依賴倒置之後,那麼什麼是依賴注入呢?
首先我們看RefreshListVIew, 我們在它初始化的時候一起初始化了RefreshHeader。
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
這樣使得RefreshListView不僅依賴於RefreshHeader這個接口,還依賴於MeituanHeader這個實現,當我們想更換header的時候就必須去修改RefreshListView的這段源碼了。
那麼我們可以換另外一種方式實現:
不在RefreshListView初始化的時候一起初始化Header,而是通過setRefreshHeader的方式提供依賴。
當我們想更換Header的時候就不需要修改RefreshListView了。也可以更加方便的定製各個不同的RefreshListView,比如產品說:這個頁面用QQHeader 那個頁面用meituanHeader……
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
// refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
public void setRefreshHeader(RefreshHeader refreshHeader) {
this.refreshHeader = refreshHeader;
}
而這就是我們說的依賴注入了,其實我們開發中經常用到這樣的依賴注入,但是我們從來沒有注意到這件事。因爲代碼敲多了,總會自然而然的貼近原則
以簡單的話來說就是
依賴注入是不在類中實例化其他依賴的類,而是先把依賴的類實例化了,然後以參數的方式傳入構造函數中,
讓上層模塊和依賴進一步解耦。
以上我們用的是setter注入
依賴注入的方式還有另外兩種,一是構造器注入。
比如:
public RefreshListView(Context context, AttributeSet attrs, RefreshHeader refreshHeader ) {
super(context, attrs);
this.refreshHeader = refreshHeader;
// refreshHeader = new MeituanHeader(context);
// refreshHeader = new QQHeader(context);
}
但是很明顯這裏不能使用……因爲RefreshListView是在Xml文件中配置,由Android系統自動生成。所以我們採用Setter注入的方式。
另外還有一種方式就是接口注入,我個人並不認同這是一種方式。
不多解釋,看看維基百科吧,裏面都是代碼,不會英文估計都是能看懂的。
https://en.wikipedia.org/wiki/Dependency_injection
另外還有一點就是Injector
故名思意,就是注入器,負責初始化依賴,並且將依賴注入到上層模塊中。
很明顯上面的下拉刷新例子中,Activity或者Fragment扮演着這個角色。
三、關於原則我已經說完了……
dagger2並不是我要介紹的重點,雖然我有心要寫,但是我發現有很多文章已經寫的很好很好了。
那麼,Dagger2是一個依賴注入的框架,使用它有什麼好處呢?
可以看看這篇文章
使用Dagger 2進行依賴注入
如果覺得我對於依賴注入解釋不夠明朗的話,也可以再看看這篇文章
依賴注入原理
===================================================
原文鏈接 http://www.jianshu.com/p/cc1427e385b5