前言
你準備好了嗎?本次列車開往 “重構” 之巔,時速 900km/s。風太大聽不見,但我就是可以很簡單很直的,給你講述事物本質和解決方案!⚡
專案常用架構比對
以下,對常見的 MVC、MVP、Clean、AAC 架構做個比對。
首先,一張表格展示各架構的類冗餘情況:
需求是,寫三個頁面,ListFragment、DetailFragment、PreviewFragment,每個頁面至少用到 3個 Note 業務、3個 User 業務。問:上述架構分別需編寫多少類?
MVC 架構的缺陷
-
View、Controller、Model 相互依賴,造成程式碼耦合。
-
難以分工,難以將 View、Controller、Model 分給不同的人寫。
-
難以維護,沒有中介軟體介面做緩衝,難以替換底層的實現。 public class NoteListFragment extends BaseFragment {
...
public void refreshList() { new Thread(new Runnable() { @Override public void run() {
//view 中直接依賴 model。那麼 view 須等 model 編寫好才能開工。 mNoteList = mDataManager.getNoteList(); mHandler.sendMessage(REFRESH_LIST, mNoteList); } }).start(); 複製程式碼
}
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg) { case REFRESH_LIST: mAdapter.setList(mNoteList); mAdapter.notifyDataSetChanged(); break; default: } } };
... }
MVP 架構的特點與侷限
- MVP 架構的特點是 面向介面程式設計。在 View、Presenter、Model 之間分別用 中介軟體介面 做銜接,當有新的底層實現時,能夠無縫替換。
- 此外,MVP 的 View 和 Model 並不產生依賴,因此可以說是對 View 和 Model 做了程式碼解耦。
有興趣的加入Android工程師交流Q群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相信你來交流,會有提升和收穫。
public class NoteListContract {
interface INoteListView {
void showDialog(String msg);
void showTip(String tip);
void refreshList(List<NoteBean> beans);
}
interface INoteListPresenter {
void requestNotes(String type);
void updateNotes(NoteBean... beans);
void deleteNotes(NoteBean... beans);
}
interface INoteListModel {
List<NoteBean> getNoteList();
int updateNote(NoteBean bean);
int deleteNote(NoteBean bean);
}
複製程式碼
}
但 MVP 架構有其侷限性。按我的理解,MVP 設計的初衷是, “讓天下沒有難替換的 View 和 Model” 。該初衷背後所基於的假設是,“上層邏輯穩定,但底層實現更替頻繁” 。在這個假設的引導下,使得三者中, 只有 Presenter 具備獨立意志和決定權,掌管著 UI 邏輯和業務邏輯,而 View 和 Model 只是外接的工具。
public class NoteListPresenter implements NoteListContract.INoteListPresenter {
private NoteListContract.INoteListModel mDataManager;
private NoteListContract.INoteListView mView;
@Override
public void requestNotes(String type) {
Observable.create(new ObservableOnSubscribe<List<NoteBean>>() {
@Override
public void subscribe(ObservableEmitter<List<NoteBean>> e) throws Exception {
List<NoteBean> noteBeans = mDataManager.getNoteList();
e.onNext(noteBeans);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<NoteBean>>() {
@Override
public void accept(List<NoteBean> beans) throws Exception {
//presenter 直接干預了 UI 在拿到資料後做什麼,使得邏輯上沒有發生解耦。
//正常來說,解耦意味著,presenter 的職能邊界僅限返回結果資料,
//由 UI 來依據響應碼處理 UI 邏輯。
mView.refreshList(beans);
}
});
}
...
複製程式碼
}
然而,這樣的假設多數時候並不實際。視覺化需求是變化多端的,在牽涉到視覺互動時,必然涉及 UI 邏輯的修改,也就是說,View 和 Presenter 的相互牽連,使得 UI 的改動需要 View 和 Presenter 編寫者配合著完成,增加溝通協作成本。
長久來看,二者都難以成長。Presenter 編寫者容易被各種非本職工作拖累,View 的編寫者不會嘗試獨立自主,例如通過多型等模式將 UI 封裝成可適應性的元件,反正 ... 有 Presenter 來各種 if else 嘛。
Clean 架構的特點和不足
為解決 Presenter 職能邊界不明確 的問題,在 Clean 架構中,業務邏輯的職能被轉移到領域層,由 Usecase 專職管理。Presenter 則弱化為 ViewModel ,作為代理資料請求,和銜接資料回撥的緩衝區。
Clean 架構的特點是 單向依賴、資料驅動程式設計。 View -> ViewModel -> Usecase -> Model 。
View 對 ViewModel 的單向依賴,是通過 databinding 特性實現的。ViewModel 只負責代理資料請求,在 Usecase 處理完業務返回結果資料時,結果資料被賦值給可觀察的 databinding 資料,而 View 則依據資料的變化而變化。
public class NoteListViewModel {
private ObservableList<NoteBean> mListObservable = new ObservableArrayList<>();
private void requestNotes(String type) {
if (null == mRequestNotesUsecase) {
mRequestNotesUsecase = new ProveListInitUseCase();
}
mUseCaseHandler.execute(mRequestNotesUsecase, new RequestNotesUsecase.RequestValues(type),
new UseCase.UseCaseCallback<RequestNotesUsecase.ResponseValue>() {
@Override
public void onSuccess(RequestNotesUsecase.ResponseValue response) {
//viewModel 的可觀察資料發生變化後,databinding 會自動更新 UI 展示。
mListObservable.clear();
mListObservable.addAll(response.getNotes());
}
@Override
public void onError() {
}
});
}
...
複製程式碼
}
有興趣的加入Android工程師交流Q群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相信你來交流,會有提升和收穫。
但 Clean 架構也有不足:粒度太細 。一個 Usecase 受限於請求引數,因而只能處理一類請求。View 請求的資料包含幾種型別,就至少需要準備幾個 Usecase。Usecase 是依據當前 View 對資料的需求量身定製的,因此 Usecase 的複用率極低,專案會因而急劇的增加類和重複程式碼。
public class RequestNotesUseCase extends UseCase<RequestNotesUseCase.RequestValues, RequestNotesUseCase.ResponseValue> {
private DataManager mDataManager;
@Override
protected void executeUseCase(final RequestValues values) {
List<NoteBean> noteBeans = mDataManager.getNotes();
...
getUseCaseCallback().onSuccess(new RequestNotesUseCase.ResponseValue(noteBeans));
}
//每新建一個 usecase 類,都需要手動為其配置 請求引數列表 和 響應引數列表。
public static final class RequestValues implements UseCase.RequestValues {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public static final class ResponseValue implements UseCase.ResponseValue {
public List<NoteBean> mBeans;
public ResponseValue(List<NoteBean> beans) {
mBeans = beans;
}
}
複製程式碼
}
AAC 架構的特點
AAC 也是資料驅動程式設計。只不過它不依賴於 MVVM 特性,而是直接在 View 中寫個觀察者回撥,以接收結果資料並處理 UI 邏輯。
public class NoteListFragment extends BaseFragment {
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel.getNote().observe(this, new Observer<NoteBean>() {
@Override
public void onChanged(@Nullable NoteBean bean) {
//update UI
}
});
}
...
複製程式碼
}
你完全可以將其理解為 B/S 架構:從 Web 前端向 Web 後端傳送了資料請求,後端在處理完畢後響應結果資料給前端,前端再依據需求處理 UI 邏輯。等於說, AAC 將業務完全壓到了 Model 層。
ViaBus 架構的由來及特點
上一輪重構專案在用 Clean 架構,為此我決定跳過 AAC,基於對移動端資料互動的理解,編寫“訊息驅動程式設計”架構。
由於藉助匯流排來代理資料的請求和響應,因此取名 ViaBus。
不同於以往的架構,ViaBus 明確界定了什麼是 UI,什麼是業務。
UI 的作用是視覺互動,為此 UI 的職責範圍是請求資料和處理 UI 邏輯 。業務的作用是供應資料,因此 業務的職責範圍是接收請求、處理資料、返回結果資料 。
UI 不需要知道資料是怎麼來的、通過誰來的,它只需向 bus 傳送一個請求,如果有業務註冊了該類 “請求處理者”,那麼自然有人來處理。業務也無需知道 UI 在拿到資料後會怎麼用,它只需向 bus 回傳結果,如果有 UI 註冊了“觀察響應者”,那麼自然有人接收,並依據響應碼行事。
這樣,在靜態 bus 的加持下,UI 和業務是完全解耦的,從根本上解決了相互牽連的問題。此外,不同於上述架構的每個 View 都要對應一個 Presenter 或 ViewModel,在 ViaBus 中,一個模組中的 UI 可以共享多個“業務處理者”例項,使 程式碼的複用率提升到100%。