Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 使用Dragger2前你必須了解的一些設計原則

使用Dragger2前你必須了解的一些設計原則

編輯:關於Android編程

可能很多人並不知道Dragger2是什麼,有什麼用,為什麼這個開源庫會這麼的熱門。
所以,在使用Dragger2之前,我們先要了解一些設計模式,看完之後想必你會喜歡上這個庫。

一、依賴倒置原則

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.
 * 

* 高仿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扮演著這個角色。

三、關於原則我已經說完了……

dragger2並不是我要介紹的重點,雖然我有心要寫,但是我發現有很多文章已經寫的很好很好了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved