Otto: No more callbacks

傳統的方式

你要作一個todo app,有一個Activity裏面有一個ListView顯示你全部的task,你的數據存儲在服務器。假設你沒采用任何的軟件架構(MVC、MVP、MVVM等等),每次app打開的時候,你從服務器把數據load下來,load完了之後,經過callback把數據傳給Activity,而後顯示到listview裏面。代碼結構大概是這個樣子java

public class TasksActivity extends Activity {
    private ListView mListView;
    //...
    private void loadTasks() {
        TaskModel model = new TaskModel();
        model.setTaskCallback(new TaskCallback() {
            public void onError(String msg, int code) {
                // handle error
            }
            
            public void onSucceed(List<Task> tasks) {
                updateTaskList(tasks);
            }
        });
        model.loadTasks();
    }
    
    private void updateTaskList(List<Task> tasks) {
        //Update the task list view
    }
    // other code
}

public class Task {
    // title, label, ..., and their getters and setters.
}

public class TaskModel {
    public void loadTasks() {
        //load tasks from server, when result returns, check if callback is not null and call callback
    }
    
    private TaskCallback mTaskCallback;
    public void setTaskCallback(TaskCallback callback) {
        this.mTaskCallback = callback;
    }
    
    public static interface TaskCallback {
        public void onError(String errorMsg, int code);
        public void onSucceed(List<Task> tasks);
    }
}

在這裏,當TaskModel load完了task之後,你用的是callback來通知Activity,task已經load好了。這種callback的方式是java裏面很是傳統的方式,尤爲是涉及到異步的時候。這種方式雖然能work,但倒是很是麻煩,也很是醜陋的方式。
首先,你要定義Callback接口,而後在用的地方實現這個接口。
再次,你要在你的model裏面申明一個callback的成員變量,在用的時候,還要判斷一下成員變量是否是空的。固然你能夠用NullObject 模式,但這也須要額外的工做。
最後,當你發現定義的callback接口要變的時候,你要改動的地方可能很是大,由於你可能有不少的地方實現了這個接口,這是很是煩人也是很是容易出錯的工做。
這一來二去,當你的model比較多的時候,你會變得很是煩,這徹底就是體力勞動啊!
那有沒有更好地方式來解決這個問題呢?你可能會想到是Handler或者是BroadcastReceiver,然而他們也不是很好用的東西,都要定義一些東西,判斷message,判斷action,傳遞一些引用等等。git

Otto的方式

這裏介紹一個library叫Otto,這是Square這個公司的一個開源項目,它是一個EventBus的library。簡單的來講,它相似於定義了一套Observer Pattern的便捷的實現方式。你在某一個地方使用@Subscribe 表示你要處理某一種事件,在某個地方用post發佈這一種事件,那麼前面用@Subscribe修飾的方法就能夠自動獲得調用。不用定義接口,不用實現接口,一切都很直觀,如你所願。
關於Otto的使用官網說的很是清楚。
在這裏簡單地用Otto重寫一下上面的代碼,讓你們感覺一下。github

public class TasksActivity extends Activity {
    private ListView mListView;
    public void onCreate() {
        OttoHelper.register(this); //對於Subscriber來講,register和unregister是有必要的
    }
    //...
    private void loadTasks() {
        new TaskModel().loadTasks();
    }
    
    // 用@Subscribe 來表示用這個方法處理某種事件,這種事件就是你的方法的參數。
    // 此外,public void是必須的,方法名能夠本身隨便取
     @Subscribe
    public void onTaskLoaded(TaskLoadedEvent event) {
        List<Task> tasks = event.tasks;
        // Update the task list view
    }
    
    @Subscribe
    public void onTaskLoadError(TaskLoadErrorEvent event) {
        //handler error
    }
    
    public void onDestroy() {
        OttoHelper.unregister(this)
    }
}

public class Task {
    // title, label, ..., and their getters and setters.
}

public class TaskModel {
    public void loadTasks() {
        // Load task from server
        OttoHelper.post(new TaskLoadedEvent(tasks)); //If load succeed
        OttoHelper.post(new TaskLoadErrorEvent(errorMsg, code); //If load error
    }
}

//Bus 通常是用做單例的,因此有一個helper會很方便
public class OttoHelper {
    private static final Bus sBusInstance = new Bus();
    public void static register(Object obj) {
        sBusInstance.register(obj);
    }
    
    public void static unregister(Object obj) {
        sBusInstance.register(obj);
    }
    
    public static void post(Object event) {
        sBusInstance.post(event);
    }
    
}

public class TaskLoadedEvent {
    public final List<Task> tasks;
    public TaskLoadedEvent(List<Task> tasks) {
        this.tasks = tasks;
    }
}

public class TaskLoadErrorEvent {
    //final field errorMsg and code, and construction, just like TaskLoadedEvent
}

須要說明的是,這裏只處理了一種事件(load task),因此Otto的優點還不是很明顯,然而你依然能夠明顯感覺到的是,TaskModelTasksActivity之間的耦合性更弱了。他們之間除了都用了TaskOttoHelper這兩個類以及用來傳遞事件的Event類,再沒有其餘共同的東西。服務器

當事件多了起來,Otto的優點就會很是明顯,增長的只是Event這些輕量級的POJO而已。
很是須要的一點是,最好每一種事件都使用特殊的Event類,千萬不要使用常見的類,好比String等等,更不要使用Object,由於Otto判斷Subscribe方法所處理的事件是經過方法的參數來判斷的。只要 instance of爲true,那麼這個方法就會獲得調用。舉個例子,在上面的TasksActivity裏,假如你有另一個Subscribe函數,它的參數是Object:架構

public class TasksActivity extends Activity {
    //...
    @Subscribe
    public void onTaskLoaded(TaskLoadedEvent event) {
        List<Task> tasks = event.tasks;
        // Update the task list view
    }
    
    @Subscribe
    public void onTaskLoadError(TaskLoadErrorEvent event) {
        //handler error
    }
    
    @Subscribe
    public void onSomeEvent(Object event) {
        //handle some-event
    }
}

在上面的事件中,bus post的任何事件,主要TasksActivity沒有unregister,那麼onSomeEvent就會獲得調用,好比task load成功了,你post了TaskLoadedEvent,那麼除了onTaskLoaded會獲得調用的以外,onSomeEvent也會獲得調用,這極可能不是你想要的。
我曾經就犯了這個愚蠢的錯誤,更搞笑的是,我在onSomeEvent裏面直接finish activity了,結果每一次別的事件過來,Activity就退出了,我覺得程序奔潰了,找了半天都找不出緣由,由於你正常finish activity是不會有error log的,最後終於懷疑到它頭上來,而後才解決了這個問題。
compile 'com.squareup:otto:1.3.7’加到你的dependencies裏面,試用如下,我敢打賭,你不再會定義callback了,Callbacks should be dead!.
固然,除了Otto,還有其餘的一些library,好比greenbot開源的EventBus,功能貌似比Otto更強,可是我我的選擇Otto,由於信仰Square,哈哈。。app

Disclaimer

這個例子只是用來講明Otto相對於callback的好處,對於一個正常的相似於上面的例子的app,這篇文章中提到的model和activity的交互方式並非最好的方式,有其餘更好的好比Functional Reactive Programming(RxJava)。同時,咱們應該使用MVP模式,把Activity當作View,而不是直接在裏面調用model。異步

若是有任何意見或建議,或者是發現文中有任何問題歡迎留言!函數

做者 小創 更多文章 | Github | 公衆號post

相關文章
相關標籤/搜索